1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

113 Commits

Author SHA1 Message Date
Jesse Hills
b914d6e305 Merge pull request #5173 from esphome/bump-2023.7.1
2023.7.1
2023-08-01 16:54:20 +12:00
Jesse Hills
956e19be7d Bump version to 2023.7.1 2023-08-01 12:08:36 +12:00
Maxime Michel
b3d5a4dfdb Fix graininess & streaks for 7.50inV2alt Waveshare e-paper (#5168) 2023-08-01 12:08:36 +12:00
Joris S
c63cdae84f invert min_rssi check (#5150) 2023-08-01 12:08:36 +12:00
J. Nick Koston
dec044ad8b Increase maximum number of BLE notifications (#5155) 2023-08-01 12:08:36 +12:00
PlainTechEnthusiast
2a12ec09fb update "Can't convert" warning to match others in homeassistant_sensor (#5162) 2023-08-01 12:08:36 +12:00
cvwillegen
91e920c498 Slightly lower template switch setup priority (#5163) 2023-08-01 12:08:35 +12:00
Stijn Tintel
9b19c45735 wifi: handle WIFI_REASON_ROAMING reason in event (#5153) 2023-08-01 12:08:35 +12:00
Keith Burzinski
3843d21dbf Swap ADC back to use 'int' because C3 (#5151) 2023-08-01 12:08:35 +12:00
Kuba Szczodrzyński
73db164fb1 Dashboard: use Popen() on Windows (#5110) 2023-08-01 12:08:35 +12:00
Jesse Hills
ab32dd7420 Merge pull request #5122 from esphome/bump-2023.7.0
2023.7.0
2023-07-19 15:44:34 +12:00
Jesse Hills
2a7aa2fc0d bump pyyaml to 6.0.1 2023-07-19 14:07:42 +12:00
Jesse Hills
f5e98eb86f Bump version to 2023.7.0 2023-07-19 12:59:51 +12:00
Jesse Hills
362a19c2e1 Merge pull request #5121 from esphome/bump-2023.7.0b3
2023.7.0b3
2023-07-19 12:40:27 +12:00
Jesse Hills
f4a4956dd4 Bump version to 2023.7.0b3 2023-07-19 11:41:24 +12:00
Jesse Hills
746488cabf Fix silence detection flag on voice assistant (#5120) 2023-07-19 11:41:24 +12:00
voed
4449248c6f [LD2410] Remove baud_rate check (#5112) 2023-07-19 11:41:24 +12:00
PlainTechEnthusiast
036e14ab7f Sigma delta fix (#4911) 2023-07-19 11:41:24 +12:00
Kevin P. Fleming
f840eee1b7 airthings_wave: Silence compiler warnings (#5098) 2023-07-19 11:41:24 +12:00
bwynants
553132443f P1 values for capacity tariff in Belgium (#5081) 2023-07-19 11:41:24 +12:00
Jesse Hills
d20242f589 Merge pull request #5107 from esphome/bump-2023.7.0b2
2023.7.0b2
2023-07-17 10:19:32 +12:00
Jesse Hills
68affce274 Bump version to 2023.7.0b2 2023-07-17 09:29:32 +12:00
Clyde Stubbs
c4b9065749 Add timeout filter (#5104) 2023-07-17 09:29:32 +12:00
Jesse Hills
d57a5d1793 Remove template switch restore_state (#5106) 2023-07-17 09:29:32 +12:00
Ilia Sotnikov
74e062fdb3 [Sprinkler] Resume fixes (#5100) 2023-07-17 09:29:32 +12:00
Pierre-Alexis Ciavaldini
6bdc0c92fe ESP32 enable ADC2 when wifi is disabled (#4381)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2023-07-17 09:29:32 +12:00
Jesse Hills
d7945de001 Dont do mqtt ip lookup if use_address has ip address (#5096)
* Dont do mqtt ip lookup id `use_address` is in config

* Fix after actually testing =)
2023-07-17 09:29:32 +12:00
Jesse Hills
3ba2a29e54 Merge pull request #5095 from esphome/bump-2023.7.0b1
2023.7.0b1
2023-07-13 10:53:51 +12:00
Jesse Hills
76b438f79c Bump version to 2023.7.0b1 2023-07-13 09:50:48 +12:00
Jesse Hills
bc14f06a07 Merge branch 'dev' into bump-2023.7.0b1 2023-07-13 09:50:47 +12:00
Jesse Hills
844cf316e2 Edit error message for pillow install to add version restrictions (#5094) 2023-07-13 09:38:24 +12:00
Lewis Baker
9344d85414 Fix PIDController::in_deadband() to give correct result when error is zero (#5078) 2023-07-13 08:57:45 +12:00
Sergey Dudanov
a539197bc4 New 'Duty Time' sensor component (#5069) 2023-07-13 08:48:16 +12:00
Sergey Dudanov
eb859e83f8 Fix use of optional<T> (#5091) 2023-07-13 08:44:30 +12:00
Pavlo Dudnytskyi
e4a640844c Fixing colon for tm1637 display if inverted set true (#5072) 2023-07-13 08:24:49 +12:00
Christian
119bbba254 Grove amend name (#5093) 2023-07-13 08:13:50 +12:00
danieltwagner
8c5978599a Add support for ATM90E26 (#4366)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 17:10:22 +12:00
Sergey Dudanov
bbf3d382e8 added uart final validate data bits (#5079) 2023-07-12 16:12:40 +12:00
Jesse Hills
c85f70a236 Bump esphome-dashboard to 20230711.0 (#5085) 2023-07-12 16:02:37 +12:00
Jesse Hills
7e52d4f5d6 Restrict pillow to versions before 10.0.0 (#5090) 2023-07-12 15:28:20 +12:00
Clyde Stubbs
6d9dbf9e54 Correct message for standard transmission. (#5088) 2023-07-12 15:22:52 +12:00
Clyde Stubbs
ec37dece12 Add MCP2515 12MHz xtal support (#5089) 2023-07-12 15:12:48 +12:00
Christian
e0fd8cd850 Add support for Grove tb6612 fng (#4797)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 15:02:53 +12:00
Kevin P. Fleming
cf65bd8ad7 airthings_wave: Battery level reporting (#4979) 2023-07-12 13:38:52 +12:00
Stefan Klug
8a9352939a Fix typo in mpu6050.cpp (#5086) 2023-07-12 13:29:38 +12:00
kswt
6ecc1c14d2 tuya_light: fix float->int conversion while setting color temperature (#5067)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: kswt <kswt@xmpp.is>
2023-07-12 13:28:48 +12:00
Stefan Rado
5f531ac9b0 Add TT21100 touchscreen component (#4793)
Co-authored-by: Rajan Patel <rpatel3001@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 13:19:19 +12:00
dentra
7a551081ee web server esp idf suppport (#3500)
* initial web_server_idf implementation

* initial web_server_idf implementation

* fix lint errors

* fix lint errors

* add captive_portal support

* fix lint errors

* fix lint errors

* add url decode

* Increase the max supported size of headers section in HTTP request

* add ota support

* add mulipart form data support (ota required)

* make linter happy

* make linter happy

* make linter happy

* fix review marks

* add DefaultHeaders support

* add DefaultHeaders support

* unify file names

* using std::isnan

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* drop multipart request support

* drop multipart request support

* drop multipart request support

* OTA is disabled by default

* fail when OTA enabled on IDF framework

* changing file permissions to remove execute bit

* return back PGM_P and strncpy_P macro

* temp web_server fix to be compat with 2022.12

* fix config handling w/o web_server

* fix compilation with "local"

* fully remove all idf ota

* merge with esphome 2023.6

* add core/hal to web_server_base

* Update esphome/components/web_server_base/__init__.py

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

* Update __init__.py

* Update __init__.py

---------

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-11 19:08:03 -05:00
KoenBreeman
74139985c9 RTC implementation of pcf8563 (#4998)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 09:19:28 +12:00
jan-hofmeier
f3cdcc008a Add Alpha3 pump component (#3787)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-11 17:12:43 +12:00
kahrendt
a391815921 Add Zio Ultrasonic Distance Sensor Component (#5059) 2023-07-11 16:24:18 +12:00
Kamil Trzciński
98fd092053 display: rename DisplayBufferRef to DisplayRef (#5002) 2023-07-10 16:38:28 -05:00
Jesse Hills
feee075122 Merge pull request #5077 from esphome/bump-2023.6.5
2023.6.5
2023-07-10 13:53:25 +12:00
Jimmy Hedman
ddde1ee31e Allow pillow versions over 10 (#5071) 2023-07-10 11:34:43 +12:00
Jimmy Hedman
c5aacdd682 Update RP2040 Aruino framwork and platform to latest (#5025) 2023-07-10 11:30:39 +12:00
Jesse Hills
a77cf1beec Bump version to 2023.6.5 2023-07-10 11:24:49 +12:00
Kevin P. Fleming
d7bfdd0efc binary_sensor: Validate max_length for on_click/on_double_click (#5068) 2023-07-10 11:24:49 +12:00
J. Nick Koston
62aee36f82 Fix bulk and single Bluetooth parser coexistence (#5073) 2023-07-10 11:24:49 +12:00
Trevor North
8ca9115dc8 Improve BME680 BSEC sensor device classes (#4859) 2023-07-10 10:03:54 +12:00
Fabian
8bf8892ab3 [Ethernet] ksz8081rna support (#4739)
Co-authored-by: Your Name <you@example.com>
2023-07-10 10:02:42 +12:00
Kevin P. Fleming
8739552c0b binary_sensor: Validate max_length for on_click/on_double_click (#5068) 2023-07-10 09:55:02 +12:00
J. Nick Koston
e6834f25ed Fix bulk and single Bluetooth parser coexistence (#5073) 2023-07-10 09:08:46 +12:00
NP v/d Spek
f9fc438de8 Fixed ili9xxx_display update() method (#5013)
There was an obsolete `if` statement left over from an other implementation.
2023-07-05 20:58:04 -05:00
Kamil Trzciński
677b2c6618 display: split DisplayBuffer and Display (#5001) 2023-07-05 14:33:26 -05:00
Tobias Oort
301a78f983 Adds 1.54" e-ink display (gdew0154m09) support to waveshare_epaper component (#4939)
* Added GDEW0154M09 in waveshare_epaper component

* noop change - trigger workflow

* Make linter happy

* Update test4.yaml

* linter doing linty things

* revert the newline removal.

* revert to prove unstable test

* add code back into test.

* no partial updates supported yet - removed from test.

* Update esphome/components/waveshare_epaper/waveshare_epaper.cpp

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>

---------

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2023-07-05 14:32:00 -05:00
Jimmy Hedman
979f014799 Make scheduler debuging work with idf >= 5 (#5052) 2023-07-05 22:05:27 +12:00
Fabian
a326dcaf0e [ili9xxx] Allow config of spi data rate. (#4701)
* Allow 80MHz ili9xxx display.

* python foo.

* update based on feedback.

* Change python

---------

Co-authored-by: Your Name <you@example.com>
2023-07-05 02:53:14 -05:00
lnicolas83
5bf2fa5c56 [ILI9xxx] Add ili9488_a (alternative gamma configuration for ILI9488) (#5027)
* Add ili9488_a

* Fix clang-tidy
2023-07-04 19:21:26 -05:00
Jimmy Hedman
fe0404a084 Some tests wasn't running (locally) (#5050) 2023-07-05 10:33:46 +12:00
Jesse Hills
22a1134f0e Fix when idf component has broken symlinks (#5058) 2023-07-05 10:31:58 +12:00
Jimmy Hedman
fc3d558d47 Initial debug component support for rp2040 (#5056) 2023-07-05 10:28:12 +12:00
Jesse Hills
45c72f1f22 Log start of i2c setup (#5049) 2023-07-04 15:26:31 +12:00
Jesse Hills
fd9cca565b Log component long time message at warning level (#5048) 2023-07-04 15:02:53 +12:00
Jesse Hills
0709367587 Merge pull request #5047 from esphome/bump-2023.6.4
2023.6.4
2023-07-04 14:42:36 +12:00
Jesse Hills
98277f6ceb Bump version to 2023.6.4 2023-07-04 14:03:58 +12:00
Jesse Hills
8dd509ba53 Update webserver to ea86d81 (#5023) 2023-07-04 14:03:57 +12:00
J. Nick Koston
8df455f55b Advertise noise is enabled (#5034) 2023-07-04 14:03:57 +12:00
Graham Brown
36782f13bf Add alarm to reserved ids (#5042) 2023-07-04 14:03:57 +12:00
Sergey Dudanov
e823067a6b fix template binary_sensor publish_initial_state option (#5033) 2023-07-04 14:03:57 +12:00
Ryan DeShone
c3ef12d580 [SCD30] Disable negative temperature offset (#4850) 2023-07-04 14:03:57 +12:00
Jesse Hills
d64d1650e3 Update webserver to ea86d81 (#5023) 2023-07-04 13:45:06 +12:00
J. Nick Koston
a74abb8ea8 Adjust signature for on_disconnect (#5009) 2023-07-04 12:57:44 +12:00
Fabian
e74ab00b3e Mopeka std fixes (#5041)
Co-authored-by: Your Name <you@example.com>
2023-07-04 12:55:04 +12:00
J. Nick Koston
2e2ac53071 Advertise noise is enabled (#5034) 2023-07-04 12:52:42 +12:00
Jimmy Hedman
87c0f48095 Prepare debug and logger component to work with idf 5.0 (#5036) 2023-07-04 12:49:27 +12:00
Jimmy Hedman
25b9bde0a5 Prepare ethernet to work with esp idf 5.0 (#5037) 2023-07-04 12:48:05 +12:00
guillempages
63d3a0e8b3 Improve the gamma settings for the S3-Box-lite display (#5046) 2023-07-04 12:43:03 +12:00
Graham Brown
4cc0f3fd53 Add alarm to reserved ids (#5042) 2023-07-04 12:28:19 +12:00
Sergey Dudanov
5b2176562b binary_sensor filters templatable delays (#5029) 2023-07-04 12:25:48 +12:00
Sergey Dudanov
099dc8d1d2 fix template binary_sensor publish_initial_state option (#5033) 2023-07-04 12:18:51 +12:00
Sergey Dudanov
cf98c497d5 binary_sensor removed unused filter (#5039) 2023-07-03 10:35:53 +12:00
dependabot[bot]
c5eb3941b9 Bump pytest-mock from 3.10.0 to 3.11.1 (#4977)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 11:44:23 +12:00
dependabot[bot]
0e93b8ee0d Bump esptool from 4.6 to 4.6.2 (#4949)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 11:44:10 +12:00
Ryan DeShone
807621402d [SCD30] Disable negative temperature offset (#4850) 2023-06-29 11:42:39 +12:00
Jesse Hills
321155eb40 Merge pull request #5020 from esphome/bump-2023.6.3
2023.6.3
2023-06-29 07:26:05 +12:00
Jesse Hills
d34c074b92 Bump version to 2023.6.3 2023-06-28 12:35:16 +12:00
Jesse Hills
abc8e903c1 Add CONFIG_BT_BLE_42_FEATURES_SUPPORTED for ble (#5008) 2023-06-28 12:35:15 +12:00
F.D.Castel
832ba38f1b Fixes compressed downloads (#5014)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-28 12:35:15 +12:00
esphomebot
70de2f5278 Synchronise Device Classes from Home Assistant (#5018) 2023-06-28 12:35:15 +12:00
Jesse Hills
604d4eec79 Update webserver to 56d73b5 (#5007) 2023-06-28 12:35:14 +12:00
Jesse Hills
ac5246e21d Remove yaml test cache (#5019) 2023-06-28 12:22:14 +12:00
Jesse Hills
951157dc26 Add CONFIG_BT_BLE_42_FEATURES_SUPPORTED for ble (#5008) 2023-06-28 11:35:35 +12:00
Jesse Hills
68119ddcd4 Attempt to fix script parameters (#4627) 2023-06-28 11:34:08 +12:00
F.D.Castel
c82be2cd60 Fixes compressed downloads (#5014)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-28 11:13:14 +12:00
esphomebot
9a149a7aba Synchronise Device Classes from Home Assistant (#5018) 2023-06-27 22:19:36 +00:00
Jesse Hills
b806eb6a61 Merge pull request #4997 from esphome/bump-2023.6.2
2023.6.2
2023-06-23 22:47:41 +12:00
Jesse Hills
39948db59a Bump version to 2023.6.2 2023-06-23 17:17:43 +12:00
Jesse Hills
fbfb4e2a73 Fix rp2040 pio tool download (#4994) 2023-06-23 17:17:43 +12:00
Samuel Sieb
595ac84779 remove unused static declarations (#4993) 2023-06-23 17:17:43 +12:00
Jesse Hills
746f72a279 Merge pull request #4992 from esphome/bump-2023.6.1
2023.6.1
2023-06-23 08:50:39 +12:00
Jesse Hills
dec6f04499 Bump version to 2023.6.1 2023-06-23 07:34:55 +12:00
Kamil Trzciński
a90d266017 display: fix white screen on binary displays (#4991) 2023-06-23 07:34:54 +12:00
Jimmy Hedman
df9fcf9850 Make ethernet_info work with esp-idf framework (#4976) 2023-06-23 07:34:54 +12:00
183 changed files with 6472 additions and 2022 deletions

View File

@@ -241,12 +241,6 @@ jobs:
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache@v3.3.1
with:
path: ~/.platformio
# yamllint disable-line rule:line-length
key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: |
. venv/bin/activate

View File

@@ -17,10 +17,11 @@ esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @ncareau
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix
@@ -31,6 +32,7 @@ esphome/components/api/* @OttoWinter
esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter
@@ -76,6 +78,7 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz
esphome/components/ens210/* @itn3rd77
@@ -102,6 +105,7 @@ esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal
@@ -200,6 +204,7 @@ esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
@@ -294,6 +299,7 @@ esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1
@@ -310,6 +316,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
@@ -321,3 +328,4 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68
esphome/components/zio_ultrasonic/* @kahrendt

View File

@@ -32,7 +32,7 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
from esphome.helpers import indent, is_ip_address
from esphome.util import (
run_external_command,
run_external_process,
@@ -308,8 +308,10 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "")
if (
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]
) and CONF_MQTT in config:
not is_ip_address(CORE.address)
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
):
from esphome import mqtt
host = mqtt.get_esphome_device_ip(

View File

@@ -24,6 +24,7 @@ ATTENUATION_MODES = {
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
@@ -78,6 +79,49 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
},
}
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
# TODO: add other variants
VARIANT_ESP32: {
4: adc2_channel_t.ADC2_CHANNEL_0,
0: adc2_channel_t.ADC2_CHANNEL_1,
2: adc2_channel_t.ADC2_CHANNEL_2,
15: adc2_channel_t.ADC2_CHANNEL_3,
13: adc2_channel_t.ADC2_CHANNEL_4,
12: adc2_channel_t.ADC2_CHANNEL_5,
14: adc2_channel_t.ADC2_CHANNEL_6,
27: adc2_channel_t.ADC2_CHANNEL_7,
25: adc2_channel_t.ADC2_CHANNEL_8,
26: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S2: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S3: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
}
def validate_adc_pin(value):
if str(value).upper() == "VCC":
@@ -89,11 +133,18 @@ def validate_adc_pin(value):
if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value)
variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
if (
variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and variant not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
):
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
if (
value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
and value not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
@@ -104,7 +155,7 @@ def validate_adc_pin(value):
)
if value != 17: # A0
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
@@ -112,7 +163,7 @@ def validate_adc_pin(value):
if CORE.is_rp2040:
value = pins.internal_gpio_input_pin_number(value)
if value not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
return pins.internal_gpio_input_pin_schema(value)
raise NotImplementedError

View File

@@ -20,15 +20,15 @@ namespace adc {
static const char *const TAG = "adc";
// 13bit for S2, and 12bit for all other esp32 variants
// 13-bit for S2, 12-bit for all other ESP32 variants
#ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
#if USE_ESP32_VARIANT_ESP32S2
static const int SOC_ADC_RTC_MAX_BITWIDTH = 13;
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12;
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif
#endif
@@ -47,14 +47,21 @@ extern "C"
#endif
#ifdef USE_ESP32
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_);
if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) {
adc1_config_channel_atten(channel1_, attenuation_);
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
if (!autorange_) {
adc2_config_channel_atten(channel2_, attenuation_);
}
}
// load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
1100, // default vref
&cal_characteristics_[i]);
switch (cal_value) {
@@ -136,9 +143,9 @@ void ADCSensor::update() {
#ifdef USE_ESP8266
float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
int32_t raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
int raw = analogRead(this->pin_->get_pin()); // NOLINT
int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
if (output_raw_) {
return raw;
@@ -150,29 +157,53 @@ float ADCSensor::sample() {
#ifdef USE_ESP32
float ADCSensor::sample() {
if (!autorange_) {
int raw = adc1_get_raw(channel_);
int raw = -1;
if (channel1_ != ADC1_CHANNEL_MAX) {
raw = adc1_get_raw(channel1_);
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
}
if (raw == -1) {
return NAN;
}
if (output_raw_) {
return raw;
}
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
return mv / 1000.0f;
}
int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_);
if (raw11 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_);
if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_);
if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_);
int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel1_);
if (raw11 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel1_);
if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel1_);
if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel1_);
}
}
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11);
if (raw11 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
if (raw6 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
if (raw2 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
}
}
}
}
@@ -181,10 +212,10 @@ float ADCSensor::sample() {
return NAN;
}
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c11 = std::min(raw11, ADC_HALF);
@@ -212,7 +243,7 @@ float ADCSensor::sample() {
adc_select_input(pin - 26);
}
int raw = adc_read();
int32_t raw = adc_read();
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(false);
}

View File

@@ -19,16 +19,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; }
void set_channel1(adc1_channel_t channel) {
channel1_ = channel;
channel2_ = ADC2_CHANNEL_MAX;
}
void set_channel2(adc2_channel_t channel) {
channel2_ = channel;
channel1_ = ADC1_CHANNEL_MAX;
}
void set_autorange(bool autorange) { autorange_ = autorange; }
#endif
/// Update adc values.
/// Update ADC values
void update() override;
/// Setup ADc
/// Setup ADC
void setup() override;
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
/// `HARDWARE_LATE` setup priority
float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
@@ -52,9 +59,10 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{};
adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif
};

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.core import CORE
from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant
from esphome.const import (
@@ -8,15 +10,15 @@ from esphome.const import (
CONF_NUMBER,
CONF_PIN,
CONF_RAW,
CONF_WIFI,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.core import CORE
from . import (
ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
validate_adc_pin,
)
@@ -25,7 +27,23 @@ AUTO_LOAD = ["voltage_sampler"]
def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
return config
def final_validate_config(config):
if CORE.is_esp32:
variant = get_esp32_variant()
if (
CONF_WIFI in fv.full_config.get()
and config[CONF_PIN][CONF_NUMBER]
in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(
f"{variant} doesn't support ADC on this pin when Wi-Fi is configured"
)
return config
@@ -55,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
validate_config,
)
FINAL_VALIDATE_SCHEMA = final_validate_config
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
@@ -81,5 +101,15 @@ async def to_code(config):
if CORE.is_esp32:
variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan))
if (
variant in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel1(chan))
elif (
variant in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel2(chan))

View File

@@ -58,6 +58,6 @@ async def to_code(config):
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@@ -3,26 +3,31 @@ import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE,
CONF_TEMPERATURE,
CONF_TVOC,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
UNIT_PERCENT,
UNIT_VOLT,
)
CODEOWNERS = ["@ncareau", "@jeromelaban"]
CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
DEPENDENCIES = ["ble_client"]
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
AirthingsWaveBase = airthings_wave_base_ns.class_(
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
@@ -34,9 +39,9 @@ BASE_SCHEMA = (
{
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
@@ -52,11 +57,21 @@ BASE_SCHEMA = (
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(
CONF_BATTERY_UPDATE_INTERVAL,
default="24h",
): cv.update_interval,
}
)
.extend(cv.polling_component_schema("5min"))
@@ -69,15 +84,20 @@ async def wave_base_to_code(var, config):
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
if config_humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config_humidity)
cg.add(var.set_humidity(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
if config_temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config_temperature)
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
if config_pressure := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(config_pressure)
cg.add(var.set_pressure(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
if config_tvoc := config.get(CONF_TVOC):
sens = await sensor.new_sensor(config_tvoc)
cg.add(var.set_tvoc(sens))
if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config_battery_voltage)
cg.add(var.set_battery_voltage(sens))
if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
cg.add(var.set_battery_update_interval(config_battery_update_interval))

View File

@@ -1,5 +1,8 @@
#include "airthings_wave_base.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32
namespace esphome {
@@ -18,22 +21,26 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
}
case ESP_GATTC_DISCONNECT_EVT: {
this->handle_ = 0;
this->acp_handle_ = 0;
this->cccd_handle_ = 0;
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
break;
if (this->request_read_values_()) {
if (!this->read_battery_next_update_) {
this->node_state = espbt::ClientState::ESTABLISHED;
} else {
// delay setting node_state to ESTABLISHED until confirmation of the notify registration
this->request_battery_();
}
}
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
this->request_read_values_();
// ensure that the client will be disconnected even if no responses arrive
this->set_response_timeout_();
break;
}
@@ -50,15 +57,29 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->get_conn_id())
break;
if (param->notify.handle == this->acp_handle_) {
this->read_battery_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
void AirthingsWaveBase::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (!this->parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
this->parent()->set_enabled(true);
@@ -69,12 +90,119 @@ void AirthingsWaveBase::update() {
}
}
void AirthingsWaveBase::request_read_values_() {
bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
return false;
}
this->handle_ = chr->handle;
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
return false;
}
this->response_pending_();
return true;
}
bool AirthingsWaveBase::request_battery_() {
uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
uint8_t cccd_value[2] = {1, 0};
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto reg_status =
esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
if (reg_status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
return false;
}
this->acp_handle_ = chr->handle;
this->cccd_handle_ = descr->handle;
auto descr_status =
esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (descr_status) {
ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
return false;
}
auto chr_status =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
&battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (chr_status) {
ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
return false;
}
this->response_pending_();
return true;
}
void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
auto *value = (AccessControlPointResponse *) (&raw_value[2]);
if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
if (this->battery_voltage_ != nullptr) {
float voltage = value->battery / 1000.0f;
this->battery_voltage_->publish_state(voltage);
}
// read the battery again at the configured update interval
if (this->battery_update_interval_ != this->update_interval_) {
this->read_battery_next_update_ = false;
this->set_timeout("battery", this->battery_update_interval_,
[this]() { this->read_battery_next_update_ = true; });
}
}
this->response_received_();
}
void AirthingsWaveBase::response_pending_() {
this->responses_pending_++;
this->set_response_timeout_();
}
void AirthingsWaveBase::response_received_() {
if (--this->responses_pending_ == 0) {
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
}
}
void AirthingsWaveBase::set_response_timeout_() {
this->set_timeout("response_timeout", 30 * 1000, [this]() {
this->responses_pending_ = 1;
this->response_received_();
});
}
} // namespace airthings_wave_base

View File

@@ -1,5 +1,8 @@
#pragma once
// All information related to reading battery levels came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32
#include <esp_gattc_api.h>
@@ -14,6 +17,11 @@
namespace esphome {
namespace airthings_wave_base {
namespace espbt = esphome::esp32_ble_tracker;
static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d;
static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902);
class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode {
public:
AirthingsWaveBase() = default;
@@ -27,21 +35,53 @@ class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientN
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
void set_battery_voltage(sensor::Sensor *voltage) {
battery_voltage_ = voltage;
this->read_battery_next_update_ = true;
}
void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; }
protected:
bool is_valid_voc_value_(uint16_t voc);
virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0;
void request_read_values_();
bool request_read_values_();
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID sensors_data_characteristic_uuid_;
uint16_t acp_handle_{0};
uint16_t cccd_handle_{0};
espbt::ESPBTUUID access_control_point_characteristic_uuid_;
uint8_t responses_pending_{0};
void response_pending_();
void response_received_();
void set_response_timeout_();
// default to *not* reading battery voltage from the device; the
// set_* function for the battery sensor will set this to 'true'
bool read_battery_next_update_{false};
bool request_battery_();
void read_battery_(uint8_t *raw_value, uint16_t value_len);
uint32_t battery_update_interval_{};
struct AccessControlPointResponse {
uint32_t unused1;
uint8_t unused2;
uint8_t illuminance;
uint8_t unused3[10];
uint16_t unused4[4];
uint16_t battery;
uint16_t unused5;
};
};
} // namespace airthings_wave_base

View File

@@ -26,12 +26,9 @@ void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
}
this->response_received_();
}
void AirthingsWaveMini::dump_config() {
@@ -42,11 +39,14 @@ void AirthingsWaveMini::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
}
AirthingsWaveMini::AirthingsWaveMini() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
}
} // namespace airthings_wave_mini

View File

@@ -7,8 +7,11 @@
namespace esphome {
namespace airthings_wave_mini {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
public:
@@ -17,7 +20,7 @@ class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
void dump_config() override;
protected:
void read_sensors(uint8_t *value, uint16_t value_len) override;
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
struct WaveMiniReadings {
uint16_t unused01;

View File

@@ -43,20 +43,17 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
} else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
}
}
this->response_received_();
}
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return radon <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return co2 <= 16383; }
void AirthingsWavePlus::dump_config() {
// these really don't belong here, but there doesn't seem to be a
@@ -66,6 +63,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
@@ -73,8 +71,10 @@ void AirthingsWavePlus::dump_config() {
}
AirthingsWavePlus::AirthingsWavePlus() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
}
} // namespace airthings_wave_plus

View File

@@ -7,8 +7,11 @@
namespace esphome {
namespace airthings_wave_plus {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public:
@@ -24,7 +27,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
bool is_valid_radon_value_(uint16_t radon);
bool is_valid_co2_value_(uint16_t co2);
void read_sensors(uint8_t *value, uint16_t value_len) override;
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};

View File

@@ -53,12 +53,12 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await airthings_wave_base.wave_base_to_code(var, config)
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
if config_radon := config.get(CONF_RADON):
sens = await sensor.new_sensor(config_radon)
cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config:
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
sens = await sensor.new_sensor(config_radon_long_term)
cg.add(var.set_radon_long_term(sens))
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens))

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@jan-hofmeier"]

View File

@@ -0,0 +1,189 @@
#include "alpha3.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <lwip/sockets.h> //gives ntohl
#ifdef USE_ESP32
namespace esphome {
namespace alpha3 {
static const char *const TAG = "alpha3";
void Alpha3::dump_config() {
ESP_LOGCONFIG(TAG, "ALPHA3");
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Head", this->head_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
}
void Alpha3::setup() {}
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor) {
if (sensor == nullptr)
return;
// we need to handle cases where a value is split over two packets
const int16_t value_length = 4; // 32bit float
// offset inside current response packet
auto rel_offset = value_offset - response_offset;
if (rel_offset <= -value_length)
return; // aready passed the value completly
if (rel_offset >= length)
return; // value not in this packet
auto start_offset = std::max(0, rel_offset);
auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
auto copy_length = end_offset - start_offset;
auto buffer_offset = std::max(-rel_offset, 0);
std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
if (rel_offset + value_length <= length) {
// we have the whole value
void *buffer = this->buffer_; // to prevent warnings when casting the pointer
*((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian
float fvalue = *((float *) buffer);
sensor->publish_state(fvalue * factor);
}
}
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
}
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum
this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
}
auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
float factor) {
this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
} else {
ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
this->response_type_[6], this->response_type_[7]);
}
this->response_offset_ += length;
}
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
this->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
return;
auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
if (ret) {
ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->node_state = espbt::ClientState::IDLE;
if (this->flow_sensor_ != nullptr)
this->flow_sensor_->publish_state(NAN);
if (this->head_sensor_ != nullptr)
this->head_sensor_->publish_state(NAN);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(NAN);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->speed_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->voltage_sensor_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
this->geni_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle == this->geni_handle_) {
this->handle_geni_response_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
void Alpha3::send_request_(uint8_t *request, size_t len) {
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
void Alpha3::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
delay(25); // need to wait between requests
}
if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
this->voltage_sensor_ != nullptr) {
uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
this->send_request_(geni_request_power, sizeof(geni_request_power));
delay(25); // need to wait between requests
}
}
} // namespace alpha3
} // namespace esphome
#endif

View File

@@ -0,0 +1,73 @@
#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 alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
protected:
sensor::Sensor *flow_sensor_{nullptr};
sensor::Sensor *head_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
uint16_t geni_handle_;
int16_t response_length_;
int16_t response_offset_;
uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
uint8_t buffer_[4];
void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor);
void handle_geni_response_(const uint8_t *response, uint16_t length);
void send_request_(uint8_t *request, size_t len);
bool is_current_response_type_(const uint8_t *response_type);
};
} // namespace alpha3
} // namespace esphome
#endif

View File

@@ -0,0 +1,85 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_CURRENT,
CONF_FLOW,
CONF_HEAD,
CONF_POWER,
CONF_SPEED,
CONF_VOLTAGE,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_METER,
UNIT_CUBIC_METER_PER_HOUR,
UNIT_REVOLUTIONS_PER_MINUTE,
)
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Alpha3),
cv.Optional(CONF_FLOW): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
accuracy_decimals=2,
),
cv.Optional(CONF_HEAD): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("15s"))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_FLOW in config:
sens = await sensor.new_sensor(config[CONF_FLOW])
cg.add(var.set_flow_sensor(sens))
if CONF_HEAD in config:
sens = await sensor.new_sensor(config[CONF_HEAD])
cg.add(var.set_head_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_SPEED in config:
sens = await sensor.new_sensor(config[CONF_SPEED])
cg.add(var.set_speed_sensor(sens))
if CONF_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(sens))

View File

@@ -1420,6 +1420,7 @@ message VoiceAssistantRequest {
bool start = 1;
string conversation_id = 2;
bool use_vad = 3;
}
message VoiceAssistantResponse {

View File

@@ -907,12 +907,13 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) {
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) {
if (!this->voice_assistant_subscription_)
return false;
VoiceAssistantRequest msg;
msg.start = start;
msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(msg);
}
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {

View File

@@ -124,7 +124,7 @@ class APIConnection : public APIServerConnection {
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
this->voice_assistant_subscription_ = msg.subscribe;
}
bool request_voice_assistant(bool start, const std::string &conversation_id);
bool request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad);
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif

View File

@@ -6348,6 +6348,10 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->start = value.as_bool();
return true;
}
case 3: {
this->use_vad = value.as_bool();
return true;
}
default:
return false;
}
@@ -6365,6 +6369,7 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start);
buffer.encode_string(2, this->conversation_id);
buffer.encode_bool(3, this->use_vad);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const {
@@ -6377,6 +6382,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" conversation_id: ");
out.append("'").append(this->conversation_id).append("'");
out.append("\n");
out.append(" use_vad: ");
out.append(YESNO(this->use_vad));
out.append("\n");
out.append("}");
}
#endif

View File

@@ -1655,6 +1655,7 @@ class VoiceAssistantRequest : public ProtoMessage {
public:
bool start{false};
std::string conversation_id{};
bool use_vad{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;

View File

@@ -323,16 +323,16 @@ void APIServer::on_shutdown() {
}
#ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant(const std::string &conversation_id) {
bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) {
for (auto &c : this->clients_) {
if (c->request_voice_assistant(true, conversation_id))
if (c->request_voice_assistant(true, conversation_id, use_vad))
return true;
}
return false;
}
void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, ""))
if (c->request_voice_assistant(false, "", false))
return;
}
}

View File

@@ -81,7 +81,7 @@ class APIServer : public Component, public Controller {
#endif
#ifdef USE_VOICE_ASSISTANT
bool start_voice_assistant(const std::string &conversation_id);
bool start_voice_assistant(const std::string &conversation_id, bool use_vad);
void stop_voice_assistant();
#endif

View File

@@ -47,7 +47,7 @@ async def async_run_logs(config, address):
except APIConnectionError:
cli.disconnect()
async def on_disconnect():
async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()

View File

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

View File

@@ -0,0 +1,235 @@
#include "atm90e26.h"
#include "atm90e26_reg.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e26 {
static const char *const TAG = "atm90e26";
void ATM90E26Component::update() {
if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) {
this->status_set_warning();
return;
}
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(this->get_line_voltage_());
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(this->get_line_current_());
}
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(this->get_active_power_());
}
if (this->reactive_power_sensor_ != nullptr) {
this->reactive_power_sensor_->publish_state(this->get_reactive_power_());
}
if (this->power_factor_sensor_ != nullptr) {
this->power_factor_sensor_->publish_state(this->get_power_factor_());
}
if (this->forward_active_energy_sensor_ != nullptr) {
this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_());
}
if (this->reverse_active_energy_sensor_ != nullptr) {
this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
this->status_clear_warning();
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
mmode |= (gain_pga_ & 0x7) << 13;
mmode |= (n_line_gain_ & 0x3) << 11;
this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
this->write16_(ATM90E26_REGISTER_FUNCEN,
0x0030); // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA);
if (read != 0x0030) {
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read);
this->mark_failed();
return;
}
// TODO: 100 * <nominal voltage, e.g. 230> * sqrt(2) * <fraction of nominal, e.g. 0.9> / (4 * gain_voltage/32768)
this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD); // Voltage sag threshhold 0x1F2F
// Set metering calibration values
this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678); // CAL Metering calibration startup command
// Configure
this->write16_(ATM90E26_REGISTER_MMODE, mmode); // Metering Mode Configuration (see above)
this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16)); // PL Constant MSB
this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF); // PL Constant LSB
// Calibrate this to be 1 pulse per Wh
this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_); // L Line Calibration Gain (active power metering)
this->write16_(ATM90E26_REGISTER_LPHI, 0x0000); // L Line Calibration Angle
this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000); // N Line Calibration Gain
this->write16_(ATM90E26_REGISTER_NPHI, 0x0000); // N Line Calibration Angle
this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD); // Active Startup Power Threshold (default) = 2237
this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000); // Active No-Load Power Threshold
this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold (default) = 2796
this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000); // Reactive No-Load Power Threshold
// Compute Checksum for the registers we set above
// low byte = sum of all bytes
uint16_t cs =
((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) +
(pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) &
0xFF;
// high byte = XOR of all bytes
cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^
(pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC)
<< 8;
this->write16_(ATM90E26_REGISTER_CS1, cs);
ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs);
// Set measurement calibration values
this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678); // Measurement calibration startup command, registers 31-3A
this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_); // Voltage RMS gain
this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_); // L line current RMS gain
this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530); // N Line Current RMS Gain
this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000); // Voltage Offset
this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000); // L Line Current Offset
this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000); // N Line Current Offse
this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000); // L Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000); // L Line Reactive Power Offset
this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000); // N Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000); // N Line Reactive Power Offset
// Compute Checksum for the registers we set above
cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF;
cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8;
this->write16_(ATM90E26_REGISTER_CS2, cs);
ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs);
this->write16_(ATM90E26_REGISTER_CALSTART,
0x8765); // Checks correctness of 21-2B registers and starts normal metering if ok
this->write16_(ATM90E26_REGISTER_ADJSTART,
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
if (sys_status & 0xC000) { // Checksum 1 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS1));
this->mark_failed();
}
if (sys_status & 0x3000) { // Checksum 2 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS2));
this->mark_failed();
}
}
void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A", this->current_sensor_);
LOG_SENSOR(" ", "Power A", this->power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];
uint16_t output;
this->enable();
delayMicroseconds(4);
this->write_byte(a_register | 0x80);
delayMicroseconds(4);
this->read_array(data, 2);
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
return output;
}
void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
this->enable();
delayMicroseconds(4);
this->write_byte(a_register & 0x7F);
delayMicroseconds(4);
this->write_byte((val >> 8) & 0xFF);
this->write_byte(val & 0xFF);
this->disable();
}
float ATM90E26Component::get_line_current_() {
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
return current / 1000.0f;
}
float ATM90E26Component::get_line_voltage_() {
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
return voltage / 100.0f;
}
float ATM90E26Component::get_active_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_reactive_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_power_factor_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
if (val & 0x8000) {
return -(val & 0x7FF) / 1000.0f;
} else {
return val / 1000.0f;
}
}
float ATM90E26Component::get_forward_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
this->cumulative_forward_active_energy_ += val;
} else {
this->cumulative_forward_active_energy_ = val;
}
// The register holds thenths of pulses, we want to output Wh
return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_reverse_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
this->cumulative_reverse_active_energy_ += val;
} else {
this->cumulative_reverse_active_energy_ = val;
}
return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
return freq / 100.0f;
}
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,72 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace atm90e26 {
class ATM90E26Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }
void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; }
void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; }
void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; }
void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; }
void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; }
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_line_freq(int freq) { line_freq_ = freq; }
void set_meter_constant(float val) { meter_constant_ = val; }
void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; }
void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; }
void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; }
void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; }
void set_gain_pga(uint16_t gain) { gain_pga_ = gain; }
void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; }
protected:
uint16_t read16_(uint8_t a_register);
int read32_(uint8_t addr_h, uint8_t addr_l);
void write16_(uint8_t a_register, uint16_t val);
float get_line_voltage_();
float get_line_current_();
float get_active_power_();
float get_reactive_power_();
float get_power_factor_();
float get_forward_active_energy_();
float get_reverse_active_energy_();
float get_frequency_();
float get_chip_temperature_();
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
uint32_t cumulative_forward_active_energy_{0};
uint32_t cumulative_reverse_active_energy_{0};
uint16_t gain_metering_{7481};
uint16_t gain_voltage_{26400};
uint16_t gain_ct_{31251};
uint16_t gain_pga_{0x4};
uint16_t n_line_gain_{0x2};
int line_freq_{60};
float meter_constant_{3200.0f};
uint32_t pl_const_{1429876};
};
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,70 @@
#pragma once
namespace esphome {
namespace atm90e26 {
/* Status and Special Register */
static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00; // Software Reset
static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01; // System Status
static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02; // Function Enable
static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03; // Voltage Sag Threshold
static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04; // Small-Power Mode
static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06; // Last Read/Write SPI/UART Value
/* Metering Calibration and Configuration Register */
static const uint8_t ATM90E26_REGISTER_LSB = 0x08; // RMS/Power 16-bit LSB
static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20; // Calibration Start Command
static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21; // High Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22; // Low Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23; // L Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_LPHI = 0x24; // L Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25; // N Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_NPHI = 0x26; // N Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27; // Active Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28; // Active No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29; // Reactive Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A; // Reactive No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B; // Metering Mode Configuration
static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C; // Checksum 1
/* Measurement Calibration Register */
static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30; // Measurement Calibration Start Command
static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31; // Voltage RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32; // L Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33; // N Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34; // Voltage Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35; // L Line Current Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36; // N Line Current Offse
static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37; // L Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38; // L Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39; // N Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A; // N Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B; // Checksum 2
/* Energy Register */
static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40; // Forward Active Energy
static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41; // Reverse Active Energy
static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42; // Absolute Active Energy
static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43; // Forward (Inductive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44; // Reverse (Capacitive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45; // Absolute Reactive Energy
static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46; // Metering Status
/* Measurement Register */
static const uint8_t ATM90E26_REGISTER_IRMS = 0x48; // L Line Current RMS
static const uint8_t ATM90E26_REGISTER_URMS = 0x49; // Voltage RMS
static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A; // L Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B; // L Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C; // Voltage Frequency
static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D; // L Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E; // Phase Angle between Voltage and L Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F; // L Line Mean Apparent Power
static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68; // N Line Current rms
static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A; // N Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B; // N Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D; // N Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E; // Phase Angle between Voltage and N Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F; // N Line Mean Apparent Power
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,157 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga"
CONF_GAIN_METERING = "gain_metering"
CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct"
LINE_FREQS = {
"50HZ": 50,
"60HZ": 60,
}
PGA_GAINS = {
"1X": 0x4,
"4X": 0x0,
"8X": 0x1,
"16X": 0x2,
"24X": 0x3,
}
atm90e26_ns = cg.esphome_ns.namespace("atm90e26")
ATM90E26Component = atm90e26_ns.class_(
"ATM90E26Component", cg.PollingComponent, spi.SPIDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ATM90E26Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Required(CONF_METER_CONSTANT): cv.positive_float,
cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t,
cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t,
cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range(
min=0, max=32767
),
cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t,
cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema())
)
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)
if CONF_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))
if CONF_REACTIVE_POWER in config:
sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER])
cg.add(var.set_reactive_power_sensor(sens))
if CONF_POWER_FACTOR in config:
sens = await sensor.new_sensor(config[CONF_POWER_FACTOR])
cg.add(var.set_power_factor_sensor(sens))
if CONF_FORWARD_ACTIVE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_FORWARD_ACTIVE_ENERGY])
cg.add(var.set_forward_active_energy_sensor(sens))
if CONF_REVERSE_ACTIVE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_REVERSE_ACTIVE_ENERGY])
cg.add(var.set_reverse_active_energy_sensor(sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_freq_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
cg.add(var.set_pl_const(config[CONF_PL_CONST]))
cg.add(var.set_gain_metering(config[CONF_GAIN_METERING]))
cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE]))
cg.add(var.set_gain_ct(config[CONF_GAIN_CT]))
cg.add(var.set_gain_pga(config[CONF_GAIN_PGA]))

View File

@@ -95,6 +95,14 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_(
@@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry()
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
@FILTER_REGISTRY.register("invert", InvertFilter, {})
def register_filter(name, filter_type, schema):
return FILTER_REGISTRY.register(name, filter_type, schema)
@register_filter("invert", InvertFilter, {})
async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_on_off",
DelayedOnOffFilter,
cv.Any(
cv.templatable(cv.positive_time_period_milliseconds),
cv.Schema(
{
cv.Required(CONF_TIME_ON): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Required(CONF_TIME_OFF): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
msg="'delayed_on_off' filter requires either a delay time to be used for both "
"turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if "
"different delay times are required.",
),
)
async def delayed_on_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
if isinstance(config, dict):
template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32)
cg.add(var.set_on_delay(template_))
template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32)
cg.add(var.set_off_delay(template_))
else:
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_on_delay(template_))
cg.add(var.set_off_delay(template_))
return var
@FILTER_REGISTRY.register(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
)
async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
@FILTER_REGISTRY.register(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
)
async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
@register_filter(
"autorepeat",
AutorepeatFilter,
cv.All(
@@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
return var
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
@register_filter("lambda", LambdaFilter, cv.returning_lambda)
async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool)
@@ -323,6 +359,18 @@ def validate_multi_click_timing(value):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
def validate_click_timing(value):
for v in value:
min_length = v.get(CONF_MIN_LENGTH)
max_length = v.get(CONF_MAX_LENGTH)
if max_length < min_length:
raise cv.Invalid(
f"Max length ({max_length}) must be larger than min length ({min_length})."
)
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
@@ -342,27 +390,33 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
cv.Optional(CONF_ON_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{

View File

@@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) {
}
}
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
if (value) {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
} else {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
}
return {};
}
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
if (value) {
this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
return {};
} else {
this->cancel_timeout("ON");
@@ -51,10 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
if (!value) {
this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
return {};
} else {
this->cancel_timeout("OFF");
@@ -114,15 +111,6 @@ LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
if (this->last_value_.has_value() && *this->last_value_ == value) {
return {};
} else {
this->last_value_ = value;
return value;
}
}
} // namespace binary_sensor
} // namespace esphome

View File

@@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
@@ -29,38 +30,40 @@ class Filter {
class DelayedOnOffFilter : public Filter, public Component {
public:
explicit DelayedOnOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_on_delay(T delay) { this->on_delay_ = delay; }
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> on_delay_{};
TemplatableValue<uint32_t> off_delay_{};
};
class DelayedOnFilter : public Filter, public Component {
public:
explicit DelayedOnFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> delay_{};
};
class DelayedOffFilter : public Filter, public Component {
public:
explicit DelayedOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
uint32_t delay_;
TemplatableValue<uint32_t> delay_{};
};
class InvertFilter : public Filter {
@@ -105,14 +108,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_;
};
class UniqueFilter : public Filter {
public:
optional<bool> new_value(bool value, bool is_initial) override;
protected:
optional<bool> last_value_{};
};
} // namespace binary_sensor
} // namespace esphome

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid,
cv.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-90, max=-30)
cv.decibel, cv.int_range(min=-100, max=-30)
),
}
)

View File

@@ -51,7 +51,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
this->found_ = false;
}
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
if (this->check_minimum_rssi_ && this->minimum_rssi_ <= device.get_rssi()) {
if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
return false;
}
switch (this->match_by_) {

View File

@@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
return ESP_OK;
}
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
}
} // namespace bluetooth_proxy
} // namespace esphome

View File

@@ -14,6 +14,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
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;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);

View File

@@ -198,6 +198,12 @@ void BluetoothProxy::loop() {
}
}
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
if (this->raw_advertisements_)
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) {
if (connection->get_address() == address)
@@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
}
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
}
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@@ -444,6 +451,7 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
}
this->api_connection_ = nullptr;
this->raw_advertisements_ = false;
this->parent_->recalculate_advertisement_parser_types();
}
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {

View File

@@ -51,6 +51,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override;
void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection);

View File

@@ -6,8 +6,10 @@ from esphome.const import (
CONF_HUMIDITY,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
@@ -17,8 +19,6 @@ from esphome.const import (
UNIT_PERCENT,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
)
from . import (
BME680BSECComponent,
@@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
TYPES = [
CONF_TEMPERATURE,
@@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
@@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
@@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT,
),
}

View File

@@ -12,6 +12,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
@@ -24,6 +25,7 @@ IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]

View File

@@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
if (use_extended_id) {
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} else {
ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
}
if (size > CAN_MAX_DATA_LENGTH)
size = CAN_MAX_DATA_LENGTH;

View File

@@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(["esp32", "esp8266"]),
)
@@ -34,8 +33,9 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.is_esp32:
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)
if CORE.using_arduino:
if CORE.is_esp32:
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "captive_portal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
@@ -46,10 +44,12 @@ void CaptivePortal::start() {
this->base_->add_ota_handler();
}
#ifdef USE_ARDUINO
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", (uint32_t) ip);
#endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
@@ -67,7 +67,7 @@ void CaptivePortal::start() {
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == "/") {
AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
response->addHeader("Content-Encoding", "gzip");
req->send(response);
return;
@@ -91,5 +91,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,9 +1,9 @@
#pragma once
#ifdef USE_ARDUINO
#include <memory>
#ifdef USE_ARDUINO
#include <DNSServer.h>
#endif
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
@@ -18,18 +18,22 @@ class CaptivePortal : public AsyncWebHandler, public Component {
CaptivePortal(web_server_base::WebServerBase *base);
void setup() override;
void dump_config() override;
#ifdef USE_ARDUINO
void loop() override {
if (this->dns_server_ != nullptr)
this->dns_server_->processNextRequest();
}
#endif
float get_setup_priority() const override;
void start();
bool is_active() const { return this->active_; }
void end() {
this->active_ = false;
this->base_->deinit();
#ifdef USE_ARDUINO
this->dns_server_->stop();
this->dns_server_ = nullptr;
#endif
}
bool canHandle(AsyncWebServerRequest *request) override {
@@ -58,12 +62,12 @@ class CaptivePortal : public AsyncWebHandler, public Component {
web_server_base::WebServerBase *base_;
bool initialized_{false};
bool active_{false};
#ifdef USE_ARDUINO
std::unique_ptr<DNSServer> dns_server_{nullptr};
#endif
};
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace captive_portal
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -38,7 +38,6 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "esp8266"]),
)

View File

@@ -5,6 +5,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/version.h"
#include <cinttypes>
#ifdef USE_ESP32
@@ -13,6 +14,7 @@
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#include <esp_chip_info.h>
#else
#include <rom/rtc.h>
#endif
@@ -20,8 +22,12 @@
#endif // USE_ESP32
#ifdef USE_ARDUINO
#ifdef USE_RP2040
#include <Arduino.h>
#else
#include <Esp.h>
#endif
#endif
namespace esphome {
namespace debug {
@@ -33,6 +39,8 @@ static uint32_t get_free_heap() {
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#elif defined(USE_RP2040)
return rp2040.getFreeHeap();
#endif
}
@@ -61,9 +69,9 @@ void DebugComponent::dump_config() {
device_info += ESPHOME_VERSION;
this->free_heap_ = get_free_heap();
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
const char *flash_mode;
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
case FM_QIO:
@@ -272,6 +280,11 @@ void DebugComponent::dump_config() {
reset_reason = ESP.getResetReason().c_str();
#endif
#ifdef USE_RP2040
ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu());
device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
#endif // USE_RP2040
#ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) {
if (device_info.length() > 255)
@@ -289,7 +302,7 @@ void DebugComponent::loop() {
uint32_t new_free_heap = get_free_heap();
if (new_free_heap < this->free_heap_ / 2) {
this->free_heap_ = new_free_heap;
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
this->status_momentary_warning("heap", 1000);
}

View File

@@ -18,10 +18,11 @@ from esphome.core import coroutine_with_priority
IS_PLATFORM_COMPONENT = True
display_ns = cg.esphome_ns.namespace("display")
Display = display_ns.class_("Display")
DisplayBuffer = display_ns.class_("DisplayBuffer")
DisplayPage = display_ns.class_("DisplayPage")
DisplayPagePtr = DisplayPage.operator("ptr")
DisplayBufferRef = DisplayBuffer.operator("ref")
DisplayRef = Display.operator("ref")
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
DisplayPageShowNextAction = display_ns.class_(
"DisplayPageShowNextAction", automation.Action
@@ -96,7 +97,7 @@ async def setup_display_core_(var, config):
pages = []
for conf in config[CONF_PAGES]:
lambda_ = await cg.process_lambda(
conf[CONF_LAMBDA], [(DisplayBufferRef, "it")], return_type=cg.void
conf[CONF_LAMBDA], [(DisplayRef, "it")], return_type=cg.void
)
page = cg.new_Pvariable(conf[CONF_ID], lambda_)
pages.append(page)

View File

@@ -0,0 +1,343 @@
#include "display.h"
#include <utility>
#include "esphome/core/log.h"
namespace esphome {
namespace display {
static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 0);
const Color COLOR_ON(255, 255, 255, 255);
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void Display::clear() { this->fill(COLOR_OFF); }
void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy;
while (true) {
this->draw_pixel_at(x1, y1, color);
if (x1 == x2 && y1 == y2)
break;
int32_t e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x1 += sx;
}
if (e2 <= dx) {
err += dx;
y1 += sy;
}
}
}
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color);
}
void HOT Display::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color);
}
void Display::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color);
}
void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color);
}
}
void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius;
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius);
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y - dy, color);
this->draw_pixel_at(center_x - dx, center_y - dy, color);
int hline_width = 2 * (-dx) + 1;
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
int x_start, y_start;
int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
font->print(x_start, y_start, this, color, text);
}
void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) {
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
}
void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
switch (x_align) {
case ImageAlign::RIGHT:
x -= image->get_width();
break;
case ImageAlign::CENTER_HORIZONTAL:
x -= image->get_width() / 2;
break;
case ImageAlign::LEFT:
default:
break;
}
switch (y_align) {
case ImageAlign::BOTTOM:
y -= image->get_height();
break;
case ImageAlign::CENTER_VERTICAL:
y -= image->get_height() / 2;
break;
case ImageAlign::TOP:
default:
break;
}
image->draw(x, y, this, color_on, color_off);
}
#ifdef USE_GRAPH
void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); }
#endif // USE_GRAPH
#ifdef USE_QR_CODE
void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
qr_code->draw(this, x, y, color_on, scale);
}
#endif // USE_QR_CODE
void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
int *width, int *height) {
int x_offset, baseline;
font->measure(text, width, &x_offset, &baseline, height);
auto x_align = TextAlign(int(align) & 0x18);
auto y_align = TextAlign(int(align) & 0x07);
switch (x_align) {
case TextAlign::RIGHT:
*x1 = x - *width;
break;
case TextAlign::CENTER_HORIZONTAL:
*x1 = x - (*width) / 2;
break;
case TextAlign::LEFT:
default:
// LEFT
*x1 = x;
break;
}
switch (y_align) {
case TextAlign::BOTTOM:
*y1 = y - *height;
break;
case TextAlign::BASELINE:
*y1 = y - baseline;
break;
case TextAlign::CENTER_VERTICAL:
*y1 = y - (*height) / 2;
break;
case TextAlign::TOP:
default:
*y1 = y;
break;
}
}
void Display::print(int x, int y, BaseFont *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
}
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text);
}
void Display::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
}
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void Display::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)
page->set_parent(this);
for (uint32_t i = 0; i < pages.size() - 1; i++) {
pages[i]->set_next(pages[i + 1]);
pages[i + 1]->set_prev(pages[i]);
}
pages[0]->set_prev(pages[pages.size() - 1]);
pages[pages.size() - 1]->set_next(pages[0]);
this->show_page(pages[0]);
}
void Display::show_page(DisplayPage *page) {
this->previous_page_ = this->page_;
this->page_ = page;
if (this->previous_page_ != this->page_) {
for (auto *t : on_page_change_triggers_)
t->process(this->previous_page_, this->page_);
}
}
void Display::show_next_page() { this->page_->show_next(); }
void Display::show_prev_page() { this->page_->show_prev(); }
void Display::do_update_() {
if (this->auto_clear_enabled_) {
this->clear();
}
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
(*this->writer_)(*this);
}
// remove all not ended clipping regions
while (is_clipping()) {
end_clipping();
}
}
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
}
void Display::start_clipping(Rect rect) {
if (!this->clipping_rectangle_.empty()) {
Rect r = this->clipping_rectangle_.back();
rect.shrink(r);
}
this->clipping_rectangle_.push_back(rect);
}
void Display::end_clipping() {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "clear: Clipping is not set.");
} else {
this->clipping_rectangle_.pop_back();
}
}
void Display::extend_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().extend(add_rect);
}
}
void Display::shrink_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().shrink(add_rect);
}
}
Rect Display::get_clipping() {
if (this->clipping_rectangle_.empty()) {
return Rect();
} else {
return this->clipping_rectangle_.back();
}
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); }
void DisplayPage::show_next() { this->next_->show(); }
void DisplayPage::show_prev() { this->prev_->show(); }
void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
} // namespace display
} // namespace esphome

View File

@@ -0,0 +1,566 @@
#pragma once
#include <cstdarg>
#include <vector>
#include "rect.h"
#include "esphome/core/color.h"
#include "esphome/core/automation.h"
#include "esphome/core/time.h"
#ifdef USE_GRAPH
#include "esphome/components/graph/graph.h"
#endif
#ifdef USE_QR_CODE
#include "esphome/components/qr_code/qr_code.h"
#endif
namespace esphome {
namespace display {
/** TextAlign is used to tell the display class how to position a piece of text. By default
* the coordinates you enter for the print*() functions take the upper left corner of the text
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the text.
*
* All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the text)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
* - BASELINE (y-coordinate of anchor is on the baseline of the text)
* - BOTTOM (y-coordinate of anchor is on the bottom of the text)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the text bounds)
* - ...
*/
enum class TextAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BASELINE = 0x02,
BOTTOM = 0x04,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x08,
RIGHT = 0x10,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BASELINE_LEFT = BASELINE | LEFT,
BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
BASELINE_RIGHT = BASELINE | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
};
/** ImageAlign is used to tell the display class how to position a image. By default
* the coordinates you enter for the image() functions take the upper left corner of the image
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the image.
*
* All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the image)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
* - BOTTOM (y-coordinate of anchor is on the bottom of the image)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the image bounds)
* - ...
*/
enum class ImageAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BOTTOM = 0x02,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x04,
RIGHT = 0x08,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
};
enum DisplayType {
DISPLAY_TYPE_BINARY = 1,
DISPLAY_TYPE_GRAYSCALE = 2,
DISPLAY_TYPE_COLOR = 3,
};
enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0,
DISPLAY_ROTATION_90_DEGREES = 90,
DISPLAY_ROTATION_180_DEGREES = 180,
DISPLAY_ROTATION_270_DEGREES = 270,
};
class Display;
class DisplayPage;
class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(Display &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type); \
ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \
ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
}
/// Turn the pixel OFF.
extern const Color COLOR_OFF;
/// Turn the pixel ON.
extern const Color COLOR_ON;
class BaseImage {
public:
virtual void draw(int x, int y, Display *display, Color color_on, Color color_off) = 0;
virtual int get_width() const = 0;
virtual int get_height() const = 0;
};
class BaseFont {
public:
virtual void print(int x, int y, Display *display, Color color, const char *text) = 0;
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
};
class Display {
public:
/// Fill the entire screen with the given color.
virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels.
void clear();
/// Get the width of the image in pixels with rotation applied.
virtual int get_width() = 0;
/// Get the height of the image in pixels with rotation applied.
virtual int get_height() = 0;
/// Set a single pixel at the specified coordinates to default color.
inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
/// Set a single pixel at the specified coordinates to the given color.
virtual void draw_pixel_at(int x, int y, Color color) = 0;
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, const char *text);
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
__attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
/** Draw the `image` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param image The image to draw.
* @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits.
*/
void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
/** Draw the `image` at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param image The image to draw.
* @param align The alignment of the image.
* @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits.
*/
void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
#ifdef USE_GRAPH
/** Draw the `graph` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
/** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param name_font The font used for the trace name
* @param value_font The font used for the trace value and units
* @param color_on The color of the border
*/
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
#endif // USE_GRAPH
#ifdef USE_QR_CODE
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param qr_code The qr_code to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
#endif
/** Get the text bounds of the given string.
*
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
* @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
* @param text The text to measure.
* @param font The font to measure the text bounds with.
* @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
* @param x1 A pointer to store the returned x coordinate of the upper left corner in.
* @param y1 A pointer to store the returned y coordinate of the upper left corner in.
* @param width A pointer to store the returned text width in.
* @param height A pointer to store the returned text height in.
*/
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
int *height);
/// Internal method to set the display writer lambda.
void set_writer(display_writer_t &&writer);
void show_page(DisplayPage *page);
void show_next_page();
void show_prev_page();
void set_pages(std::vector<DisplayPage *> pages);
const DisplayPage *get_active_page() const { return this->page_; }
void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
DisplayRotation get_rotation() const { return this->rotation_; }
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
* returns the type the display is currently configured to.
*/
virtual DisplayType get_display_type() = 0;
/** Set the clipping rectangle for further drawing
*
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
*
* return true if success, false if error
*/
void start_clipping(Rect rect);
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
start_clipping(Rect(left, top, right - left, bottom - top));
};
/** Add a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void extend_clipping(Rect rect);
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
this->extend_clipping(Rect(left, top, right - left, bottom - top));
};
/** substract a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void shrink_clipping(Rect rect);
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
};
/** Reset the invalidation region
*/
void end_clipping();
/** Get the current the clipping rectangle
*
* return rect for active clipping region
*/
Rect get_clipping();
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
protected:
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
void do_update_();
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
std::vector<Rect> clipping_rectangle_;
};
class DisplayPage {
public:
DisplayPage(display_writer_t writer);
void show();
void show_next();
void show_prev();
void set_parent(Display *parent);
void set_prev(DisplayPage *prev);
void set_next(DisplayPage *next);
const display_writer_t &get_writer() const;
protected:
Display *parent_;
display_writer_t writer_;
DisplayPage *prev_{nullptr};
DisplayPage *next_{nullptr};
};
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
public:
TEMPLATABLE_VALUE(DisplayPage *, page)
void play(Ts... x) override {
auto *page = this->page_.value(x...);
if (page != nullptr) {
page->show();
}
}
};
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
public:
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_next_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
public:
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_prev_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
public:
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
void set_page(DisplayPage *page) { this->page_ = page; }
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
protected:
Display *parent_;
DisplayPage *page_;
};
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
public:
explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
void process(DisplayPage *from, DisplayPage *to);
void set_from(DisplayPage *p) { this->from_ = p; }
void set_to(DisplayPage *p) { this->to_ = p; }
protected:
DisplayPage *from_{nullptr};
DisplayPage *to_{nullptr};
};
} // namespace display
} // namespace esphome

View File

@@ -1,10 +1,8 @@
#include "display_buffer.h"
#include <utility>
#include "esphome/core/application.h"
#include "esphome/core/color.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -12,9 +10,6 @@ namespace display {
static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 0);
const Color COLOR_ON(255, 255, 255, 255);
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(buffer_length);
@@ -25,8 +20,6 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->clear();
}
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
int DisplayBuffer::get_width() {
switch (this->rotation_) {
case DISPLAY_ROTATION_90_DEGREES:
@@ -38,6 +31,7 @@ int DisplayBuffer::get_width() {
return this->get_width_internal();
}
}
int DisplayBuffer::get_height() {
switch (this->rotation_) {
case DISPLAY_ROTATION_0_DEGREES:
@@ -49,7 +43,7 @@ int DisplayBuffer::get_height() {
return this->get_width_internal();
}
}
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
return; // NOLINT
@@ -73,333 +67,6 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt();
}
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy;
while (true) {
this->draw_pixel_at(x1, y1, color);
if (x1 == x2 && y1 == y2)
break;
int32_t e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x1 += sx;
}
if (e2 <= dx) {
err += dx;
y1 += sy;
}
}
}
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color);
}
void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color);
}
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color);
}
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color);
}
}
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius;
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius);
int dy = 0;
int err = 2 - 2 * radius;
int e2;
do {
this->draw_pixel_at(center_x - dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y + dy, color);
this->draw_pixel_at(center_x + dx, center_y - dy, color);
this->draw_pixel_at(center_x - dx, center_y - dy, color);
int hline_width = 2 * (-dx) + 1;
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
e2 = err;
if (e2 < dy) {
err += ++dy * 2 + 1;
if (-dx == dy && e2 <= dx) {
e2 = 0;
}
}
if (e2 > dx) {
err += ++dx * 2 + 1;
}
} while (dx <= 0);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
int x_start, y_start;
int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
font->print(x_start, y_start, this, color, text);
}
void DisplayBuffer::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format,
va_list arg) {
char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
}
void DisplayBuffer::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
switch (x_align) {
case ImageAlign::RIGHT:
x -= image->get_width();
break;
case ImageAlign::CENTER_HORIZONTAL:
x -= image->get_width() / 2;
break;
case ImageAlign::LEFT:
default:
break;
}
switch (y_align) {
case ImageAlign::BOTTOM:
y -= image->get_height();
break;
case ImageAlign::CENTER_VERTICAL:
y -= image->get_height() / 2;
break;
case ImageAlign::TOP:
default:
break;
}
image->draw(x, y, this, color_on, color_off);
}
#ifdef USE_GRAPH
void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
graph->draw_legend(this, x, y, color_on);
}
#endif // USE_GRAPH
#ifdef USE_QR_CODE
void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
qr_code->draw(this, x, y, color_on, scale);
}
#endif // USE_QR_CODE
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
int *width, int *height) {
int x_offset, baseline;
font->measure(text, width, &x_offset, &baseline, height);
auto x_align = TextAlign(int(align) & 0x18);
auto y_align = TextAlign(int(align) & 0x07);
switch (x_align) {
case TextAlign::RIGHT:
*x1 = x - *width;
break;
case TextAlign::CENTER_HORIZONTAL:
*x1 = x - (*width) / 2;
break;
case TextAlign::LEFT:
default:
// LEFT
*x1 = x;
break;
}
switch (y_align) {
case TextAlign::BOTTOM:
*y1 = y - *height;
break;
case TextAlign::BASELINE:
*y1 = y - baseline;
break;
case TextAlign::CENTER_VERTICAL:
*y1 = y - (*height) / 2;
break;
case TextAlign::TOP:
default:
*y1 = y;
break;
}
}
void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text);
}
void DisplayBuffer::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
va_end(arg);
}
void DisplayBuffer::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void DisplayBuffer::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)
page->set_parent(this);
for (uint32_t i = 0; i < pages.size() - 1; i++) {
pages[i]->set_next(pages[i + 1]);
pages[i + 1]->set_prev(pages[i]);
}
pages[0]->set_prev(pages[pages.size() - 1]);
pages[pages.size() - 1]->set_next(pages[0]);
this->show_page(pages[0]);
}
void DisplayBuffer::show_page(DisplayPage *page) {
this->previous_page_ = this->page_;
this->page_ = page;
if (this->previous_page_ != this->page_) {
for (auto *t : on_page_change_triggers_)
t->process(this->previous_page_, this->page_);
}
}
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
void DisplayBuffer::do_update_() {
if (this->auto_clear_enabled_) {
this->clear();
}
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
(*this->writer_)(*this);
}
// remove all not ended clipping regions
while (is_clipping()) {
end_clipping();
}
}
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format,
ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
}
void DisplayBuffer::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
}
void DisplayBuffer::start_clipping(Rect rect) {
if (!this->clipping_rectangle_.empty()) {
Rect r = this->clipping_rectangle_.back();
rect.shrink(r);
}
this->clipping_rectangle_.push_back(rect);
}
void DisplayBuffer::end_clipping() {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "clear: Clipping is not set.");
} else {
this->clipping_rectangle_.pop_back();
}
}
void DisplayBuffer::extend_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().extend(add_rect);
}
}
void DisplayBuffer::shrink_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
} else {
this->clipping_rectangle_.back().shrink(add_rect);
}
}
Rect DisplayBuffer::get_clipping() {
if (this->clipping_rectangle_.empty()) {
return Rect();
} else {
return this->clipping_rectangle_.back();
}
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); }
void DisplayPage::show_next() { this->next_->show(); }
void DisplayPage::show_prev() { this->prev_->show(); }
void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; }
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
} // namespace display
} // namespace esphome

View File

@@ -2,568 +2,35 @@
#include <cstdarg>
#include <vector>
#include "rect.h"
#include "display.h"
#include "display_color_utils.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/time.h"
#ifdef USE_GRAPH
#include "esphome/components/graph/graph.h"
#endif
#ifdef USE_QR_CODE
#include "esphome/components/qr_code/qr_code.h"
#endif
namespace esphome {
namespace display {
/** TextAlign is used to tell the display class how to position a piece of text. By default
* the coordinates you enter for the print*() functions take the upper left corner of the text
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the text.
*
* All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the text)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
* - BASELINE (y-coordinate of anchor is on the baseline of the text)
* - BOTTOM (y-coordinate of anchor is on the bottom of the text)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the text bounds)
* - ...
*/
enum class TextAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BASELINE = 0x02,
BOTTOM = 0x04,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x08,
RIGHT = 0x10,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BASELINE_LEFT = BASELINE | LEFT,
BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
BASELINE_RIGHT = BASELINE | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
};
/** ImageAlign is used to tell the display class how to position a image. By default
* the coordinates you enter for the image() functions take the upper left corner of the image
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
* refer to the *center* of the image.
*
* All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
* these options are allowed:
*
* - LEFT (x-coordinate of anchor point is on left)
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
* - RIGHT (x-coordinate of anchor point is on right)
*
* For the Y-Axis alignment these options are allowed:
*
* - TOP (y-coordinate of anchor is on the top of the image)
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
* - BOTTOM (y-coordinate of anchor is on the bottom of the image)
*
* These options are then combined to create combined TextAlignment options like:
* - TOP_LEFT (default)
* - CENTER (anchor point is in the middle of the image bounds)
* - ...
*/
enum class ImageAlign {
TOP = 0x00,
CENTER_VERTICAL = 0x01,
BOTTOM = 0x02,
LEFT = 0x00,
CENTER_HORIZONTAL = 0x04,
RIGHT = 0x08,
TOP_LEFT = TOP | LEFT,
TOP_CENTER = TOP | CENTER_HORIZONTAL,
TOP_RIGHT = TOP | RIGHT,
CENTER_LEFT = CENTER_VERTICAL | LEFT,
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
BOTTOM_LEFT = BOTTOM | LEFT,
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
BOTTOM_RIGHT = BOTTOM | RIGHT,
HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
};
enum DisplayType {
DISPLAY_TYPE_BINARY = 1,
DISPLAY_TYPE_GRAYSCALE = 2,
DISPLAY_TYPE_COLOR = 3,
};
enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0,
DISPLAY_ROTATION_90_DEGREES = 90,
DISPLAY_ROTATION_180_DEGREES = 180,
DISPLAY_ROTATION_270_DEGREES = 270,
};
class DisplayBuffer;
class DisplayPage;
class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(DisplayBuffer &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type); \
ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \
ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
}
/// Turn the pixel OFF.
extern const Color COLOR_OFF;
/// Turn the pixel ON.
extern const Color COLOR_ON;
class BaseImage {
class DisplayBuffer : public Display {
public:
virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
virtual int get_width() const = 0;
virtual int get_height() const = 0;
};
class BaseFont {
public:
virtual void print(int x, int y, DisplayBuffer *display, Color color, const char *text) = 0;
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
};
class DisplayBuffer {
public:
/// Fill the entire screen with the given color.
virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels.
void clear();
/// Get the width of the image in pixels with rotation applied.
int get_width();
int get_width() override;
/// Get the height of the image in pixels with rotation applied.
int get_height();
int get_height() override;
/// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param text The text to draw.
*/
void print(int x, int y, BaseFont *font, const char *text);
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
__attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param align The alignment of the text.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param font The font to draw the text with.
* @param format The strftime format to use.
* @param time The time to format.
*/
void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
/** Draw the `image` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param image The image to draw.
* @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits.
*/
void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
/** Draw the `image` at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param image The image to draw.
* @param align The alignment of the image.
* @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits.
*/
void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
#ifdef USE_GRAPH
/** Draw the `graph` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
/** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param graph The graph id for which the legend applies to
* @param name_font The font used for the trace name
* @param value_font The font used for the trace value and units
* @param color_on The color of the border
*/
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
#endif // USE_GRAPH
#ifdef USE_QR_CODE
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param qr_code The qr_code to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
#endif
/** Get the text bounds of the given string.
*
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
* @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
* @param text The text to measure.
* @param font The font to measure the text bounds with.
* @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
* @param x1 A pointer to store the returned x coordinate of the upper left corner in.
* @param y1 A pointer to store the returned y coordinate of the upper left corner in.
* @param width A pointer to store the returned text width in.
* @param height A pointer to store the returned text height in.
*/
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
int *height);
/// Internal method to set the display writer lambda.
void set_writer(display_writer_t &&writer);
void show_page(DisplayPage *page);
void show_next_page();
void show_prev_page();
void set_pages(std::vector<DisplayPage *> pages);
const DisplayPage *get_active_page() const { return this->page_; }
void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
void draw_pixel_at(int x, int y, Color color) override;
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
DisplayRotation get_rotation() const { return this->rotation_; }
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
* returns the type the display is currently configured to.
*/
virtual DisplayType get_display_type() = 0;
/** Set the clipping rectangle for further drawing
*
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
*
* return true if success, false if error
*/
void start_clipping(Rect rect);
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
start_clipping(Rect(left, top, right - left, bottom - top));
};
/** Add a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void extend_clipping(Rect rect);
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
this->extend_clipping(Rect(left, top, right - left, bottom - top));
};
/** substract a rectangular region to the invalidation region
* - This is usually called when an element has been modified
*
* @param[in] rect: Rectangle to add to the invalidation region
*/
void shrink_clipping(Rect rect);
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
};
/** Reset the invalidation region
*/
void end_clipping();
/** Get the current the clipping rectangle
*
* return rect for active clipping region
*/
Rect get_clipping();
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
protected:
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
void init_internal_(uint32_t buffer_length);
void do_update_();
uint8_t *buffer_{nullptr};
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
std::vector<Rect> clipping_rectangle_;
};
class DisplayPage {
public:
DisplayPage(display_writer_t writer);
void show();
void show_next();
void show_prev();
void set_parent(DisplayBuffer *parent);
void set_prev(DisplayPage *prev);
void set_next(DisplayPage *next);
const display_writer_t &get_writer() const;
protected:
DisplayBuffer *parent_;
display_writer_t writer_;
DisplayPage *prev_{nullptr};
DisplayPage *next_{nullptr};
};
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
public:
TEMPLATABLE_VALUE(DisplayPage *, page)
void play(Ts... x) override {
auto *page = this->page_.value(x...);
if (page != nullptr) {
page->show();
}
}
};
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
public:
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_next_page(); }
DisplayBuffer *buffer_;
};
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
public:
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_prev_page(); }
DisplayBuffer *buffer_;
};
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
public:
DisplayIsDisplayingPageCondition(DisplayBuffer *parent) : parent_(parent) {}
void set_page(DisplayPage *page) { this->page_ = page; }
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
protected:
DisplayBuffer *parent_;
DisplayPage *page_;
};
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
public:
explicit DisplayOnPageChangeTrigger(DisplayBuffer *parent) { parent->add_on_page_change_trigger(this); }
void process(DisplayPage *from, DisplayPage *to);
void set_from(DisplayPage *p) { this->from_ = p; }
void set_to(DisplayPage *p) { this->to_ = p; }
protected:
DisplayPage *from_{nullptr};
DisplayPage *to_{nullptr};
};
} // namespace display

View File

@@ -87,7 +87,7 @@ async def to_code(config):
cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID]))
# DSMR Parser
cg.add_library("glmnet/Dsmr", "0.7")
cg.add_library("glmnet/Dsmr", "0.8")
# Crypto
cg.add_library("rweather/Crypto", "0.4.0")

View File

@@ -243,6 +243,30 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_WATER,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(
"active_energy_import_current_average_demand"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"active_energy_import_maximum_demand_running_month"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
"active_energy_import_maximum_demand_last_13_months"
): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

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

View File

@@ -0,0 +1,103 @@
#include "duty_time_sensor.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace duty_time_sensor {
static const char *const TAG = "duty_time_sensor";
void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) {
sensor->add_on_state_callback([this](bool state) { this->process_state_(state); });
}
void DutyTimeSensor::start() {
if (!this->last_state_)
this->process_state_(true);
}
void DutyTimeSensor::stop() {
if (this->last_state_)
this->process_state_(false);
}
void DutyTimeSensor::update() {
if (this->last_state_)
this->process_state_(true);
}
void DutyTimeSensor::loop() {
if (this->func_ == nullptr)
return;
const bool state = this->func_();
if (state != this->last_state_)
this->process_state_(state);
}
void DutyTimeSensor::setup() {
uint32_t seconds = 0;
if (this->restore_) {
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash());
this->pref_.load(&seconds);
}
this->set_value_(seconds);
}
void DutyTimeSensor::set_value_(const uint32_t sec) {
this->last_time_ = 0;
if (this->last_state_)
this->last_time_ = millis(); // last time with 0 ms correction
this->publish_and_save_(sec, 0);
}
void DutyTimeSensor::process_state_(const bool state) {
const uint32_t now = millis();
if (this->last_state_) {
// update or falling edge
const uint32_t tm = now - this->last_time_;
const uint32_t ms = tm % 1000;
this->publish_and_save_(this->total_sec_ + tm / 1000, ms);
this->last_time_ = now - ms; // store time with ms correction
if (!state) {
// falling edge
this->last_time_ = ms; // temporary store ms correction only
this->last_state_ = false;
if (this->last_duty_time_sensor_ != nullptr) {
const uint32_t turn_on_ms = now - this->edge_time_;
this->last_duty_time_sensor_->publish_state(turn_on_ms * 1e-3f);
}
}
} else if (state) {
// rising edge
this->last_time_ = now - this->last_time_; // store time with ms correction
this->edge_time_ = now; // store turn-on start time
this->last_state_ = true;
}
}
void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) {
this->total_sec_ = sec;
this->publish_state(sec + ms * 1e-3f);
if (this->restore_)
this->pref_.save(&sec);
}
void DutyTimeSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Duty Time:");
ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval());
ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_));
LOG_SENSOR(" ", "Duty Time Sensor:", this);
LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_);
}
} // namespace duty_time_sensor
} // namespace esphome

View File

@@ -0,0 +1,88 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace duty_time_sensor {
class DutyTimeSensor : public sensor::Sensor, public PollingComponent {
public:
void setup() override;
void update() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void start();
void stop();
bool is_running() const { return this->last_state_; }
void reset() { this->set_value_(0); }
void set_lambda(std::function<bool()> &&func) { this->func_ = func; }
void set_sensor(binary_sensor::BinarySensor *sensor);
void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; }
void set_restore(bool restore) { this->restore_ = restore; }
protected:
void set_value_(uint32_t sec);
void process_state_(bool state);
void publish_and_save_(uint32_t sec, uint32_t ms);
std::function<bool()> func_{nullptr};
sensor::Sensor *last_duty_time_sensor_{nullptr};
ESPPreferenceObject pref_;
uint32_t total_sec_;
uint32_t last_time_;
uint32_t edge_time_;
bool last_state_{false};
bool restore_;
};
template<typename... Ts> class StartAction : public Action<Ts...> {
public:
explicit StartAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->start(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class StopAction : public Action<Ts...> {
public:
explicit StopAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->stop(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class ResetAction : public Action<Ts...> {
public:
explicit ResetAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->reset(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class RunningCondition : public Condition<Ts...> {
public:
explicit RunningCondition(DutyTimeSensor *parent, bool state) : parent_(parent), state_(state) {}
bool check(Ts... x) override { return this->parent_->is_running() == this->state_; }
protected:
DutyTimeSensor *parent_;
bool state_;
};
} // namespace duty_time_sensor
} // namespace esphome

View File

@@ -0,0 +1,121 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.automation import (
Action,
Condition,
maybe_simple_id,
register_action,
register_condition,
)
from esphome.components import binary_sensor, sensor
from esphome.const import (
CONF_ID,
CONF_SENSOR,
CONF_RESTORE,
CONF_LAMBDA,
UNIT_SECOND,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
DEVICE_CLASS_DURATION,
ENTITY_CATEGORY_DIAGNOSTIC,
)
CONF_LAST_TIME = "last_time"
duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor")
DutyTimeSensor = duty_time_sensor_ns.class_(
"DutyTimeSensor", sensor.Sensor, cg.PollingComponent
)
StartAction = duty_time_sensor_ns.class_("StartAction", Action)
StopAction = duty_time_sensor_ns.class_("StopAction", Action)
ResetAction = duty_time_sensor_ns.class_("ResetAction", Action)
SetAction = duty_time_sensor_ns.class_("SetAction", Action)
RunningCondition = duty_time_sensor_ns.class_("RunningCondition", Condition)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
DutyTimeSensor,
unit_of_measurement=UNIT_SECOND,
icon="mdi:timer-play-outline",
accuracy_decimals=3,
state_class=STATE_CLASS_TOTAL_INCREASING,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
cv.Optional(CONF_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_LAMBDA): cv.lambda_,
cv.Optional(CONF_RESTORE, default=False): cv.boolean,
cv.Optional(CONF_LAST_TIME): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
icon="mdi:timer-marker-outline",
accuracy_decimals=3,
state_class=STATE_CLASS_TOTAL,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
.extend(cv.polling_component_schema("60s")),
cv.has_at_most_one_key(CONF_SENSOR, CONF_LAMBDA),
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_restore(config[CONF_RESTORE]))
if CONF_SENSOR in config:
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.bool_)
cg.add(var.set_lambda(lambda_))
if CONF_LAST_TIME in config:
sens = await sensor.new_sensor(config[CONF_LAST_TIME])
cg.add(var.set_last_duty_time_sensor(sens))
# AUTOMATIONS
DUTY_TIME_ID_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(DutyTimeSensor),
}
)
@register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_start_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)
@register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_stop_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)
@register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_reset_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)
@register_condition(
"sensor.duty_time.is_running", RunningCondition, DUTY_TIME_ID_SCHEMA
)
async def duty_time_is_running_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@register_condition(
"sensor.duty_time.is_not_running", RunningCondition, DUTY_TIME_ID_SCHEMA
)
async def duty_time_is_not_running_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)

View File

@@ -547,6 +547,8 @@ def copy_files():
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git", ".github"),
symlinks=True,
ignore_dangling_symlinks=True,
)
dir = os.path.dirname(__file__)

View File

@@ -55,3 +55,4 @@ async def to_code(config):
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)

View File

@@ -263,6 +263,7 @@ async def to_code(config):
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT")

View File

@@ -107,16 +107,16 @@ void ESP32BLETracker::loop() {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
}
bool bulk_parsed = false;
for (auto *listener : this->listeners_) {
bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
for (auto *client : this->clients_) {
bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) {
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
for (auto *client : this->clients_) {
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
}
if (!bulk_parsed) {
if (this->parse_advertisements_) {
for (size_t i = 0; i < index; i++) {
ESPBTDevice device;
device.parse_scan_rst(this->scan_result_buffer_[i]);
@@ -284,6 +284,32 @@ void ESP32BLETracker::end_of_scan_() {
void ESP32BLETracker::register_client(ESPBTClient *client) {
client->app_id = ++this->app_id_;
this->clients_.push_back(client);
this->recalculate_advertisement_parser_types();
}
void ESP32BLETracker::register_listener(ESPBTDeviceListener *listener) {
listener->set_parent(this);
this->listeners_.push_back(listener);
this->recalculate_advertisement_parser_types();
}
void ESP32BLETracker::recalculate_advertisement_parser_types() {
this->raw_advertisements_ = false;
this->parse_advertisements_ = false;
for (auto *listener : this->listeners_) {
if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
this->parse_advertisements_ = true;
} else {
this->raw_advertisements_ = true;
}
}
for (auto *client : this->clients_) {
if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
this->parse_advertisements_ = true;
} else {
this->raw_advertisements_ = true;
}
}
}
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {

View File

@@ -27,6 +27,11 @@ using namespace esp32_ble;
using adv_data_t = std::vector<uint8_t>;
enum AdvertisementParserType {
PARSED_ADVERTISEMENTS,
RAW_ADVERTISEMENTS,
};
struct ServiceData {
ESPBTUUID uuid;
adv_data_t data;
@@ -116,6 +121,9 @@ class ESPBTDeviceListener {
virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
return false;
};
virtual AdvertisementParserType get_advertisement_parser_type() {
return AdvertisementParserType::PARSED_ADVERTISEMENTS;
};
void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
protected:
@@ -184,12 +192,9 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
void loop() override;
void register_listener(ESPBTDeviceListener *listener) {
listener->set_parent(this);
this->listeners_.push_back(listener);
}
void register_listener(ESPBTDeviceListener *listener);
void register_client(ESPBTClient *client);
void recalculate_advertisement_parser_types();
void print_bt_device_info(const ESPBTDevice &device);
@@ -231,6 +236,8 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
bool scan_continuous_;
bool scan_active_;
bool scanner_idle_;
bool raw_advertisements_{false};
bool parse_advertisements_{false};
SemaphoreHandle_t scan_result_lock_;
SemaphoreHandle_t scan_end_lock_;
size_t scan_result_index_{0};

View File

@@ -35,6 +35,7 @@ ETHERNET_TYPES = {
"IP101": EthernetType.ETHERNET_TYPE_IP101,
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
}
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")

View File

@@ -19,7 +19,11 @@
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_eth.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include "esp_eth_phy_802_3.h"
#else
#include "eth_phy_regs_struct.h"
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
@@ -170,7 +174,11 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
return ESP_OK;
}
#if ESP_IDF_VERSION_MAJOR >= 5
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) {
#else
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
#endif
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
esp_eth_mediator_t *eth = jl1101->eth;
/* in case any link status has changed, let's assume we're in link down status */
@@ -285,7 +293,11 @@ static esp_err_t jl1101_init(esp_eth_phy_t *phy) {
esp_eth_mediator_t *eth = jl1101->eth;
// Detect PHY address
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
#if ESP_IDF_VERSION_MAJOR >= 5
PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
#else
PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
#endif
}
/* Power on Ethernet PHY */
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
@@ -324,7 +336,11 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) {
jl1101->parent.init = jl1101_init;
jl1101->parent.deinit = jl1101_deinit;
jl1101->parent.set_mediator = jl1101_set_mediator;
#if ESP_IDF_VERSION_MAJOR >= 5
jl1101->parent.autonego_ctrl = jl1101_negotiate;
#else
jl1101->parent.negotiate = jl1101_negotiate;
#endif
jl1101->parent.get_link = jl1101_get_link;
jl1101->parent.pwrctl = jl1101_pwrctl;
jl1101->parent.get_addr = jl1101_get_addr;

View File

@@ -41,18 +41,27 @@ void EthernetComponent::setup() {
this->eth_netif_ = esp_netif_new(&cfg);
// Init MAC and PHY configs to default
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = this->phy_addr_;
phy_config.reset_gpio_num = this->power_pin_;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
#if ESP_IDF_VERSION_MAJOR >= 5
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
esp32_emac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#else
mac_config.smi_mdc_gpio_num = this->mdc_pin_;
mac_config.smi_mdio_gpio_num = this->mdio_pin_;
mac_config.clock_config.rmii.clock_mode = this->clk_mode_;
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
#endif
switch (this->type_) {
case ETHERNET_TYPE_LAN8720: {
@@ -75,8 +84,13 @@ void EthernetComponent::setup() {
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break;
}
case ETHERNET_TYPE_KSZ8081: {
case ETHERNET_TYPE_KSZ8081:
case ETHERNET_TYPE_KSZ8081RNA: {
#if ESP_IDF_VERSION_MAJOR >= 5
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
#else
this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
#endif
break;
}
default: {
@@ -89,6 +103,12 @@ void EthernetComponent::setup() {
this->eth_handle_ = nullptr;
err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error");
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
this->ksz8081_set_clock_reference_(mac);
}
/* attach Ethernet driver to TCP/IP stack */
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
ESPHL_ERROR_CHECK(err, "ETH netif attach error");
@@ -171,6 +191,10 @@ void EthernetComponent::dump_config() {
eth_type = "KSZ8081";
break;
case ETHERNET_TYPE_KSZ8081RNA:
eth_type = "KSZ8081RNA";
break;
default:
eth_type = "Unknown";
break;
@@ -221,13 +245,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
return;
}
ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event);
}
void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) {
global_eth_component->connected_ = true;
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%d)", event_id);
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id);
}
void EthernetComponent::start_connect_() {
@@ -372,6 +396,37 @@ bool EthernetComponent::powerdown() {
return true;
}
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
#define KSZ80XX_PC2R_REG_ADDR (0x1F)
esp_err_t err;
uint32_t phy_control_2;
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
* KSZ8081RNA:
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII 25 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* KSZ8081RND:
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII 25 MHz clock mode.
*/
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
phy_control_2 |= 1 << 7;
err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2);
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
}
#undef KSZ80XX_PC2R_REG_ADDR
}
} // namespace ethernet
} // namespace esphome

View File

@@ -21,6 +21,7 @@ enum EthernetType {
ETHERNET_TYPE_IP101,
ETHERNET_TYPE_JL1101,
ETHERNET_TYPE_KSZ8081,
ETHERNET_TYPE_KSZ8081RNA,
};
struct ManualIP {
@@ -67,6 +68,8 @@ class EthernetComponent : public Component {
void start_connect_();
void dump_connect_params_();
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
std::string use_address_;
uint8_t phy_addr_{0};

View File

@@ -3,6 +3,7 @@ from pathlib import Path
import hashlib
import os
import re
from packaging import version
import requests
@@ -66,13 +67,18 @@ def validate_pillow_installed(value):
except ImportError as err:
raise cv.Invalid(
"Please install the pillow python package to use this feature. "
"(pip install pillow)"
'(pip install pillow">4.0.0,<10.0.0")'
) from err
if PIL.__version__[0] < "4":
if version.parse(PIL.__version__) < version.parse("4.0.0"):
raise cv.Invalid(
"Please update your pillow installation to at least 4.0.x. "
"(pip install -U pillow)"
'(pip install pillow">4.0.0,<10.0.0")'
)
if version.parse(PIL.__version__) >= version.parse("10.0.0"):
raise cv.Invalid(
"Please downgrade your pillow installation to below 10.0.0. "
'(pip install pillow">4.0.0,<10.0.0")'
)
return value

View File

@@ -10,7 +10,7 @@ namespace font {
static const char *const TAG = "font";
void Glyph::draw(int x_at, int y_start, display::DisplayBuffer *display, Color color) const {
void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const {
int scan_x1, scan_y1, scan_width, scan_height;
this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
@@ -118,7 +118,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
*x_offset = min_x;
*width = x - min_x;
}
void Font::print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) {
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text) {
int i = 0;
int x_at = x_start;
while (text[i] != '\0') {

View File

@@ -22,7 +22,7 @@ class Glyph {
public:
Glyph(const GlyphData *data) : glyph_data_(data) {}
void draw(int x, int y, display::DisplayBuffer *display, Color color) const;
void draw(int x, int y, display::Display *display, Color color) const;
const char *get_char() const;
@@ -50,7 +50,7 @@ class Font : public display::BaseFont {
int match_next_glyph(const char *str, int *match_length);
void print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) override;
void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override;
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; }

View File

@@ -1,5 +1,5 @@
#include "graph.h"
#include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/display.h"
#include "esphome/core/color.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -56,7 +56,7 @@ void GraphTrace::init(Graph *g) {
this->data_.set_update_time_ms(g->get_duration() * 1000 / g->get_width());
}
void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
/// Plot border
if (this->border_) {
buff->horizontal_line(x_offset, y_offset, this->width_, color);
@@ -303,7 +303,7 @@ void GraphLegend::init(Graph *g) {
}
}
void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) {
if (!legend_)
return;

View File

@@ -8,9 +8,9 @@
namespace esphome {
// forward declare DisplayBuffer
// forward declare Display
namespace display {
class DisplayBuffer;
class Display;
class BaseFont;
} // namespace display
@@ -133,8 +133,8 @@ class GraphTrace {
class Graph : public Component {
public:
void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color);
void setup() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; }

View File

@@ -0,0 +1,152 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_CHANNEL,
CONF_SPEED,
CONF_DIRECTION,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@max246"]
grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng")
GROVE_TB6612FNG = grove_tb6612fng_ns.class_(
"GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice
)
GROVETB6612FNGMotorRunAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorRunAction", automation.Action
)
GROVETB6612FNGMotorBrakeAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorBrakeAction", automation.Action
)
GROVETB6612FNGMotorStopAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorStopAction", automation.Action
)
GROVETB6612FNGMotorStandbyAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorStandbyAction", automation.Action
)
GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorNoStandbyAction", automation.Action
)
DIRECTION_TYPE = {
"FORWARD": 1,
"BACKWARD": 2,
}
CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(GROVE_TB6612FNG),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x14))
)
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)
@automation.register_action(
"grove_tb6612fng.run",
GROVETB6612FNGMotorRunAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
cv.Required(CONF_SPEED): cv.templatable(cv.int_range(min=0, max=255)),
cv.Required(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True),
}
),
)
async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16)
template_speed = (
template_speed if config[CONF_DIRECTION] == "FORWARD" else -template_speed
)
cg.add(var.set_channel(template_channel))
cg.add(var.set_speed(template_speed))
return var
@automation.register_action(
"grove_tb6612fng.break",
GROVETB6612FNGMotorBrakeAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
}
),
)
async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
cg.add(var.set_channel(template_channel))
return var
@automation.register_action(
"grove_tb6612fng.stop",
GROVETB6612FNGMotorStopAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
}
),
)
async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
cg.add(var.set_channel(template_channel))
return var
@automation.register_action(
"grove_tb6612fng.standby",
GROVETB6612FNGMotorStandbyAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
}
),
)
async def grove_tb6612fng_standby_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"grove_tb6612fng.no_standby",
GROVETB6612FNGMotorNoStandbyAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
}
),
)
async def grove_tb6612fng_no_standby_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View File

@@ -0,0 +1,171 @@
#include "grove_tb6612fng.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace grove_tb6612fng {
static const char *const TAG = "GroveMotorDriveTB6612FNG";
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE = 0x00;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STOP = 0x01;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CW = 0x02;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CCW = 0x03;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY = 0x04;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY = 0x05;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN = 0x06;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP = 0x07;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN = 0x08;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR = 0x11;
void GroveMotorDriveTB6612FNG::dump_config() {
ESP_LOGCONFIG(TAG, "GroveMotorDriveTB6612FNG:");
LOG_I2C_DEVICE(this);
}
void GroveMotorDriveTB6612FNG::setup() {
ESP_LOGCONFIG(TAG, "Setting up Grove Motor Drive TB6612FNG ...");
if (!this->standby()) {
this->mark_failed();
return;
}
}
bool GroveMotorDriveTB6612FNG::standby() {
uint8_t status = 0;
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY, &status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set standby failed!");
this->status_set_warning();
return false;
}
return true;
}
bool GroveMotorDriveTB6612FNG::not_standby() {
uint8_t status = 0;
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY, &status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set not standby failed!");
this->status_set_warning();
return false;
}
return true;
}
void GroveMotorDriveTB6612FNG::set_i2c_addr(uint8_t addr) {
if (addr == 0x00 || addr >= 0x80) {
return;
}
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR, &addr, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set new i2c address failed!");
this->status_set_warning();
return;
}
this->set_i2c_address(addr);
}
void GroveMotorDriveTB6612FNG::dc_motor_run(uint8_t channel, int16_t speed) {
speed = clamp<int16_t>(speed, -255, 255);
buffer_[0] = channel;
if (speed >= 0) {
buffer_[1] = speed;
} else {
buffer_[1] = (uint8_t) (-speed);
}
if (speed >= 0) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CW, buffer_, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run motor failed!");
this->status_set_warning();
return;
}
} else {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CCW, buffer_, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run motor failed!");
this->status_set_warning();
return;
}
}
}
void GroveMotorDriveTB6612FNG::dc_motor_brake(uint8_t channel) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE, &channel, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Break motor failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::dc_motor_stop(uint8_t channel) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STOP, &channel, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Stop dc motor failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm) {
uint8_t cw = 0;
// 0.1ms_per_step
uint16_t ms_per_step = 0;
if (steps > 0) {
cw = 1;
}
// stop
else if (steps == 0) {
this->stepper_stop();
return;
} else if (steps == INT16_MIN) {
steps = INT16_MAX;
} else {
steps = -steps;
}
rpm = clamp<uint16_t>(rpm, 1, 300);
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
buffer_[0] = mode;
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
buffer_[2] = steps;
buffer_[3] = (steps >> 8);
buffer_[4] = ms_per_step;
buffer_[5] = (ms_per_step >> 8);
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN, buffer_, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run stepper failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_stop() {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP, nullptr, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Send stop stepper failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw) {
// 4=>infinite ccw 5=>infinite cw
uint8_t cw = (is_cw) ? 5 : 4;
// 0.1ms_per_step
uint16_t ms_per_step = 0;
rpm = clamp<uint16_t>(rpm, 1, 300);
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
buffer_[0] = mode;
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
buffer_[2] = ms_per_step;
buffer_[3] = (ms_per_step >> 8);
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN, buffer_, 4) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Write stepper keep run failed");
this->status_set_warning();
return;
}
}
} // namespace grove_tb6612fng
} // namespace esphome

View File

@@ -0,0 +1,208 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/automation.h"
//#include "esphome/core/helpers.h"
/*
Grove_Motor_Driver_TB6612FNG.h
A library for the Grove - Motor Driver(TB6612FNG)
Copyright (c) 2018 seeed technology co., ltd.
Website : www.seeed.cc
Author : Jerry Yip
Create Time: 2018-06
Version : 0.1
Change Log :
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
namespace esphome {
namespace grove_tb6612fng {
enum MotorChannelTypeT {
MOTOR_CHA = 0,
MOTOR_CHB = 1,
};
enum StepperModeTypeT {
FULL_STEP = 0,
WAVE_DRIVE = 1,
HALF_STEP = 2,
MICRO_STEPPING = 3,
};
class GroveMotorDriveTB6612FNG : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
/*************************************************************
Description
Enter standby mode. Normally you don't need to call this, except that
you have called notStandby() before.
Parameter
Null.
Return
True/False.
*************************************************************/
bool standby();
/*************************************************************
Description
Exit standby mode. Motor driver does't do any action at this mode.
Parameter
Null.
Return
True/False.
*************************************************************/
bool not_standby();
/*************************************************************
Description
Set an new I2C address.
Parameter
addr: 0x01~0x7f
Return
Null.
*************************************************************/
void set_i2c_addr(uint8_t addr);
/*************************************************************
Description
Drive a motor.
Parameter
chl: MOTOR_CHA or MOTOR_CHB
speed: -255~255, if speed > 0, motor moves clockwise.
Note that there is always a starting speed(a starting voltage) for motor.
If the input voltage is 5V, the starting speed should larger than 100 or
smaller than -100.
Return
Null.
*************************************************************/
void dc_motor_run(uint8_t channel, int16_t speed);
/*************************************************************
Description
Brake, stop the motor immediately
Parameter
chl: MOTOR_CHA or MOTOR_CHB
Return
Null.
*************************************************************/
void dc_motor_brake(uint8_t channel);
/*************************************************************
Description
Stop the motor slowly.
Parameter
chl: MOTOR_CHA or MOTOR_CHB
Return
Null.
*************************************************************/
void dc_motor_stop(uint8_t channel);
/*************************************************************
Description
Drive a stepper.
Parameter
mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING,
for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png
steps: The number of steps to run, range from -32768 to 32767.
When steps = 0, the stepper stops.
When steps > 0, the stepper runs clockwise. When steps < 0, the stepper runs anticlockwise.
rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300.
Note that high rpm will lead to step lose, so rpm should not be larger than 150.
Return
Null.
*************************************************************/
void stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm);
/*************************************************************
Description
Stop a stepper.
Parameter
Null.
Return
Null.
*************************************************************/
void stepper_stop();
// keeps moving(direction same as the last move, default to clockwise)
/*************************************************************
Description
Keep a stepper running.
Parameter
mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING,
for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png
rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300.
Note that high rpm will lead to step lose, so rpm should not be larger than 150.
is_cw: Set the running direction, true for clockwise and false for anti-clockwise.
Return
Null.
*************************************************************/
void stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw);
private:
uint8_t buffer_[16];
};
template<typename... Ts>
class GROVETB6612FNGMotorRunAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
TEMPLATABLE_VALUE(uint16_t, speed)
void play(Ts... x) override {
auto channel = this->channel_.value(x...);
auto speed = this->speed_.value(x...);
this->parent_->dc_motor_run(channel, speed);
}
};
template<typename... Ts>
class GROVETB6612FNGMotorBrakeAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
void play(Ts... x) override { this->parent_->dc_motor_brake(this->channel_.value(x...)); }
};
template<typename... Ts>
class GROVETB6612FNGMotorStopAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
void play(Ts... x) override { this->parent_->dc_motor_stop(this->channel_.value(x...)); }
};
template<typename... Ts>
class GROVETB6612FNGMotorStandbyAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
void play(Ts... x) override { this->parent_->standby(); }
};
template<typename... Ts>
class GROVETB6612FNGMotorNoStandbyAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
void play(Ts... x) override { this->parent_->not_standby(); }
};
} // namespace grove_tb6612fng
} // namespace esphome

View File

@@ -12,7 +12,7 @@ void HomeassistantSensor::setup() {
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_number<float>(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str());
this->publish_state(NAN);
return;
}

View File

@@ -14,6 +14,7 @@ namespace i2c {
static const char *const TAG = "i2c.idf";
void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
static i2c_port_t next_port = 0;
port_ = next_port++;

View File

@@ -13,6 +13,7 @@ from esphome.const import (
CONF_PAGES,
CONF_RESET_PIN,
CONF_DIMENSIONS,
CONF_DATA_RATE,
)
DEPENDENCIES = ["spi"]
@@ -43,6 +44,7 @@ MODELS = {
"ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI),
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI),
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI),
"ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI),
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI),
"S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI),
"S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI),
@@ -97,6 +99,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
cv.file_
),
cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA,
}
)
.extend(cv.polling_component_schema("1s"))
@@ -118,7 +121,7 @@ async def to_code(config):
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
@@ -175,3 +178,6 @@ async def to_code(config):
if rhs is not None:
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr))
spi_data_rate = str(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]])
cg.add_define("ILI9XXXDisplay_DATA_RATE", cg.RawExpression(spi_data_rate))

View File

@@ -152,12 +152,10 @@ void ILI9XXXDisplay::update() {
this->need_update_ = true;
return;
}
this->prossing_update_ = true;
do {
this->prossing_update_ = true;
this->need_update_ = false;
if (!this->need_update_) {
this->do_update_();
}
this->do_update_();
} while (this->need_update_);
this->prossing_update_ = false;
this->display_();
@@ -411,6 +409,17 @@ void ILI9XXXILI9488::initialize() {
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXILI9488A::initialize() {
this->init_lcd_(INITCMD_ILI9488_A);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXST7796::initialize() {
this->init_lcd_(INITCMD_ST7796);
if (this->width_ == 0) {

View File

@@ -15,10 +15,14 @@ enum ILI9XXXColorMode {
BITS_16 = 0x10,
};
#ifndef ILI9XXXDisplay_DATA_RATE
#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ
#endif // ILI9XXXDisplay_DATA_RATE
class ILI9XXXDisplay : public PollingComponent,
public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override;
@@ -128,6 +132,12 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay {
void initialize() override;
};
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
class ILI9XXXILI9488A : public ILI9XXXDisplay {
protected:
void initialize() override;
};
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXST7796 : public ILI9XXXDisplay {
protected:

View File

@@ -139,6 +139,40 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
//ILI9XXX_INVON , 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2
ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL
ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN
ILI9XXX_IFMODE, 1, 0x00,
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
ILI9XXX_MADCTL, 1, 0x28,
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
@@ -218,12 +252,12 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
0x0E, 0x09, 0x00,
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
0x31, 0x36, 0x0F,
ILI9XXX_GMCTRP1 , 14, 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, // Set Gamma
0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14,
0x18, 0x1B,
ILI9XXX_GMCTRN1 , 14, 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, // Set Gamma
0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14,
0x17, 0x1B,
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
ILI9XXX_DISPON , 0x80, // Display on
0x00 // End of list

View File

@@ -5,7 +5,7 @@
namespace esphome {
namespace image {
void Image::draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) {
void Image::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
switch (type_) {
case IMAGE_TYPE_BINARY: {
for (int img_x = 0; img_x < width_; img_x++) {

View File

@@ -39,7 +39,7 @@ class Image : public display::BaseImage {
int get_height() const override;
ImageType get_type() const;
void draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) override;
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; }

View File

@@ -115,7 +115,7 @@ async def to_code(config):
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@@ -112,7 +112,6 @@ CONFIG_SCHEMA = cv.All(
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2410",
baud_rate=256000,
require_tx=True,
require_rx=True,
parity="NONE",

View File

@@ -16,6 +16,7 @@ McpMode = mcp2515_ns.enum("CANCTRL_REQOP_MODE")
CAN_CLOCK = {
"8MHZ": CanClock.MCP_8MHZ,
"12MHZ": CanClock.MCP_12MHZ,
"16MHZ": CanClock.MCP_16MHZ,
"20MHZ": CanClock.MCP_20MHZ,
}

View File

@@ -16,11 +16,14 @@ const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_R
bool MCP2515::setup_internal() {
this->spi_setup();
if (this->reset_() == canbus::ERROR_FAIL)
if (this->reset_() != canbus::ERROR_OK)
return false;
this->set_bitrate_(this->bit_rate_, this->mcp_clock_);
this->set_mode_(this->mcp_mode_);
ESP_LOGV(TAG, "setup done");
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
return false;
if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK)
return false;
uint8_t err_flags = this->get_error_flags_();
ESP_LOGD(TAG, "mcp2515 setup done, error_flags = %02X", err_flags);
return true;
}
@@ -38,7 +41,7 @@ canbus::Error MCP2515::reset_() {
set_registers_(MCP_TXB0CTRL, zeros, 14);
set_registers_(MCP_TXB1CTRL, zeros, 14);
set_registers_(MCP_TXB2CTRL, zeros, 14);
ESP_LOGD(TAG, "reset() CLEARED TXB registers");
ESP_LOGV(TAG, "reset() CLEARED TXB registers");
set_register_(MCP_RXB0CTRL, 0);
set_register_(MCP_RXB1CTRL, 0);
@@ -114,16 +117,12 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
uint32_t end_time = millis() + 10;
bool mode_match = false;
while (millis() < end_time) {
uint8_t new_mode = read_register_(MCP_CANSTAT);
new_mode &= CANSTAT_OPMOD;
mode_match = new_mode == mode;
if (mode_match) {
break;
}
if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode)
return canbus::ERROR_OK;
}
return mode_match ? canbus::ERROR_OK : canbus::ERROR_FAIL;
ESP_LOGE(TAG, "Failed to set mode");
return canbus::ERROR_FAIL;
}
canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) {
@@ -451,6 +450,78 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
}
break;
case (MCP_12MHZ):
switch (can_speed) {
case (canbus::CAN_5KBPS): // 5Kbps
cfg1 = MCP_12MHZ_5KBPS_CFG1;
cfg2 = MCP_12MHZ_5KBPS_CFG2;
cfg3 = MCP_12MHZ_5KBPS_CFG3;
break;
case (canbus::CAN_10KBPS): // 10Kbps
cfg1 = MCP_12MHZ_10KBPS_CFG1;
cfg2 = MCP_12MHZ_10KBPS_CFG2;
cfg3 = MCP_12MHZ_10KBPS_CFG3;
break;
case (canbus::CAN_20KBPS): // 20Kbps
cfg1 = MCP_12MHZ_20KBPS_CFG1;
cfg2 = MCP_12MHZ_20KBPS_CFG2;
cfg3 = MCP_12MHZ_20KBPS_CFG3;
break;
case (canbus::CAN_33KBPS): // 33.333Kbps
cfg1 = MCP_12MHZ_33K3BPS_CFG1;
cfg2 = MCP_12MHZ_33K3BPS_CFG2;
cfg3 = MCP_12MHZ_33K3BPS_CFG3;
break;
case (canbus::CAN_40KBPS): // 40Kbps
cfg1 = MCP_12MHZ_40KBPS_CFG1;
cfg2 = MCP_12MHZ_40KBPS_CFG2;
cfg3 = MCP_12MHZ_40KBPS_CFG3;
break;
case (canbus::CAN_50KBPS): // 50Kbps
cfg2 = MCP_12MHZ_50KBPS_CFG2;
cfg3 = MCP_12MHZ_50KBPS_CFG3;
break;
case (canbus::CAN_80KBPS): // 80Kbps
cfg1 = MCP_12MHZ_80KBPS_CFG1;
cfg2 = MCP_12MHZ_80KBPS_CFG2;
cfg3 = MCP_12MHZ_80KBPS_CFG3;
break;
case (canbus::CAN_100KBPS): // 100Kbps
cfg1 = MCP_12MHZ_100KBPS_CFG1;
cfg2 = MCP_12MHZ_100KBPS_CFG2;
cfg3 = MCP_12MHZ_100KBPS_CFG3;
break;
case (canbus::CAN_125KBPS): // 125Kbps
cfg1 = MCP_12MHZ_125KBPS_CFG1;
cfg2 = MCP_12MHZ_125KBPS_CFG2;
cfg3 = MCP_12MHZ_125KBPS_CFG3;
break;
case (canbus::CAN_200KBPS): // 200Kbps
cfg1 = MCP_12MHZ_200KBPS_CFG1;
cfg2 = MCP_12MHZ_200KBPS_CFG2;
cfg3 = MCP_12MHZ_200KBPS_CFG3;
break;
case (canbus::CAN_250KBPS): // 250Kbps
cfg1 = MCP_12MHZ_250KBPS_CFG1;
cfg2 = MCP_12MHZ_250KBPS_CFG2;
cfg3 = MCP_12MHZ_250KBPS_CFG3;
break;
case (canbus::CAN_500KBPS): // 500Kbps
cfg1 = MCP_12MHZ_500KBPS_CFG1;
cfg2 = MCP_12MHZ_500KBPS_CFG2;
cfg3 = MCP_12MHZ_500KBPS_CFG3;
break;
case (canbus::CAN_1000KBPS): // 1Mbps
cfg1 = MCP_12MHZ_1000KBPS_CFG1;
cfg2 = MCP_12MHZ_1000KBPS_CFG2;
cfg3 = MCP_12MHZ_1000KBPS_CFG3;
break;
default:
set = 0;
break;
}
break;
case (MCP_16MHZ):
switch (can_speed) {
case (canbus::CAN_5KBPS): // 5Kbps
@@ -602,6 +673,7 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
set_register_(MCP_CNF3, cfg3); // NOLINT
return canbus::ERROR_OK;
} else {
ESP_LOGE(TAG, "Invalid frequency/bitrate combination: %d/%d", can_clock, can_speed);
return canbus::ERROR_FAIL;
}
}

View File

@@ -11,7 +11,7 @@ static const uint32_t SPI_CLOCK = 10000000; // 10MHz
static const int N_TXBUFFERS = 3;
static const int N_RXBUFFERS = 2;
enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_8MHZ };
enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_12MHZ, MCP_8MHZ };
enum MASK { MASK0, MASK1 };
enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 };
enum RXBn { RXB0 = 0, RXB1 = 1 };

View File

@@ -207,6 +207,62 @@ static const uint8_t MCP_8MHZ_5KBPS_CFG1 = 0x1F;
static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF;
static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87;
/*
* Speed 12M
*/
static const uint8_t MCP_12MHZ_1000KBPS_CFG1 = 0x00;
static const uint8_t MCP_12MHZ_1000KBPS_CFG2 = 0x88;
static const uint8_t MCP_12MHZ_1000KBPS_CFG3 = 0x81;
static const uint8_t MCP_12MHZ_500KBPS_CFG1 = 0x00;
static const uint8_t MCP_12MHZ_500KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_500KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_250KBPS_CFG1 = 0x01;
static const uint8_t MCP_12MHZ_250KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_250KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_200KBPS_CFG1 = 0x01;
static const uint8_t MCP_12MHZ_200KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_200KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_125KBPS_CFG1 = 0x03;
static const uint8_t MCP_12MHZ_125KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_125KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_100KBPS_CFG1 = 0x03;
static const uint8_t MCP_12MHZ_100KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_100KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_80KBPS_CFG1 = 0x04;
static const uint8_t MCP_12MHZ_80KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_80KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_50KBPS_CFG1 = 0x07;
static const uint8_t MCP_12MHZ_50KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_50KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_40KBPS_CFG1 = 0x09;
static const uint8_t MCP_12MHZ_40KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_40KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_33K3BPS_CFG1 = 0x08;
static const uint8_t MCP_12MHZ_33K3BPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_33K3BPS_CFG3 = 0x84;
static const uint8_t MCP_12MHZ_20KBPS_CFG1 = 0x0E;
static const uint8_t MCP_12MHZ_20KBPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_20KBPS_CFG3 = 0x84;
static const uint8_t MCP_12MHZ_10KBPS_CFG1 = 0x31;
static const uint8_t MCP_12MHZ_10KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_10KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_5KBPS_CFG1 = 0x3B;
static const uint8_t MCP_12MHZ_5KBPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_5KBPS_CFG3 = 0x84;
/*
* speed 16M
*/

View File

@@ -57,6 +57,10 @@ void MDNSComponent::compile_records_() {
service.txt_records.push_back({"network", "ethernet"});
#endif
#ifdef USE_API_NOISE
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
#endif
#ifdef ESPHOME_PROJECT_NAME
service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});

View File

@@ -54,16 +54,16 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const auto &manu_datas = device.get_manufacturer_datas();
if (manu_datas.size() != 1) {
ESP_LOGE(TAG, "%s: Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
return false;
}
const auto &manu_data = manu_datas[0];
ESP_LOGVV(TAG, "%s: Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
ESP_LOGE(TAG, "%s: Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
return false;
}
@@ -72,20 +72,20 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL) {
ESP_LOGE(TAG, "%s: Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
return false;
}
ESP_LOGVV(TAG, "%s: Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "%s: Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
for (u_int8_t i = 0; i < 3; i++) {
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
mopeka_data->val[i].value_0, mopeka_data->val[i].time_0);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
mopeka_data->val[i].value_1, mopeka_data->val[i].time_1);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
mopeka_data->val[i].value_2, mopeka_data->val[i].time_2);
ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
mopeka_data->val[i].value_3, mopeka_data->val[i].time_3);
}
@@ -146,19 +146,19 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
// This value is better than a previous one.
best_value = measurements_value[i];
best_time = measurement_time;
// Reset measurement_time or next values.
measurement_time = 0;
}
// Reset measurement_time or next values.
measurement_time = 0;
}
}
}
ESP_LOGV(TAG, "%s: Found %u values with best data %u time %u.", device.address_str().c_str(),
ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(),
number_of_usable_values, best_value, best_time);
if (number_of_usable_values < 2 || best_value < 2 || best_time < 2) {
if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) {
// At least two measurement values must be present.
ESP_LOGW(TAG, "%s: Poor read quality. Setting distance to 0.", device.address_str().c_str());
ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str());
if (this->distance_ != nullptr) {
this->distance_->publish_state(0);
}
@@ -167,7 +167,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
}
} else {
float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c);
ESP_LOGV(TAG, "%s: Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;

View File

@@ -77,7 +77,7 @@ void MPU6050Component::setup() {
accel_config &= 0b11100111;
accel_config |= (MPU6050_RANGE_2G << 3);
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
if (!this->write_byte(MPU6050_REGISTER_GYRO_CONFIG, gyro_config)) {
if (!this->write_byte(MPU6050_REGISTER_ACCEL_CONFIG, accel_config)) {
this->mark_failed();
return;
}

View File

@@ -52,6 +52,6 @@ async def to_code(config):
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

View File

@@ -0,0 +1,109 @@
#include "pcf8563.h"
#include "esphome/core/log.h"
// Datasheet:
// - https://nl.mouser.com/datasheet/2/302/PCF8563-1127619.pdf
namespace esphome {
namespace pcf8563 {
static const char *const TAG = "PCF8563";
void PCF8563Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up PCF8563...");
if (!this->read_rtc_()) {
this->mark_failed();
}
}
void PCF8563Component::update() { this->read_time(); }
void PCF8563Component::dump_config() {
ESP_LOGCONFIG(TAG, "PCF8563:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with PCF8563 failed!");
}
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; }
void PCF8563Component::read_time() {
if (!this->read_rtc_()) {
return;
}
if (pcf8563_.reg.stop) {
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
return;
}
ESPTime rtc_time{
.second = uint8_t(pcf8563_.reg.second + 10 * pcf8563_.reg.second_10),
.minute = uint8_t(pcf8563_.reg.minute + 10u * pcf8563_.reg.minute_10),
.hour = uint8_t(pcf8563_.reg.hour + 10u * pcf8563_.reg.hour_10),
.day_of_week = uint8_t(pcf8563_.reg.weekday),
.day_of_month = uint8_t(pcf8563_.reg.day + 10u * pcf8563_.reg.day_10),
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
.month = uint8_t(pcf8563_.reg.month + 10u * pcf8563_.reg.month_10),
.year = uint16_t(pcf8563_.reg.year + 10u * pcf8563_.reg.year_10 + 2000),
.is_dst = false, // not used
.timestamp = 0, // overwritten by recalc_timestamp_utc(false)
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
}
void PCF8563Component::write_time() {
auto now = time::RealTimeClock::utcnow();
if (!now.is_valid()) {
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
return;
}
pcf8563_.reg.year = (now.year - 2000) % 10;
pcf8563_.reg.year_10 = (now.year - 2000) / 10 % 10;
pcf8563_.reg.month = now.month % 10;
pcf8563_.reg.month_10 = now.month / 10;
pcf8563_.reg.day = now.day_of_month % 10;
pcf8563_.reg.day_10 = now.day_of_month / 10;
pcf8563_.reg.weekday = now.day_of_week;
pcf8563_.reg.hour = now.hour % 10;
pcf8563_.reg.hour_10 = now.hour / 10;
pcf8563_.reg.minute = now.minute % 10;
pcf8563_.reg.minute_10 = now.minute / 10;
pcf8563_.reg.second = now.second % 10;
pcf8563_.reg.second_10 = now.second / 10;
pcf8563_.reg.stop = false;
this->write_rtc_();
}
bool PCF8563Component::read_rtc_() {
if (!this->read_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) {
ESP_LOGE(TAG, "Can't read I2C data.");
return false;
}
ESP_LOGD(TAG, "Read %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u STOP:%s CLKOUT:%0u", pcf8563_.reg.hour_10,
pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second,
pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10,
pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled);
return true;
}
bool PCF8563Component::write_rtc_() {
if (!this->write_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) {
ESP_LOGE(TAG, "Can't write I2C data.");
return false;
}
ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u OSC:%s CLKOUT:%0u", pcf8563_.reg.hour_10,
pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second,
pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10,
pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled);
return true;
}
} // namespace pcf8563
} // namespace esphome

View File

@@ -0,0 +1,124 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace pcf8563 {
class PCF8563Component : public time::RealTimeClock, public i2c::I2CDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void read_time();
void write_time();
protected:
bool read_rtc_();
bool write_rtc_();
union PCF8563Reg {
struct {
// Control_1 register
bool : 3;
bool power_on_reset : 1;
bool : 1;
bool stop : 1;
bool : 1;
bool ext_test : 1;
// Control_2 register
bool time_int : 1;
bool alarm_int : 1;
bool timer_flag : 1;
bool alarm_flag : 1;
bool timer_int_timer_pulse : 1;
bool : 3;
// Seconds register
uint8_t second : 4;
uint8_t second_10 : 3;
bool clock_int : 1;
// Minutes register
uint8_t minute : 4;
uint8_t minute_10 : 3;
uint8_t : 1;
// Hours register
uint8_t hour : 4;
uint8_t hour_10 : 2;
uint8_t : 2;
// Days register
uint8_t day : 4;
uint8_t day_10 : 2;
uint8_t : 2;
// Weekdays register
uint8_t weekday : 3;
uint8_t unused_3 : 5;
// Months register
uint8_t month : 4;
uint8_t month_10 : 1;
uint8_t : 2;
uint8_t century : 1;
// Years register
uint8_t year : 4;
uint8_t year_10 : 4;
// Minute Alarm register
uint8_t minute_alarm : 4;
uint8_t minute_alarm_10 : 3;
bool minute_alarm_enabled : 1;
// Hour Alarm register
uint8_t hour_alarm : 4;
uint8_t hour_alarm_10 : 2;
uint8_t : 1;
bool hour_alarm_enabled : 1;
// Day Alarm register
uint8_t day_alarm : 4;
uint8_t day_alarm_10 : 2;
uint8_t : 1;
bool day_alarm_enabled : 1;
// Weekday Alarm register
uint8_t weekday_alarm : 3;
uint8_t : 4;
bool weekday_alarm_enabled : 1;
// CLKout control register
uint8_t frequency_output : 2;
uint8_t : 5;
uint8_t clkout_enabled : 1;
// Timer control register
uint8_t timer_source_frequency : 2;
uint8_t : 5;
uint8_t timer_enabled : 1;
// Timer register
uint8_t countdown_period : 8;
} reg;
mutable uint8_t raw[sizeof(reg)];
} pcf8563_;
};
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<PCF8563Component> {
public:
void play(Ts... x) override { this->parent_->write_time(); }
};
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<PCF8563Component> {
public:
void play(Ts... x) override { this->parent_->read_time(); }
};
} // namespace pcf8563
} // namespace esphome

View File

@@ -0,0 +1,62 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome import automation
from esphome.components import i2c, time
from esphome.const import CONF_ID
CODEOWNERS = ["@KoenBreeman"]
DEPENDENCIES = ["i2c"]
pcf8563_ns = cg.esphome_ns.namespace("pcf8563")
pcf8563Component = pcf8563_ns.class_(
"PCF8563Component", time.RealTimeClock, i2c.I2CDevice
)
WriteAction = pcf8563_ns.class_("WriteAction", automation.Action)
ReadAction = pcf8563_ns.class_("ReadAction", automation.Action)
CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(pcf8563Component),
}
).extend(i2c.i2c_device_schema(0xA3))
@automation.register_action(
"pcf8563.write_time",
WriteAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(pcf8563Component),
}
),
)
async def pcf8563_write_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"pcf8563.read_time",
ReadAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(pcf8563Component),
}
),
)
async def pcf8563_read_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await time.register_time(var, config)

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