1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 16:41:50 +00:00

Compare commits

...

175 Commits

Author SHA1 Message Date
Jesse Hills
fcf0761ba3 Merge branch 'dev' into jesserockz-2022-199 2023-05-02 16:08:46 +12:00
Jesse Hills
4b9732629e Update setup based on raspiaudio repo 2023-05-02 14:10:47 +12:00
dependabot[bot]
fb094fca0f Bump zeroconf from 0.56.0 to 0.60.0 (#4767)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-02 09:52:42 +12:00
looping40
de10b356cf Max6956 support added (#3764)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-05-02 09:51:48 +12:00
cooki35
bd6d6caa8a Add support for V2 of the waveshare 5.83in e-paper display. (#3660) 2023-05-02 09:36:20 +12:00
Mat931
1c4af08ed3 Add support for BLE passkey authentication (#4258)
Co-authored-by: Branden Cash <203336+ammmze@users.noreply.github.com>
2023-05-02 09:25:10 +12:00
Philippe FOUQUET
c97d361b6c Add support for hyt271 (#4282)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-05-01 16:18:31 +12:00
marshn
379b1d84dd RF Codec for Drayton Digistat heating controller (#4494) 2023-05-01 16:12:53 +12:00
Luis Andrade
c13e20643b play_folder bugfix and addition of play_mp3 (#4758) 2023-05-01 16:01:24 +12:00
Mat931
76b6fcf554 Add PCA6416A Support (#4681) 2023-05-01 16:00:21 +12:00
Jesse Hills
57e909e790 Only pre-install libraries in docker images (#4766) 2023-05-01 15:57:57 +12:00
dependabot[bot]
d6f7876e68 Bump pyupgrade from 3.3.1 to 3.3.2 (#4751)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-05-01 00:01:52 +00:00
Jesse Hills
56e0923c22 Switch ESPAsyncTCP-esphome to esphome fork (#4764) 2023-05-01 11:09:01 +12:00
tracestep
f4b98f5e32 Power down PN532 before deep sleep (#4707) 2023-05-01 09:24:15 +12:00
Jesse Hills
2d56b70a36 Bump git version in Dockerfile (#4763) 2023-05-01 08:51:46 +12:00
dependabot[bot]
980cfaf295 Bump aioesphomeapi from 13.7.1 to 13.7.2 (#4753)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 07:57:02 +12:00
Keith Burzinski
c2a43c733a Fix sprinkler switch restore_mode (#4756) 2023-05-01 07:52:05 +12:00
RoboMagus
568e65a6ab Fix assumed_state switch webserver (#4259) 2023-05-01 07:28:21 +12:00
Jesse Hills
5cef11acf0 Add include 2023-04-28 17:32:32 +12:00
Jesse Hills
b1ad48c824 Set up mclk 2023-04-28 17:32:32 +12:00
Jesse Hills
35b1b5fae6 Change value 2023-04-28 17:32:32 +12:00
Jesse Hills
ce20b64712 Try setting up later 2023-04-28 17:32:32 +12:00
Jesse Hills
1720b2a37e Set volume on amplifier 2023-04-28 17:32:32 +12:00
Jesse Hills
c974fbbea8 Remove pin stuff 2023-04-28 17:32:32 +12:00
Jesse Hills
00c5233bf4 Remove power_pin from test4 2023-04-28 17:32:32 +12:00
Jesse Hills
7efb138933 Remove power_pin 2023-04-28 17:32:32 +12:00
Jesse Hills
f73435f820 Comment Keyword change 2023-04-28 17:32:31 +12:00
Jesse Hills
bfc55beedc Fix and add to test4 2023-04-28 17:32:28 +12:00
Jesse Hills
9fa057eae8 Create basic ES8388 DAC amplifier component 2023-04-28 17:31:53 +12:00
Jesse Hills
59d6b3afa0 Merge branch 'release' into dev 2023-04-27 20:06:04 +12:00
Jesse Hills
b89c04b928 Merge pull request #4748 from esphome/bump-2023.4.2
2023.4.2
2023-04-27 20:02:15 +12:00
Jesse Hills
12090657bb Bump version to 2023.4.2 2023-04-27 19:36:21 +12:00
itpeters
4e21cf0bdd Don't allow fingerprint_grow enroll cancellation when no enrollment started (#4745) 2023-04-27 19:36:21 +12:00
Jesse Hills
ba4ef72d56 Only request VA port from first client that is subscribed (#4747) 2023-04-27 19:36:21 +12:00
Jimmy Hedman
f3e6a4314f Debug component doesn't work on RP2040 (#4728) 2023-04-27 19:36:21 +12:00
gcopeland
e14ce3d950 I2c scan recovery reset fix (#4724)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-27 19:36:21 +12:00
itpeters
70aa38f5bd Don't allow fingerprint_grow enroll cancellation when no enrollment started (#4745) 2023-04-27 16:34:20 +12:00
Jesse Hills
55ec082628 Only request VA port from first client that is subscribed (#4747) 2023-04-27 04:22:12 +00:00
Jesse Hills
6f27126c8d Move am43 sensor code and remove auto load on cover (#4631) 2023-04-27 13:24:42 +12:00
Jesse Hills
ee21a91313 Add mlx90614 sensors (#3749)
Co-authored-by: Greg Arnold <greg@arnoldassociates.com>
Co-authored-by: notsonominal <130870838+notsonominal@users.noreply.github.com>
2023-04-27 13:17:09 +12:00
Jesse Hills
c5efaa1c00 Remove climate legacy away flags (#4744) 2023-04-27 13:11:32 +12:00
Jesse Hills
6476357596 Expand the platformio dep installer to also install platforms and tools (#4716) 2023-04-27 12:26:06 +12:00
dependabot[bot]
77f71acbc8 Bump pytest from 7.3.0 to 7.3.1 (#4686)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 11:45:23 +12:00
dependabot[bot]
4a08a5413d Bump tornado from 6.2 to 6.3.1 (#4741) 2023-04-27 11:00:34 +12:00
dependabot[bot]
e3d89cc6b6 Bump pylint from 2.17.2 to 2.17.3 (#4740)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 10:49:40 +12:00
RoboMagus
64afb07e91 Fix 'blutooth' typo in esp32_ble component (#4738) 2023-04-27 10:48:53 +12:00
Keith Burzinski
f639f7c280 Add on_tag_removed trigger for RC522 (#4742) 2023-04-27 10:47:45 +12:00
Jimmy Hedman
986dd2ddd2 Debug component doesn't work on RP2040 (#4728) 2023-04-27 08:03:30 +12:00
gcopeland
7abdb5d046 I2c scan recovery reset fix (#4724)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-27 08:00:37 +12:00
Jesse Hills
0f1e186189 Merge pull request #4732 from esphome/bump-2023.4.1
2023.4.1
2023-04-24 09:47:08 +12:00
Jesse Hills
96d208e0d8 Bump version to 2023.4.1 2023-04-24 09:11:07 +12:00
Jesse Hills
38ed38864e Use proper schema for delta filter (#4723) 2023-04-24 09:11:07 +12:00
Samuel Sieb
a12ba7bd38 fix flip_x (#4727) 2023-04-24 09:11:06 +12:00
dependabot[bot]
4a177e3931 Bump aioesphomeapi from 13.7.0 to 13.7.1 (#4725)
Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 13.7.0 to 13.7.1.
- [Release notes](https://github.com/esphome/aioesphomeapi/releases)
- [Commits](https://github.com/esphome/aioesphomeapi/compare/v13.7.0...v13.7.1)

---
updated-dependencies:
- dependency-name: aioesphomeapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-23 20:53:50 +00:00
Alexander Momchilov
bef5b38d49 Add supports_stop trait to Cover (#3897)
* Add "stop" trait to Cover

* Add `supports_stop` to Cover protobuf msg

* Run `script/api_protobuf/api_protobuf.py`

... followed by `script/clang-format -i`

* Add `has_stop` field to template Cover

* Set `has_stop` during Cover codegen

* Set `supports_stop` trait on all other Cover types

* Bump APIVersion to 1.8

---------

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-23 20:51:32 +00:00
Rebbe Pod
0a95f116fc Get Sunrise & Sunset for a Specific Date (#4712)
* Update real_time_clock.cpp

* Update real_time_clock.h

* Update sun.h

* Update sun.h

* Update sun.h

* Enable the sunAtLocation to be used externally

* Enable the sunAtLocation to be used externally

* Update sun.h

* Update sun.h

* update

* update

* update to only use one function

* Update sun.h

* Update sun.cpp
2023-04-23 20:44:35 +00:00
Jesse Hills
327cd662b4 Use proper schema for delta filter (#4723) 2023-04-23 20:42:46 +00:00
Samuel Sieb
bb05ba3d00 fix flip_x (#4727) 2023-04-22 07:41:12 +00:00
Keith Burzinski
c0ad5d1d16 Initial attempt at supporting ESP-IDF 5.0.0 (#4364)
* requirements: add pyparsing >= 3.0

ESP-IDF >= 5.0 requires pyparsing's rest_of_file, which was introduced
in version 3.0.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* esp32: fix build with ESP-IDF >= 5

We need to include esp_timer.h to be able to use esp_timer_get_time().
This header existed in ESP-IDF < 5 so we don't need if guards.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* ota: fix build with ESP-IDF >= 5

As of version 5, esp_task_wdt_init() takes a struct as argument. We also
need to include spi_flash_mmap.h.

[split unrelated change into separate commits, maintain ESP-IDF < 5
compat, use esp_task_wdt_reconfigure, add commit message]
Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* core: fix build with ESP-IDF >= 5

These header files already existed in ESP-IDF < 5 so skip if guards.

[add commit message]
Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* wifi: fix build with ESP-IDF >= 5

ESP-IDF 4.1 introduced the esp-netif API as successor to the tcp_adapter
API. The tcp_adapter API was removed in ESP-IDF 5.0.0. Part of the wifi
component was already migrated to the new API. Migrate the leftover uses
of the old API to the new API to fix build on ESP-IDF >= 5.

The version of ESP-IDF currently in use (4.4.4) supports the new API, so
we don't need any if guards to maintain backwards compatibility.

Also replace xQueueHandle, which is a pre FreeRTOS v8.0.0 data type,
with QueueHandle_t, so we don't need to enable backward compatibility
(CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY).

This reverts part of commit d42f35de5d to wifi_component_esp_idf.cpp,
as the esp-netif API handles that internally.

[replace pre FreeRTOS v8.0.0 data type, add commit message]
Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* mdns: fix build with ESP-IDF >= 5

In ESP-IDF 5.0.0, the mdns component was removed and moved to another
repository. Since the mdns component in esphome is always built, we
need to add the mdns component from the esp-protocols repository. This
component depends on ESP-IDF >= 5.0, so we need to add a version guard.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

* docker: install python3-venv

As of version 6.0.1, platform-espressif32 requires python3-venv.
Switching between esp-idf 4.4.4 and 5.0 causes problems with esp-idf
python dependencies installed by PlatformIO. They've solved this by
using venv. Install python3-venv so that platform-espressif32 6.0.1 and
later can be used, and we don't need to wipe the dependencies manually
when switching esp-idf versions.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>

---------

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
Co-authored-by: Stijn Tintel <stijn@linux-ipv6.be>
2023-04-20 03:54:06 +00:00
Bella Coola
4c39631428 Add support for passive WiFi scanning (#4666)
* Add support for passive WiFi scanning.

* Apply suggestions from code review

Made changes suggested by @jesserockz

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

---------

Co-authored-by: BellaCoola <unknown>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-20 03:53:42 +00:00
Fabian
afc2b3b74f Keep Device Class in Flash. (#4639)
* Keep Device Class in Flash.

* Remove blank line

---------

Co-authored-by: Your Name <you@example.com>
2023-04-20 03:53:35 +00:00
Jesse Hills
19fc1417ae Merge pull request #4715 from esphome/bump-2023.4.0
2023.4.0
2023-04-20 15:18:09 +12:00
Jesse Hills
e2fefa51f5 Bump version to 2023.4.0 2023-04-20 14:06:53 +12:00
Jesse Hills
f668d5617f Merge branch 'beta' into bump-2023.4.0 2023-04-20 14:06:52 +12:00
Jesse Hills
47c4ff15d6 Merge pull request #4714 from esphome/bump-2023.4.0b4
2023.4.0b4
2023-04-20 14:00:12 +12:00
Jesse Hills
ccf1bdc0b4 Bump version to 2023.4.0b4 2023-04-20 13:10:26 +12:00
Jesse Hills
e993fcf80c Bump arduino platform version to 5.3.0 (#4713)
* Bump arduino platform version to 5.3.0

* Update root platformio.ini
2023-04-20 13:10:25 +12:00
tracestep
1bdc30a09e Add ethernet powerdown (fixes esphome/issues#4420) (#4706)
* Add ethernet powerdown

* Add on_shutdown (fixes esphome/issues#4420

* Sync dev and clang-tidy fix

* fix typo and trainling space

* Initialize phy_ member

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

* Use `this` pointer

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

* Member initialized at declaration

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

* Use `this` pointer

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

* Use `this` pointer

---------

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-20 13:10:25 +12:00
Jesse Hills
0f7e34e7ec Bump arduino platform version to 5.3.0 (#4713)
* Bump arduino platform version to 5.3.0

* Update root platformio.ini
2023-04-20 00:44:49 +00:00
tracestep
2be703b329 Add ethernet powerdown (fixes esphome/issues#4420) (#4706)
* Add ethernet powerdown

* Add on_shutdown (fixes esphome/issues#4420

* Sync dev and clang-tidy fix

* fix typo and trainling space

* Initialize phy_ member

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

* Use `this` pointer

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

* Member initialized at declaration

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

* Use `this` pointer

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

* Use `this` pointer

---------

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-19 22:10:01 +00:00
Jesse Hills
4cea74ef3b Call on_error if no api client connected that handles voice (#4709) 2023-04-19 20:56:37 +00:00
Jesse Hills
3a587ea0d4 Add event triggers to voice_assistant (#4699)
* Add event triggers to voice_assistant

* Add triggers to test
2023-04-17 02:57:28 +00:00
Szewcson
8a60919e1f Add timeout to i2c write error logs (#4697) 2023-04-16 20:12:13 +00:00
Jimmy Hedman
382dcddf12 Fixed dns2 for ethernet (#4698) 2023-04-16 20:10:07 +00:00
Jesse Hills
6b67acbeb5 debug component, allow without debug logging (#4685) 2023-04-14 02:29:28 +00:00
dependabot[bot]
7b0fca6824 Bump docker/build-push-action from 3 to 4 (#4367)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 08:13:13 +00:00
dependabot[bot]
47555d314a Bump peter-evans/create-pull-request from 4 to 5 (#4661)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4 to 5.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v4...v5)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 08:12:24 +00:00
dependabot[bot]
0643b71908 Bump aioesphomeapi from 13.5.1 to 13.7.0 (#4676)
Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 13.5.1 to 13.7.0.
- [Release notes](https://github.com/esphome/aioesphomeapi/releases)
- [Commits](https://github.com/esphome/aioesphomeapi/compare/v13.5.1...v13.7.0)

---
updated-dependencies:
- dependency-name: aioesphomeapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 05:31:47 +00:00
kahrendt
afc848bf22 Add Bayesian type for binary_sensor_map component (#4640)
* initial support for Bayesian type

* Cast bool state of binary_sensor to uint64_t

* Rename channels to observations with Bayesian

* Improve/standardize comments for all types

* Use black to correct sensor.py formatting

* Add SUM and BAYESIAN binary sensor map tests

* Remove unused variable

* Update esphome/components/binary_sensor_map/binary_sensor_map.cpp

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

---------

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-04-13 01:48:29 +00:00
Jesse Hills
cc1eb648f9 Only allow 5 jobs from each CI run to be in parallel (#4682) 2023-04-12 22:46:19 +00:00
Jesse Hills
04a139fe3d Bump version to 2023.5.0-dev 2023-04-13 10:13:53 +12:00
Jesse Hills
e4b2de5c68 Merge pull request #4623 from esphome/bump-2023.3.2
2023.3.2
2023-03-27 16:45:14 +13:00
Jesse Hills
f862b479e7 Bump version to 2023.3.2 2023-03-27 15:58:29 +13:00
tracestep
358c59bd8d SX1509 minimum loop period (fixes esphome/issues#4325) (#4613)
* Minimum loop period (fixes esphome/issues#4325)

* clang-tidy suggestions

* More clang-tidy suggestions
2023-03-27 15:58:28 +13:00
guillempages
74fe135c9c Fix animation resizing (#4608)
Animation resizing in RGB24 format is causing an error "Image cannot be resized to a bigger size". Other image types do not show the issue, and the only difference is the "image.thumbnail" call.

Removed the call and tested; the animation is shown with the desired size.
2023-03-27 15:58:28 +13:00
Jesse Hills
8d3896172d Swap curly brackets for round on LockGuard (#4610) 2023-03-27 15:58:28 +13:00
Kai Gerken
9d9725144d Fix compile error on pzemdc.h (#4583) 2023-03-27 15:58:28 +13:00
Jesse Hills
dd8dc1ef1d Merge pull request #4609 from esphome/bump-2023.3.1
2023.3.1
2023-03-22 12:40:28 +13:00
Jesse Hills
bc427de16a Bump version to 2023.3.1 2023-03-22 12:03:34 +13:00
Jesse Hills
db5988bbe1 rp2040: Use fake Mutex lock (#4602) 2023-03-22 12:03:34 +13:00
Nathaniel Wesley Filardo
a3875af4b4 climate: brown paper bag fix for on_configure (#4573)
I forgot this hunk in https://github.com/esphome/esphome/pull/4511 .
I'm sorry for the noise.
2023-03-22 12:03:34 +13:00
Jesse Hills
e6737479f7 Merge pull request #4568 from esphome/bump-2023.3.0
2023.3.0
2023-03-16 10:40:18 +13:00
Jesse Hills
7f75832bf1 Bump version to 2023.3.0 2023-03-16 09:38:19 +13:00
Jesse Hills
33339e3bd8 Merge branch 'beta' into bump-2023.3.0 2023-03-16 09:38:19 +13:00
Jesse Hills
c037e95861 Merge pull request #4478 from esphome/bump-2023.2.4
2023.2.4
2023-02-23 14:17:01 +13:00
Jesse Hills
2e1b35959f Bump version to 2023.2.4 2023-02-23 13:42:02 +13:00
Jesse Hills
7f46d9e0f9 Fix multiple remote_receivers with triggers (#4477) 2023-02-23 13:42:01 +13:00
Samuel Sieb
069b5f81a0 fix parity (#4476)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2023-02-23 13:42:01 +13:00
konsulten
3a36d0b13f BL0939 state_class set for energy sensors (#4463)
BL0939 was missing TOTAL_INCREASING for energy (kWh) thus it did not show as statistics in home assistant
2023-02-23 13:42:01 +13:00
Jesse Hills
f0760e99b7 Merge pull request #4466 from esphome/bump-2023.2.3
2023.2.3
2023-02-20 08:54:32 +13:00
Jesse Hills
18fecf8c09 Bump version to 2023.2.3 2023-02-20 08:16:47 +13:00
Jesse Hills
414cf1b333 Update Manifest to rmeove unused dashboard files and include .c ethernet drivers (#4459) 2023-02-20 08:16:46 +13:00
jmichiel
d10f891f51 fix preset discovery config (#4451)
Co-authored-by: Michiel, Jeroen <jeroen.michiel@teledyneflir.com>
2023-02-20 08:16:46 +13:00
Jesse Hills
b5927322e6 Merge pull request #4458 from esphome/bump-2023.2.2
2023.2.2
2023-02-17 19:38:01 +13:00
Jesse Hills
1cf4107e1c Bump version to 2023.2.2 2023-02-17 19:11:15 +13:00
Jesse Hills
c12408326c Fix adoption of variants and pico-w (#4455) 2023-02-17 19:11:14 +13:00
Jesse Hills
4434e59e5a Merge pull request #4445 from esphome/bump-2023.2.1
2023.2.1
2023-02-16 15:44:32 +13:00
Jesse Hills
45180d98f6 Bump version to 2023.2.1 2023-02-16 14:06:42 +13:00
Jesse Hills
44494ad18e Add ESPHome version to generated platformio.ini (#4443)
* Add ESPHome version to generated platformio.ini

* Move description to platformio section
2023-02-16 14:06:42 +13:00
Jesse Hills
1447536906 Merge pull request #4441 from esphome/bump-2023.2.0
2023.2.0
2023-02-15 22:33:31 +13:00
Jesse Hills
27ec517084 Remove dup line 2023-02-15 21:39:17 +13:00
Jesse Hills
ce1f034bac Bump version to 2023.2.0 2023-02-15 19:29:02 +13:00
Jesse Hills
f1f96f16e9 Merge branch 'beta' into bump-2023.2.0 2023-02-15 19:29:02 +13:00
Jesse Hills
4af4649e23 Merge pull request #4355 from esphome/bump-2022.12.8
2022.12.8
2023-01-27 10:04:29 +13:00
Jesse Hills
8bcddef39d Bump version to 2022.12.8 2023-01-27 09:44:41 +13:00
Franck Nijhof
4ac96ccea2 Add Home Assistant integration discovery (#4328) 2023-01-27 09:44:41 +13:00
Franck Nijhof
3c5de77ae9 Refactor NGINX configuration of Home Assistant Add-on (#4312) 2023-01-27 09:44:41 +13:00
Franck Nijhof
a2925b1d37 Migrate old-style S6 scripts to s6-rc.d (#4311) 2023-01-27 09:44:41 +13:00
Franck Nijhof
73748e9e20 Upgrades add-on base image to 6.2.0 (#4310) 2023-01-27 09:44:40 +13:00
Jesse Hills
75c9823899 Merge pull request #4350 from esphome/bump-2022.12.7
2022.12.7
2023-01-26 15:50:03 +13:00
Jesse Hills
c8c0bd3351 Bump version to 2022.12.7 2023-01-26 15:00:18 +13:00
melyux
e1cdeb7c8f Add a soft reset in setup() for bmp280 (#4329)
fixes https://github.com/esphome/issues/issues/3383
2023-01-26 15:00:17 +13:00
Alex
7f97f42552 Fix BME280 initialization before wifi setup (#4190)
fixes https://github.com/esphome/issues/issues/3530
2023-01-26 15:00:17 +13:00
Stephan Martin
aa7f3569ec rename esp32 CAN to TWAI, so it compiles again (#4334)
fixes https://github.com/esphome/issues/issues/4023
2023-01-26 15:00:17 +13:00
Denis Bodor (aka Lefinnois)
2d0a08442e fix stepper jump back with small steps (#4339) 2023-01-26 15:00:17 +13:00
Joakim Sørensen
d2380756b2 Add "content" to deploy-ha-addon-repo dispatch (#4349) 2023-01-26 15:00:17 +13:00
Jesse Hills
925e3cb6c9 Fix missing s 2023-01-23 20:34:31 +00:00
Jesse Hills
6757acba56 Merge pull request #4342 from esphome/bump-2022.12.6
2022.12.6
2023-01-24 08:58:45 +13:00
Jesse Hills
5cc91cdd95 Bump version to 2022.12.6 2023-01-24 08:30:42 +13:00
Jesse Hills
2b41886819 Move from docker manifest command to buildx with platforms (#4320) 2023-01-24 08:30:42 +13:00
Jesse Hills
72c6efd6a0 Merge pull request #4335 from esphome/bump-2022.12.5
2022.12.5
2023-01-23 09:27:18 +13:00
Jesse Hills
a1f1804112 Bump version to 2022.12.5 2023-01-23 09:06:20 +13:00
Jesse Hills
a8b1ceb4e9 Bump nano version in lint docker image (#4218) 2023-01-23 09:06:20 +13:00
Jesse Hills
4fb0f7f8c6 Merge pull request #4323 from esphome/bump-2022.12.4
2022.12.4
2023-01-21 16:17:57 +13:00
Jesse Hills
958cadeca8 Bump version to 2022.12.4 2023-01-20 18:33:09 +13:00
J. Nick Koston
00f2655f1a Always send the MTU request for BLE v3 cached connections (#4322)
closes https://github.com/esphome/esphome/pull/4321
fixes https://github.com/esphome/issues/issues/4041
fixes https://github.com/esphome/issues/issues/3951
2023-01-20 18:33:09 +13:00
Jesse Hills
074f5029eb Fix gpio pin mode for ISR pins (#4216) 2023-01-20 18:33:09 +13:00
Jesse Hills
1691976587 Merge pull request #4214 from esphome/bump-2022.12.3
2022.12.3
2022-12-20 23:34:01 +13:00
Jesse Hills
60e6b4d21e Bump version to 2022.12.3 2022-12-20 23:15:39 +13:00
Jesse Hills
5750591df2 Fix ESP32 GPIO when using INPUT PULLUP mode (#4213) 2022-12-20 23:15:39 +13:00
Jesse Hills
a75da54455 Merge pull request #4212 from esphome/bump-2022.12.2
2022.12.2
2022-12-20 11:06:34 +13:00
Jesse Hills
de7f6c3f5f Bump version to 2022.12.2 2022-12-20 10:49:36 +13:00
J. Nick Koston
4245480656 Handle zero padding anywhere in the combined adv data (#4208)
fixes https://github.com/esphome/issues/issues/3913
2022-12-20 10:49:35 +13:00
Jesse Hills
1824c8131e Fix import_full_config for adoption configs (#4197)
* Fix git raw url

* Fix setting full config query param

* Force dashboard import urls to have a branch or tag reference for full import
2022-12-20 10:49:35 +13:00
Jesse Hills
4e9606d2e0 Merge pull request #4196 from esphome/bump-2022.12.1
2022.12.1
2022-12-16 14:05:24 +13:00
Jesse Hills
78500fa933 Bump version to 2022.12.1 2022-12-16 13:36:11 +13:00
Jesse Hills
9c69b98a49 Fix i2s_audio media_player compiling for esp32-s2 (#4195) 2022-12-16 13:36:10 +13:00
Jesse Hills
e6d8ef98d3 Mark ESP32-S2 as not having Bluetooth (#4194) 2022-12-16 13:36:10 +13:00
Stefan Agner
3f1af1690b Support non-multiarch toolchains on 32-bit ARM (#4191)
fixes https://github.com/esphome/issues/issues/3904
2022-12-16 13:36:10 +13:00
Jesse Hills
84374b6b1e Merge pull request #4186 from esphome/bump-2022.12.0
2022.12.0
2022-12-14 17:06:24 +13:00
Jesse Hills
391316c9b5 Bump version to 2022.12.0 2022-12-14 16:37:39 +13:00
Jesse Hills
705c62ebd7 Merge branch 'beta' into bump-2022.12.0 2022-12-14 16:37:39 +13:00
Jesse Hills
7209dd8bae Merge pull request #4152 from esphome/bump-2022.11.5
2022.11.5
2022-12-06 13:12:57 +13:00
Jesse Hills
ab736c89bb Bump version to 2022.11.5 2022-12-06 12:52:48 +13:00
Jesse Hills
6911639617 Fix board pin alias lookup (#4147) 2022-12-06 12:52:48 +13:00
Jesse Hills
b9720d0715 Merge pull request #4130 from esphome/bump-2022.11.4
2022.11.4
2022-12-01 15:38:52 +13:00
Jesse Hills
47b3267ed4 Bump version to 2022.11.4 2022-12-01 13:47:50 +13:00
Jesse Hills
e16ba2adb5 Fix queuing scripts not compiling (#4077) 2022-12-01 13:47:50 +13:00
Nicolas Graziano
0a19b1e32c Dashboard, after login use relative url. (#4103) 2022-12-01 13:47:49 +13:00
Jesse Hills
bae9a950c0 current-based cover fix copy paste mistake (#4124) 2022-12-01 13:47:49 +13:00
Jesse Hills
72b2943332 Merge pull request #4083 from esphome/bump-2022.11.3
2022.11.3
2022-11-25 07:23:08 +13:00
Jesse Hills
4ec0ef7548 Bump version to 2022.11.3 2022-11-24 17:01:52 +13:00
Jesse Hills
25bc6761f6 Don't convert climate temperature step (#4082) 2022-11-24 17:01:52 +13:00
Brian Kaufman
81b6562c25 Fix units for refresh: never (#4048) 2022-11-24 17:01:52 +13:00
Samuel Sieb
ae74189fc2 fix missing library (#4051) 2022-11-24 17:01:51 +13:00
Jesse Hills
9e516efe10 Merge pull request #4074 from esphome/bump-2022.11.2
2022.11.2
2022-11-23 16:06:34 +13:00
Jesse Hills
366e29439e Bump version to 2022.11.2 2022-11-23 13:04:21 +13:00
J. Nick Koston
1c9c700d7f Avoid creating a new espbt::ESPBTUUID each loop when registering for notify (#4069) 2022-11-23 13:04:21 +13:00
J. Nick Koston
b2e6b9d31f Avoid 128bit uuid loop for 16/32 bit uuids (#4068) 2022-11-23 13:04:21 +13:00
Jesse Hills
7623f63846 rp2040_pwm frequency is per pair of pins (#4061) 2022-11-23 13:04:21 +13:00
Jesse Hills
2bfaf9dce3 Update web_server index (#4060) 2022-11-23 13:04:20 +13:00
Jesse Hills
5c2c1560bb Fix rp2040 pwm to use pico-sdk, not mbed (#4059) 2022-11-23 13:04:20 +13:00
Jesse Hills
f7096ab78e Merge pull request #4041 from esphome/bump-2022.11.1
2022.11.1
2022-11-17 15:40:51 +13:00
Jesse Hills
98f8feb625 Bump version to 2022.11.1 2022-11-17 13:52:15 +13:00
Jesse Hills
9944ca414e Support ADC on RP2040 (#4040) 2022-11-17 13:52:15 +13:00
132 changed files with 2864 additions and 425 deletions

View File

@@ -11,6 +11,7 @@ on:
- ".github/workflows/**"
- "requirements*.txt"
- "platformio.ini"
- "script/platformio_install_deps.py"
pull_request:
paths:
@@ -18,6 +19,7 @@ on:
- ".github/workflows/**"
- "requirements*.txt"
- "platformio.ini"
- "script/platformio_install_deps.py"
permissions:
contents: read

View File

@@ -23,6 +23,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
include:
- id: ci-custom

View File

@@ -117,7 +117,7 @@ jobs:
--suffix "${{ matrix.image.suffix }}"
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/Dockerfile

View File

@@ -48,7 +48,7 @@ jobs:
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Commit changes
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v5
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com>

View File

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

View File

@@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
@@ -109,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M
esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12
esphome/components/i2c/* @esphome/core
esphome/components/i2s_audio/* @jesserockz
esphome/components/i2s_audio/media_player/* @jesserockz
@@ -138,6 +140,7 @@ esphome/components/ltr390/* @sjtrny
esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger
esphome/components/max6956/* @looping40
esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz
@@ -162,6 +165,7 @@ esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz
esphome/components/mmc5603/* @benhoff
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
@@ -186,6 +190,7 @@ esphome/components/nfc/* @jesserockz
esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pid/* @OttoWinter

View File

@@ -24,8 +24,9 @@ RUN \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \
git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \
&& rm -rf \
@@ -59,10 +60,10 @@ RUN \
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini
&& /platformio_install_deps.py /platformio.ini --libraries
# ======================= docker-type image =======================

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env python3
# This script is used in the docker containers to preinstall
# all platformio libraries in the global storage
import configparser
import subprocess
import sys
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
config.read(sys.argv[1])
libs = []
# Extract from every lib_deps key in all sections
for section in config.sections():
conf = config[section]
if "lib_deps" not in conf:
continue
for lib_dep in conf["lib_deps"].splitlines():
if not lib_dep:
# Empty line or comment
continue
if lib_dep.startswith("${"):
# Extending from another section
continue
if "@" not in lib_dep:
# No version pinned, this is an internal lib
continue
libs.append(lib_dep)
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View File

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

View File

@@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43", "sensor"]
AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position"
@@ -27,10 +27,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config)
await cg.register_component(var, config)
await cover.register_cover(var, config)
await ble_client.register_ble_node(var, config)

View File

@@ -40,6 +40,7 @@ void Am43Component::loop() {
CoverTraits Am43Component::get_traits() {
auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true);
traits.set_supports_tilt(false);
traits.set_is_assumed_state(false);

View File

@@ -11,6 +11,7 @@ from esphome.const import (
UNIT_PERCENT,
)
AUTO_LOAD = ["am43"]
CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43")
@@ -38,15 +39,15 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield ble_client.register_ble_node(var, config)
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))

View File

@@ -1,6 +1,6 @@
#include "am43.h"
#include "esphome/core/log.h"
#include "am43_sensor.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32

View File

@@ -288,6 +288,7 @@ message ListEntitiesCoverResponse {
bool disabled_by_default = 9;
string icon = 10;
EntityCategory entity_category = 11;
bool supports_stop = 12;
}
enum LegacyCoverState {
@@ -861,8 +862,7 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
bool legacy_away = 7;
bool unused_legacy_away = 7;
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10;
@@ -885,9 +885,8 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
bool has_legacy_away = 10;
bool legacy_away = 11;
bool unused_has_legacy_away = 10;
bool unused_legacy_away = 11;
bool has_fan_mode = 12;
ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14;

View File

@@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
resp.custom_fan_mode = climate->custom_fan_mode.value();
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
}
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
resp.custom_preset = climate->custom_preset.value();
@@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_legacy_away)
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
@@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 7;
resp.api_version_minor = 8;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name();

View File

@@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 12: {
this->supports_stop = value.as_bool();
return true;
}
default:
return false;
}
@@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
buffer.encode_bool(12, this->supports_stop);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
@@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" supports_stop: ");
out.append(YESNO(this->supports_stop));
out.append("\n");
out.append("}");
}
#endif
@@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true;
}
case 7: {
this->legacy_away = value.as_bool();
this->unused_legacy_away = value.as_bool();
return true;
}
case 8: {
@@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->target_temperature);
buffer.encode_float(5, this->target_temperature_low);
buffer.encode_float(6, this->target_temperature_high);
buffer.encode_bool(7, this->legacy_away);
buffer.encode_bool(7, this->unused_legacy_away);
buffer.encode_enum<enums::ClimateAction>(8, this->action);
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
@@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append(" unused_legacy_away: ");
out.append(YESNO(this->unused_legacy_away));
out.append("\n");
out.append(" action: ");
@@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true;
}
case 10: {
this->has_legacy_away = value.as_bool();
this->unused_has_legacy_away = value.as_bool();
return true;
}
case 11: {
this->legacy_away = value.as_bool();
this->unused_legacy_away = value.as_bool();
return true;
}
case 12: {
@@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->target_temperature_low);
buffer.encode_bool(8, this->has_target_temperature_high);
buffer.encode_float(9, this->target_temperature_high);
buffer.encode_bool(10, this->has_legacy_away);
buffer.encode_bool(11, this->legacy_away);
buffer.encode_bool(10, this->unused_has_legacy_away);
buffer.encode_bool(11, this->unused_legacy_away);
buffer.encode_bool(12, this->has_fan_mode);
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
buffer.encode_bool(14, this->has_swing_mode);
@@ -3959,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" has_legacy_away: ");
out.append(YESNO(this->has_legacy_away));
out.append(" unused_has_legacy_away: ");
out.append(YESNO(this->unused_has_legacy_away));
out.append("\n");
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append(" unused_legacy_away: ");
out.append(YESNO(this->unused_legacy_away));
out.append("\n");
out.append(" has_fan_mode: ");

View File

@@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage {
float target_temperature{0.0f};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
bool legacy_away{false};
bool unused_legacy_away{false};
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
@@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage {
float target_temperature_low{0.0f};
bool has_target_temperature_high{false};
float target_temperature_high{0.0f};
bool has_legacy_away{false};
bool legacy_away{false};
bool unused_has_legacy_away{false};
bool unused_legacy_away{false};
bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false};

View File

@@ -429,15 +429,16 @@ void APIServer::on_shutdown() {
#ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant() {
bool result = false;
for (auto &c : this->clients_) {
result |= c->request_voice_assistant(true);
if (c->request_voice_assistant(true))
return true;
}
return result;
return false;
}
void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) {
c->request_voice_assistant(false);
if (c->request_voice_assistant(false))
return;
}
}
#endif

View File

@@ -18,5 +18,5 @@ async def to_code(config):
# https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
elif CORE.is_esp8266:
# https://github.com/OttoWinter/ESPAsyncTCP
cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
# https://github.com/esphome/ESPAsyncTCP
cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3")

View File

@@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
}
BinarySensor::BinarySensor() : state(false) {}
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string BinarySensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void BinarySensor::add_filter(Filter *filter) {
filter->parent_ = this;
if (this->filter_list_ == nullptr) {

View File

@@ -34,7 +34,7 @@ namespace binary_sensor {
* The sub classes should notify the front-end of new states via the publish_state() method which
* handles inverted inputs for you.
*/
class BinarySensor : public EntityBase {
class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
public:
explicit BinarySensor();
@@ -60,12 +60,6 @@ class BinarySensor : public EntityBase {
/// The current reported state of the binary sensor.
bool state;
/// Manually set the Home Assistant device class (see binary_sensor::device_class)
void set_device_class(const std::string &device_class);
/// Get the device class for this binary sensor, using the manual override if specified.
std::string get_device_class();
void add_filter(Filter *filter);
void add_filters(const std::vector<Filter *> &filters);
@@ -82,7 +76,6 @@ class BinarySensor : public EntityBase {
protected:
CallbackManager<void(bool)> state_callback_{};
optional<std::string> device_class_{}; ///< Stores the override of the device class
Filter *filter_list_{nullptr};
bool has_state_{false};
bool publish_initial_state_{false};

View File

@@ -16,6 +16,9 @@ void BinarySensorMap::loop() {
case BINARY_SENSOR_MAP_TYPE_SUM:
this->process_sum_();
break;
case BINARY_SENSOR_MAP_TYPE_BAYESIAN:
this->process_bayesian_();
break;
}
}
@@ -23,46 +26,51 @@ void BinarySensorMap::process_group_() {
float total_current_value = 0.0;
uint8_t num_active_sensors = 0;
uint64_t mask = 0x00;
// check all binary_sensors for its state. when active add its value to total_current_value.
// create a bitmask for the binary_sensor status on all channels
// - check all binary_sensors for its state
// - if active, add its value to total_current_value.
// - creates a bitmask for the binary_sensor states on all channels
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
if (bs.binary_sensor->state) {
num_active_sensors++;
total_current_value += bs.sensor_value;
total_current_value += bs.parameters.sensor_value;
mask |= 1ULL << i;
}
}
// check if the sensor map was touched
// potentially update state only if a binary_sensor is active
if (mask != 0ULL) {
// did the bit_mask change or is it a new sensor touch
// publish the average if the bitmask has changed
if (this->last_mask_ != mask) {
float publish_value = total_current_value / num_active_sensors;
this->publish_state(publish_value);
}
} else if (this->last_mask_ != 0ULL) {
// is this a new sensor release
// no buttons are pressed and the states have changed since last run, so publish NAN
ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
this->publish_state(NAN);
}
this->last_mask_ = mask;
}
void BinarySensorMap::process_sum_() {
float total_current_value = 0.0;
uint64_t mask = 0x00;
// - check all binary_sensor states
// - if active, add its value to total_current_value
// - creates a bitmask for the binary_sensor status on all channels
// - creates a bitmask for the binary_sensor states on all channels
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
if (bs.binary_sensor->state) {
total_current_value += bs.sensor_value;
total_current_value += bs.parameters.sensor_value;
mask |= 1ULL << i;
}
}
// update state only if the binary sensor states have changed or if no state has ever been sent on boot
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
if ((this->last_mask_ != mask) || (!this->has_state())) {
this->publish_state(total_current_value);
}
@@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() {
this->last_mask_ = mask;
}
void BinarySensorMap::process_bayesian_() {
float posterior_probability = this->bayesian_prior_;
uint64_t mask = 0x00;
// - compute the posterior probability by taking the product of the predicate probablities for each observation
// - create a bitmask for the binary_sensor states on all channels/observations
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
posterior_probability *=
this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability,
bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false);
mask |= ((uint64_t) (bs.binary_sensor->state)) << i;
}
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
if ((this->last_mask_ != mask) || (!this->has_state())) {
this->publish_state(posterior_probability);
}
this->last_mask_ = mask;
}
float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true,
float prob_given_false) {
float prob_state_source_true = prob_given_true;
float prob_state_source_false = prob_given_false;
// if sensor is off, then we use the probabilities for the observation's complement
if (!sensor_state) {
prob_state_source_true = 1 - prob_given_true;
prob_state_source_false = 1 - prob_given_false;
}
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
}
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
BinarySensorMapChannel sensor_channel{
.binary_sensor = sensor,
.sensor_value = value,
.parameters{
.sensor_value = value,
},
};
this->channels_.push_back(sensor_channel);
}
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) {
BinarySensorMapChannel sensor_channel{
.binary_sensor = sensor,
.parameters{
.probabilities{
.given_true = prob_given_true,
.given_false = prob_given_false,
},
},
};
this->channels_.push_back(sensor_channel);
}
} // namespace binary_sensor_map
} // namespace esphome

View File

@@ -12,51 +12,88 @@ namespace binary_sensor_map {
enum BinarySensorMapType {
BINARY_SENSOR_MAP_TYPE_GROUP,
BINARY_SENSOR_MAP_TYPE_SUM,
BINARY_SENSOR_MAP_TYPE_BAYESIAN,
};
struct BinarySensorMapChannel {
binary_sensor::BinarySensor *binary_sensor;
float sensor_value;
union {
float sensor_value;
struct {
float given_true;
float given_false;
} probabilities;
} parameters;
};
/** Class to group binary_sensors to one Sensor.
/** Class to map one or more binary_sensors to one Sensor.
*
* Each binary sensor represents a float value in the group.
* Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result
*/
class BinarySensorMap : public sensor::Sensor, public Component {
public:
void dump_config() override;
/**
* The loop checks all binary_sensor states
* When the binary_sensor reports a true value for its state, then the float value it represents is added to the
* total_current_value
* The loop calls the configured type processing method
*
* Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors
* average value. When the value changed and no sensors ar active we publish NAN.
* */
* The processing method loops through all sensors and calculates the numerical result
* The result is only published if a binary sensor state has changed or, for some types, on initial boot
*/
void loop() override;
float get_setup_priority() const override { return setup_priority::DATA; }
/** Add binary_sensors to the group.
* Each binary_sensor represents a float value when its state is true
/**
* Add binary_sensors to the group when only one parameter is needed for the configured mapping type.
*
* @param *sensor The binary sensor.
* @param value The value this binary_sensor represents
*/
void add_channel(binary_sensor::BinarySensor *sensor, float value);
void set_sensor_type(BinarySensorMapType sensor_type);
/**
* Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type.
*
* @param *sensor The binary sensor.
* @param prob_given_true Probability this observation is on when the Bayesian event is true
* @param prob_given_false Probability this observation is on when the Bayesian event is false
*/
void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false);
void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; };
protected:
std::vector<BinarySensorMapChannel> channels_{};
BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
// this gives max 64 channels per binary_sensor_map
// this allows a max of 64 channels/observations in order to keep track of binary_sensor states
uint64_t last_mask_{0x00};
// Bayesian event prior probability before taking into account any observations
float bayesian_prior_{};
/**
* methods to process the types of binary_sensor_maps
* GROUP: process_group_() just map to a value
* Methods to process the binary_sensor_maps types
*
* GROUP: process_group_() averages all the values
* ADD: process_add_() adds all the values
* BAYESIAN: process_bayesian_() computes the predicate probability
* */
void process_group_();
void process_sum_();
void process_bayesian_();
/**
* Computes the Bayesian predicate for a specific observation
* If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement
*
* @param sensor_state State of observation
* @param prior Prior probability before accounting for this observation
* @param prob_given_true Probability this observation is on when the Bayesian event is true
* @param prob_given_false Probability this observation is on when the Bayesian event is false
* */
float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false);
};
} // namespace binary_sensor_map

View File

@@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_(
)
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
CONF_BAYESIAN = "bayesian"
CONF_PRIOR = "prior"
CONF_PROB_GIVEN_TRUE = "prob_given_true"
CONF_PROB_GIVEN_FALSE = "prob_given_false"
CONF_OBSERVATIONS = "observations"
SENSOR_MAP_TYPES = {
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN,
}
entry = {
entry_one_parameter = {
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_VALUE): cv.float_,
}
entry_bayesian_parameters = {
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1),
cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1),
}
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_GROUP: sensor.sensor_schema(
@@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema(
).extend(
{
cv.Required(CONF_CHANNELS): cv.All(
cv.ensure_list(entry), cv.Length(min=1, max=64)
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
),
}
),
@@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema(
).extend(
{
cv.Required(CONF_CHANNELS): cv.All(
cv.ensure_list(entry), cv.Length(min=1, max=64)
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
),
}
),
CONF_BAYESIAN: sensor.sensor_schema(
BinarySensorMap,
accuracy_decimals=2,
).extend(
{
cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1),
cv.Required(CONF_OBSERVATIONS): cv.All(
cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64)
),
}
),
@@ -66,6 +90,17 @@ async def to_code(config):
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
cg.add(var.set_sensor_type(constant))
for ch in config[CONF_CHANNELS]:
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
if config[CONF_TYPE] == CONF_BAYESIAN:
cg.add(var.set_bayesian_prior(config[CONF_PRIOR]))
for obs in config[CONF_OBSERVATIONS]:
input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR])
cg.add(
var.add_channel(
input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE]
)
)
else:
for ch in config[CONF_CHANNELS]:
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))

View File

@@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_(
BLEClientDisconnectTrigger = ble_client_ns.class_(
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
)
BLEClientPasskeyRequestTrigger = ble_client_ns.class_(
"BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef)
)
BLEClientPasskeyNotificationTrigger = ble_client_ns.class_(
"BLEClientPasskeyNotificationTrigger",
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
)
BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_(
"BLEClientNumericComparisonRequestTrigger",
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
)
# Actions
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
BLEPasskeyReplyAction = ble_client_ns.class_(
"BLEClientPasskeyReplyAction", automation.Action
)
BLENumericComparisonReplyAction = ble_client_ns.class_(
"BLEClientNumericComparisonReplyAction", automation.Action
)
BLERemoveBondAction = ble_client_ns.class_(
"BLEClientRemoveBondAction", automation.Action
)
CONF_PASSKEY = "passkey"
CONF_ACCEPT = "accept"
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
# enforce this in yaml checks.
@@ -56,6 +83,29 @@ CONFIG_SCHEMA = (
),
}
),
cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLEClientPasskeyRequestTrigger
),
}
),
cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLEClientPasskeyNotificationTrigger
),
}
),
cv.Optional(
CONF_ON_NUMERIC_COMPARISON_REQUEST
): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLEClientNumericComparisonRequestTrigger
),
}
),
}
)
.extend(cv.COMPONENT_SCHEMA)
@@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema(
}
)
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean),
}
)
BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)),
}
)
BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
}
)
@automation.register_action(
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
)
async def ble_write_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
value = config[CONF_VALUE]
if cg.is_template(value):
@@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args):
return var
@automation.register_action(
"ble_client.numeric_comparison_reply",
BLENumericComparisonReplyAction,
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA,
)
async def numeric_comparison_reply_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
accept = config[CONF_ACCEPT]
if cg.is_template(accept):
templ = await cg.templatable(accept, args, cg.bool_)
cg.add(var.set_value_template(templ))
else:
cg.add(var.set_value_simple(accept))
return var
@automation.register_action(
"ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA
)
async def passkey_reply_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
passkey = config[CONF_PASSKEY]
if cg.is_template(passkey):
templ = await cg.templatable(passkey, args, cg.uint32)
cg.add(var.set_value_template(templ))
else:
cg.add(var.set_value_simple(passkey))
return var
@automation.register_action(
"ble_client.remove_bond",
BLERemoveBondAction,
BLE_REMOVE_BOND_ACTION_SCHEMA,
)
async def remove_bond_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -148,3 +267,12 @@ async def to_code(config):
for conf in config.get(CONF_ON_DISCONNECT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PASSKEY_REQUEST, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)

View File

@@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
}
};
class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
this->trigger();
}
}
};
class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode {
public:
explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
uint32_t passkey = param->ble_security.key_notif.passkey;
this->trigger(passkey);
}
}
};
class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode {
public:
explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_NC_REQ_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
uint32_t passkey = param->ble_security.key_notif.passkey;
this->trigger(passkey);
}
}
};
class BLEWriterClientNode : public BLEClientNode {
public:
BLEWriterClientNode(BLEClient *ble_client) {
@@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
};
template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
public:
BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
void play(Ts... x) override {
uint32_t passkey;
if (has_simple_value_) {
passkey = this->value_simple_;
} else {
passkey = this->value_template_(x...);
}
if (passkey > 999999)
return;
esp_bd_addr_t remote_bda;
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
esp_ble_passkey_reply(remote_bda, true, passkey);
}
void set_value_template(std::function<uint32_t(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
}
void set_value_simple(const uint32_t &value) {
this->value_simple_ = value;
has_simple_value_ = true;
}
private:
BLEClient *parent_{nullptr};
bool has_simple_value_ = true;
uint32_t value_simple_{0};
std::function<uint32_t(Ts...)> value_template_{};
};
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
public:
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
void play(Ts... x) override {
esp_bd_addr_t remote_bda;
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
if (has_simple_value_) {
esp_ble_confirm_reply(remote_bda, this->value_simple_);
} else {
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
}
}
void set_value_template(std::function<bool(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
}
void set_value_simple(const bool &value) {
this->value_simple_ = value;
has_simple_value_ = true;
}
private:
BLEClient *parent_{nullptr};
bool has_simple_value_ = true;
bool value_simple_{false};
std::function<bool(Ts...)> value_template_{};
};
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
public:
BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
void play(Ts... x) override {
esp_bd_addr_t remote_bda;
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
esp_ble_remove_bond_device(remote_bda);
}
private:
BLEClient *parent_{nullptr};
};
} // namespace ble_client
} // namespace esphome

View File

@@ -27,7 +27,7 @@ class BLEClient;
class BLEClientNode {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
esp_ble_gattc_cb_param_t *param){};
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
virtual void loop() {}
void set_address(uint64_t address) { address_ = address; }

View File

@@ -13,8 +13,5 @@ void Button::press() {
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() { return this->device_class_; }
} // namespace button
} // namespace esphome

View File

@@ -26,7 +26,7 @@ namespace button {
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
class Button : public EntityBase, public EntityBase_DeviceClass {
public:
/** Press this button. This is called by the front-end.
*
@@ -40,19 +40,12 @@ class Button : public EntityBase {
*/
void add_on_press_callback(std::function<void()> &&callback);
/// Set the Home Assistant device class (see button::device_class).
void set_device_class(const std::string &device_class);
/// Get the device class for this button.
std::string get_device_class();
protected:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action() = 0;
CallbackManager<void()> press_callback_{};
std::string device_class_{};
};
} // namespace button

View File

@@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
validate_climate_fan_mode
),
@@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
)
cg.add(var.set_target_temperature_high(template_))
if CONF_AWAY in config:
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
cg.add(var.set_away(template_))
if CONF_FAN_MODE in config:
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
cg.add(var.set_fan_mode(template_))

View File

@@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
optional<bool> ClimateCall::get_away() const {
if (!this->preset_.has_value())
return {};
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
}
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
ClimateCall &ClimateCall::set_away(bool away) {
this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_away(optional<bool> away) {
if (away.has_value())
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
this->target_temperature_high_ = target_temperature_high;
return *this;

View File

@@ -64,10 +64,6 @@ class ClimateCall {
* For climate devices with two point target temperature control
*/
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
ClimateCall &set_away(bool away);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
ClimateCall &set_away(optional<bool> away);
/// Set the fan mode of the climate device.
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
/// Set the fan mode of the climate device.
@@ -97,8 +93,6 @@ class ClimateCall {
const optional<float> &get_target_temperature() const;
const optional<float> &get_target_temperature_low() const;
const optional<float> &get_target_temperature_high() const;
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
optional<bool> get_away() const;
const optional<ClimateFanMode> &get_fan_mode() const;
const optional<ClimateSwingMode> &get_swing_mode() const;
const optional<std::string> &get_custom_fan_mode() const;
@@ -184,14 +178,6 @@ class Climate : public EntityBase {
};
};
/** Whether the climate device is in away mode.
*
* Away allows climate devices to have two different target temperature configs:
* one for normal mode and one for away mode.
*/
ESPDEPRECATED("away is deprecated, use preset instead", "v1.20")
bool away{false};
/// The active fan mode of the climate device.
optional<ClimateFanMode> fan_mode;

View File

@@ -117,15 +117,6 @@ class ClimateTraits {
bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset);
}
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
void set_supports_away(bool supports) {
if (supports) {
supported_presets_.insert(CLIMATE_PRESET_AWAY);
supported_presets_.insert(CLIMATE_PRESET_HOME);
}
}
ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }

View File

@@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() {
// copy traits manually so it doesn't break when new options are added
// but the control() method hasn't implemented them yet.
traits.set_is_assumed_state(base.get_is_assumed_state());
traits.set_supports_stop(base.get_supports_stop());
traits.set_supports_position(base.get_supports_position());
traits.set_supports_tilt(base.get_supports_tilt());
traits.set_supports_toggle(base.get_supports_toggle());

View File

@@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) {
return *this;
}
bool CoverCall::get_stop() const { return this->stop_; }
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
CoverCall Cover::make_call() { return {this}; }
void Cover::open() {
auto call = this->make_call();
@@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() {
return {};
return recovered;
}
std::string Cover::get_device_class() {
if (this->device_class_override_.has_value())
return *this->device_class_override_;
return "";
}
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }

View File

@@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op);
* to control all values of the cover. Also implement get_traits() to return what operations
* the cover supports.
*/
class Cover : public EntityBase {
class Cover : public EntityBase, public EntityBase_DeviceClass {
public:
explicit Cover();
@@ -156,8 +156,6 @@ class Cover : public EntityBase {
void publish_state(bool save = true);
virtual CoverTraits get_traits() = 0;
void set_device_class(const std::string &device_class);
std::string get_device_class();
/// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
bool is_fully_open() const;
@@ -172,7 +170,6 @@ class Cover : public EntityBase {
optional<CoverRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
optional<std::string> device_class_override_{};
ESPPreferenceObject rtc_;
};

View File

@@ -15,12 +15,15 @@ class CoverTraits {
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
bool get_supports_toggle() const { return this->supports_toggle_; }
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
bool get_supports_stop() const { return this->supports_stop_; }
void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; }
protected:
bool is_assumed_state_{false};
bool supports_position_{false};
bool supports_tilt_{false};
bool supports_toggle_{false};
bool supports_stop_{false};
};
} // namespace cover

View File

@@ -12,6 +12,7 @@ using namespace esphome::cover;
CoverTraits CurrentBasedCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_is_assumed_state(false);

View File

@@ -17,26 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug")
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.polling_component_schema("60s"))
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "esp8266"]),
)
async def to_code(config):

View File

@@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component {
traits.set_supports_tilt(true);
break;
case DemoCoverType::TYPE_4:
traits.set_supports_stop(true);
traits.set_is_assumed_state(true);
traits.set_supports_tilt(true);
break;

View File

@@ -40,6 +40,7 @@ DEVICE = {
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
@@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
return var
@automation.register_action(
"dfplayer.play_mp3",
PlayMp3Action,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Required(CONF_FILE): cv.templatable(cv.int_),
},
key=CONF_FILE,
),
)
async def dfplayer_play_mp3_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_ = await cg.templatable(config[CONF_FILE], args, float)
cg.add(var.set_file(template_))
return var
@automation.register_action(
"dfplayer.play",
PlayFileAction,

View File

@@ -7,10 +7,10 @@ namespace dfplayer {
static const char *const TAG = "dfplayer";
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
if (folder < 100 && file < 256) {
if (folder <= 10 && file <= 1000) {
this->ack_set_is_playing_ = true;
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
} else if (folder <= 10 && file <= 1000) {
} else if (folder < 100 && file < 256) {
this->ack_set_is_playing_ = true;
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
} else {

View File

@@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component {
this->ack_set_is_playing_ = true;
this->send_cmd_(0x02);
}
void play_mp3(uint16_t file) {
this->ack_set_is_playing_ = true;
this->send_cmd_(0x12, file);
}
void play_file(uint16_t file) {
this->ack_set_is_playing_ = true;
this->send_cmd_(0x03, file);
@@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component {
DFPLAYER_SIMPLE_ACTION(NextAction, next)
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)
void play(Ts... x) override {
auto file = this->file_.value(x...);
this->parent_->play_mp3(file);
}
};
template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)

View File

@@ -11,6 +11,7 @@ using namespace esphome::cover;
CoverTraits EndstopCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_is_assumed_state(false);

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
es8388_ns = cg.esphome_ns.namespace("es8388")
ES8388Component = es8388_ns.class_("ES8388Component", cg.Component, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema({cv.GenerateID(): cv.declare_id(ES8388Component)})
.extend(i2c.i2c_device_schema(0x10))
.extend(cv.COMPONENT_SCHEMA)
)
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)

View File

@@ -0,0 +1,75 @@
#include "es8388_component.h"
#include "esphome/core/hal.h"
#include <soc/io_mux_reg.h>
namespace esphome {
namespace es8388 {
void ES8388Component::setup() {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);
// mute
this->write_byte(0x19, 0x04);
// powerup
this->write_byte(0x01, 0x50);
this->write_byte(0x02, 0x00);
// worker mode
this->write_byte(0x08, 0x00);
// DAC powerdown
this->write_byte(0x04, 0xC0);
// vmidsel/500k ADC/DAC idem
this->write_byte(0x00, 0x12);
// i2s 16 bits
this->write_byte(0x17, 0x18);
// sample freq 256
this->write_byte(0x18, 0x02);
// LIN2/RIN2 for mixer
this->write_byte(0x26, 0x00);
// left DAC to left mixer
this->write_byte(0x27, 0x90);
// right DAC to right mixer
this->write_byte(0x2A, 0x90);
// DACLRC ADCLRC idem
this->write_byte(0x2B, 0x80);
this->write_byte(0x2D, 0x00);
// DAC volume max
this->write_byte(0x1B, 0x00);
this->write_byte(0x1A, 0x00);
// ADC poweroff
this->write_byte(0x03, 0xFF);
// ADC amp 24dB
this->write_byte(0x09, 0x88);
// LINPUT1/RINPUT1
this->write_byte(0x0A, 0x00);
// ADC mono left
this->write_byte(0x0B, 0x02);
// i2S 16b
this->write_byte(0x0C, 0x0C);
// MCLK 256
this->write_byte(0x0D, 0x02);
// ADC Volume
this->write_byte(0x10, 0x00);
this->write_byte(0x11, 0x00);
// ALC OFF
this->write_byte(0x03, 0x09);
this->write_byte(0x2B, 0x80);
this->write_byte(0x02, 0xF0);
delay(1);
this->write_byte(0x02, 0x00);
// DAC power-up LOUT1/ROUT1 enabled
this->write_byte(0x04, 0x30);
this->write_byte(0x03, 0x00);
// DAC volume max
this->write_byte(0x2E, 0x1C);
this->write_byte(0x2F, 0x1C);
// unmute
this->write_byte(0x19, 0x00);
}
} // namespace es8388
} // namespace esphome

View File

@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
namespace esphome {
namespace es8388 {
class ES8388Component : public Component, public i2c::I2CDevice {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE - 1; }
};
} // namespace es8388
} // namespace esphome

View File

@@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
# The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
@@ -252,7 +252,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32 @ {value}"
return f"platformio/espressif32@{value}"
except cv.Invalid:
return value
@@ -367,12 +367,12 @@ async def to_code(config):
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"]
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
@@ -433,7 +433,7 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.partitions", "partitions.csv")

View File

@@ -7,6 +7,7 @@
#include <freertos/task.h>
#include <esp_idf_version.h>
#include <esp_task_wdt.h>
#include <esp_timer.h>
#include <soc/rtc.h>
#if ESP_IDF_VERSION_MAJOR >= 4

View File

@@ -9,8 +9,9 @@ CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id"
CONF_IO_CAPABILITY = "io_capability"
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
@@ -19,17 +20,28 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
IoCapability = esp32_ble_ns.enum("IoCapability")
IO_CAPABILITY = {
"none": IoCapability.IO_CAP_NONE,
"keyboard_only": IoCapability.IO_CAP_IN,
"keyboard_display": IoCapability.IO_CAP_KBDISP,
"display_only": IoCapability.IO_CAP_OUT,
"display_yes_no": IoCapability.IO_CAP_IO,
}
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
}
).extend(cv.COMPONENT_SCHEMA)
def validate_variant(_):
variant = get_esp32_variant()
if variant in NO_BLUTOOTH_VARIANTS:
if variant in NO_BLUETOOTH_VARIANTS:
raise cv.Invalid(f"{variant} does not support Bluetooth")
@@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)

View File

@@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() {
return false;
}
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
return false;
@@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
void ESP32BLE::dump_config() {
const uint8_t *mac_address = esp_bt_dev_get_address();
if (mac_address) {
const char *io_capability_s;
switch (this->io_cap_) {
case ESP_IO_CAP_OUT:
io_capability_s = "display_only";
break;
case ESP_IO_CAP_IO:
io_capability_s = "display_yes_no";
break;
case ESP_IO_CAP_IN:
io_capability_s = "keyboard_only";
break;
case ESP_IO_CAP_NONE:
io_capability_s = "none";
break;
case ESP_IO_CAP_KBDISP:
io_capability_s = "keyboard_display";
break;
default:
io_capability_s = "invalid";
break;
}
ESP_LOGCONFIG(TAG, "ESP32 BLE:");
ESP_LOGCONFIG(TAG, " MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
ESP_LOGCONFIG(TAG, " IO Capability: %s", io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
}

View File

@@ -25,6 +25,14 @@ typedef struct {
uint16_t mtu;
} conn_status_t;
enum IoCapability {
IO_CAP_OUT = ESP_IO_CAP_OUT,
IO_CAP_IO = ESP_IO_CAP_IO,
IO_CAP_IN = ESP_IO_CAP_IN,
IO_CAP_NONE = ESP_IO_CAP_NONE,
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
};
class GAPEventHandler {
public:
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
@@ -44,6 +52,8 @@ class GATTsEventHandler {
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void setup() override;
void loop() override;
void dump_config() override;
@@ -72,6 +82,7 @@ class ESP32BLE : public Component {
Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_;
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -125,7 +125,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif8266 @ {value}"
return f"platformio/espressif8266@{value}"
except cv.Invalid:
return value
@@ -181,7 +181,7 @@ async def to_code(config):
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
[f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"],
)
# Default for platformio is LWIP2_LOW_MEMORY with:

View File

@@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; }
void EthernetComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
// Delay here to allow power to stabilise before Ethernet is initialised.
delay(300); // NOLINT
if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
// Delay here to allow power to stabilise before Ethernet is initialized.
delay(300); // NOLINT
}
esp_err_t err;
err = esp_netif_init();
@@ -52,30 +54,29 @@ void EthernetComponent::setup() {
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
esp_eth_phy_t *phy;
switch (this->type_) {
case ETHERNET_TYPE_LAN8720: {
phy = esp_eth_phy_new_lan87xx(&phy_config);
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
break;
}
case ETHERNET_TYPE_RTL8201: {
phy = esp_eth_phy_new_rtl8201(&phy_config);
this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
break;
}
case ETHERNET_TYPE_DP83848: {
phy = esp_eth_phy_new_dp83848(&phy_config);
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
break;
}
case ETHERNET_TYPE_IP101: {
phy = esp_eth_phy_new_ip101(&phy_config);
this->phy_ = esp_eth_phy_new_ip101(&phy_config);
break;
}
case ETHERNET_TYPE_JL1101: {
phy = esp_eth_phy_new_jl1101(&phy_config);
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break;
}
case ETHERNET_TYPE_KSZ8081: {
phy = esp_eth_phy_new_ksz8081(&phy_config);
this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
break;
}
default: {
@@ -84,7 +85,7 @@ void EthernetComponent::setup() {
}
}
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
this->eth_handle_ = nullptr;
err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error");
@@ -356,6 +357,21 @@ std::string EthernetComponent::get_use_address() const {
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
bool EthernetComponent::powerdown() {
ESP_LOGI(TAG, "Powering down ethernet PHY");
if (this->phy_ == nullptr) {
ESP_LOGE(TAG, "Ethernet PHY not assigned");
return false;
}
this->connected_ = false;
this->started_ = false;
if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
ESP_LOGE(TAG, "Error powering down ethernet PHY");
return false;
}
return true;
}
} // namespace ethernet
} // namespace esphome

View File

@@ -45,6 +45,7 @@ class EthernetComponent : public Component {
void dump_config() override;
float get_setup_priority() const override;
bool can_proceed() override;
void on_shutdown() override { powerdown(); }
bool is_connected();
void set_phy_addr(uint8_t phy_addr);
@@ -58,6 +59,7 @@ class EthernetComponent : public Component {
network::IPAddress get_ip_address();
std::string get_use_address() const;
void set_use_address(const std::string &use_address);
bool powerdown();
protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
@@ -82,6 +84,7 @@ class EthernetComponent : public Component {
uint32_t connect_begin_;
esp_netif_t *eth_netif_{nullptr};
esp_eth_handle_t eth_handle_;
esp_eth_phy_t *phy_{nullptr};
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -41,6 +41,7 @@ void FeedbackCover::setup() {
CoverTraits FeedbackCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_stop(true);
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_is_assumed_state(this->assumed_state_);

View File

@@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
this->enrollment_done_callback_.call(this->enrollment_slot_);
this->get_fingerprint_count_();
} else {
this->enrollment_failed_callback_.call(this->enrollment_slot_);
if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
this->enrollment_failed_callback_.call(this->enrollment_slot_);
}
}
this->enrollment_image_ = 0;
this->enrollment_slot_ = 0;
this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
if (this->enrolling_binary_sensor_ != nullptr) {
this->enrolling_binary_sensor_->publish_state(false);
}

View File

@@ -13,6 +13,8 @@ namespace fingerprint_grow {
static const uint16_t START_CODE = 0xEF01;
static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF;
enum GrowPacketType {
COMMAND = 0x01,
DATA = 0x02,
@@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
uint32_t new_password_ = -1;
GPIOPin *sensing_pin_{nullptr};
uint8_t enrollment_image_ = 0;
uint16_t enrollment_slot_ = 0;
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
uint8_t enrollment_buffers_ = 5;
bool waiting_removal_ = false;
uint32_t last_aura_led_control_ = 0;

View File

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

View File

@@ -0,0 +1,52 @@
#include "hyt271.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace hyt271 {
static const char *const TAG = "hyt271";
static const uint8_t HYT271_ADDRESS = 0x28;
void HYT271Component::dump_config() {
ESP_LOGCONFIG(TAG, "HYT271:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
}
void HYT271Component::update() {
uint8_t raw_data[4];
if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values");
return;
}
this->set_timeout("wait_convert", 50, [this]() {
uint8_t raw_data[4];
if (this->read(raw_data, 4) != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values");
return;
}
uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2;
uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1];
float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f;
float humidity = (float(raw_humidity)) * (100.0f / 16383.0f);
ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity);
if (this->temperature_ != nullptr)
this->temperature_->publish_state(temperature);
if (this->humidity_ != nullptr)
this->humidity_->publish_state(humidity);
this->status_clear_warning();
});
}
float HYT271Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace hyt271
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace hyt271 {
class HYT271Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void dump_config() override;
/// Update the sensor values (temperature+humidity).
void update() override;
float get_setup_priority() const override;
protected:
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
};
} // namespace hyt271
} // namespace esphome

View File

@@ -0,0 +1,56 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
DEPENDENCIES = ["i2c"]
hyt271_ns = cg.esphome_ns.namespace("hyt271")
HYT271Component = hyt271_ns.class_(
"HYT271Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HYT271Component),
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x28))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))

View File

@@ -3,6 +3,7 @@
#include "i2c_bus_arduino.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <Arduino.h>
#include <cstring>
@@ -227,10 +228,14 @@ void ArduinoI2CBus::recover_() {
// When SCL is kept LOW at this point, we might be looking at a device
// that applies clock stretching. Wait for the release of the SCL line,
// but not forever. There is no specification for the maximum allowed
// time. We'll stick to 500ms here.
auto wait = 20;
// time. We yield and reset the WDT, so as to avoid triggering reset.
// No point in trying to recover the bus by forcing a uC reset. Bus
// should recover in a few ms or less else not likely to recovery at
// all.
auto wait = 250;
while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT
delay(25);
App.feed_wdt();
delayMicroseconds(half_period_usec * 2);
}
if (digitalRead(scl_pin_) == LOW) { // NOLINT
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");

View File

@@ -4,6 +4,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <cstring>
#include <cinttypes>
@@ -273,10 +274,14 @@ void IDFI2CBus::recover_() {
// When SCL is kept LOW at this point, we might be looking at a device
// that applies clock stretching. Wait for the release of the SCL line,
// but not forever. There is no specification for the maximum allowed
// time. We'll stick to 500ms here.
auto wait = 20;
// time. We yield and reset the WDT, so as to avoid triggering reset.
// No point in trying to recover the bus by forcing a uC reset. Bus
// should recover in a few ms or less else not likely to recovery at
// all.
auto wait = 250;
while (wait-- && gpio_get_level(scl_pin) == 0) {
delay(25);
App.feed_wdt();
delayMicroseconds(half_period_usec * 2);
}
if (gpio_get_level(scl_pin) == 0) {
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");

View File

@@ -0,0 +1,148 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_NUMBER,
CONF_MODE,
CONF_INVERTED,
CONF_INPUT,
CONF_OUTPUT,
CONF_PULLUP,
)
CODEOWNERS = ["@looping40"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
CONF_BRIGHTNESS_MODE = "brightness_mode"
CONF_BRIGHTNESS_GLOBAL = "brightness_global"
max6956_ns = cg.esphome_ns.namespace("max6956")
MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice)
MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin)
# Actions
SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action)
SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action)
MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE")
CURRENT_MODES = {
"global": MAX6956_CURRENTMODE.GLOBAL,
"segment": MAX6956_CURRENTMODE.SEGMENT,
}
CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(MAX6956),
cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range(
min=0, max=15
),
cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum(
CURRENT_MODES, lower=True
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x40))
)
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)
cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE]))
cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL]))
def validate_mode(value):
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
raise cv.Invalid("Mode must be either input or output")
if value[CONF_INPUT] and value[CONF_OUTPUT]:
raise cv.Invalid("Mode must be either input or output")
if value[CONF_PULLUP] and not value[CONF_INPUT]:
raise cv.Invalid("Pullup only available with input")
return value
CONF_MAX6956 = "max6956"
MAX6956_PIN_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(MAX6956GPIOPin),
cv.Required(CONF_MAX6956): cv.use_id(MAX6956),
cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
},
validate_mode,
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA)
async def max6956_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_MAX6956])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var
@automation.register_action(
"max6956.set_brightness_global",
SetCurrentGlobalAction,
cv.maybe_simple_value(
{
cv.GenerateID(CONF_ID): cv.use_id(MAX6956),
cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable(
cv.int_range(min=0, max=15)
),
},
key=CONF_BRIGHTNESS_GLOBAL,
),
)
async def max6956_set_brightness_global_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float)
cg.add(var.set_brightness_global(template_))
return var
@automation.register_action(
"max6956.set_brightness_mode",
SetCurrentModeAction,
cv.maybe_simple_value(
{
cv.Required(CONF_ID): cv.use_id(MAX6956),
cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable(
cv.enum(CURRENT_MODES, lower=True)
),
},
key=CONF_BRIGHTNESS_MODE,
),
)
async def max6956_set_brightness_mode_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float)
cg.add(var.set_brightness_mode(template_))
return var

View File

@@ -0,0 +1,40 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/max6956/max6956.h"
namespace esphome {
namespace max6956 {
template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> {
public:
SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {}
TEMPLATABLE_VALUE(uint8_t, brightness_global)
void play(Ts... x) override {
this->max6956_->set_brightness_global(this->brightness_global_.value(x...));
this->max6956_->write_brightness_global();
}
protected:
MAX6956 *max6956_;
};
template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> {
public:
SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {}
TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode)
void play(Ts... x) override {
this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...));
this->max6956_->write_brightness_mode();
}
protected:
MAX6956 *max6956_;
};
} // namespace max6956
} // namespace esphome

View File

@@ -0,0 +1,170 @@
#include "max6956.h"
#include "esphome/core/log.h"
namespace esphome {
namespace max6956 {
static const char *const TAG = "max6956";
/// Masks for MAX6956 Configuration register
const uint32_t MASK_TRANSITION_DETECTION = 0x80;
const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40;
const uint32_t MASK_NORMAL_OPERATION = 0x01;
const uint32_t MASK_1PORT_VALUE = 0x03;
const uint32_t MASK_PORT_CONFIG = 0x03;
const uint8_t MASK_CONFIG_CURRENT = 0x40;
const uint8_t MASK_CURRENT_PIN = 0x0F;
/**************************************
* MAX6956 *
**************************************/
void MAX6956::setup() {
ESP_LOGCONFIG(TAG, "Setting up MAX6956...");
uint8_t configuration;
if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) {
this->mark_failed();
return;
}
write_brightness_global();
write_brightness_mode();
/** TO DO : read transition detection in yaml
TO DO : read indivdual current in yaml **/
this->read_reg_(MAX6956_CONFIGURATION, &configuration);
ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
configuration = configuration | MASK_NORMAL_OPERATION;
this->write_reg_(MAX6956_CONFIGURATION, configuration);
ESP_LOGCONFIG(TAG, "Enabling normal operation");
ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
}
bool MAX6956::digital_read(uint8_t pin) {
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
uint8_t value = 0;
this->read_reg_(reg_addr, &value);
return (value & MASK_1PORT_VALUE);
}
void MAX6956::digital_write(uint8_t pin, bool value) {
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
this->write_reg_(reg_addr, value);
}
void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) {
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
uint8_t config = 0;
uint8_t shift = 2 * (pin % 4);
MAX6956GPIOMode mode = MAX6956_INPUT;
if (flags == gpio::FLAG_INPUT) {
mode = MAX6956GPIOMode::MAX6956_INPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP;
} else if (flags == gpio::FLAG_OUTPUT) {
mode = MAX6956GPIOMode::MAX6956_OUTPUT;
}
this->read_reg_(reg_addr, &config);
config &= ~(MASK_PORT_CONFIG << shift);
config |= (mode << shift);
this->write_reg_(reg_addr, config);
}
void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) {
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
uint8_t config = 0;
uint8_t shift = 2 * (pin % 4);
MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED;
if (flags == max6956::FLAG_LED) {
mode = MAX6956GPIOMode::MAX6956_LED;
}
this->read_reg_(reg_addr, &config);
config &= ~(MASK_PORT_CONFIG << shift);
config |= (mode << shift);
this->write_reg_(reg_addr, config);
}
void MAX6956::set_brightness_global(uint8_t current) {
if (current > 15) {
ESP_LOGE(TAG, "Global brightness out off range (%u)", current);
return;
}
global_brightness_ = current;
}
void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); }
void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; };
void MAX6956::write_brightness_mode() {
uint8_t reg_addr = MAX6956_CONFIGURATION;
uint8_t config = 0;
this->read_reg_(reg_addr, &config);
config &= ~MASK_CONFIG_CURRENT;
config |= brightness_mode_ << 6;
this->write_reg_(reg_addr, config);
}
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
uint8_t config = 0;
uint8_t shift = 4 * (pin % 2);
uint8_t bright = roundf(brightness * 15);
if (prev_bright_[pin - MAX6956_MIN] == bright)
return;
prev_bright_[pin - MAX6956_MIN] = bright;
this->read_reg_(reg_addr, &config);
config &= ~(MASK_CURRENT_PIN << shift);
config |= (bright << shift);
this->write_reg_(reg_addr, config);
}
bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) {
if (this->is_failed())
return false;
return this->read_byte(reg, value);
}
bool MAX6956::write_reg_(uint8_t reg, uint8_t value) {
if (this->is_failed())
return false;
return this->write_byte(reg, value);
}
void MAX6956::dump_config() {
ESP_LOGCONFIG(TAG, "MAX6956");
if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) {
ESP_LOGCONFIG(TAG, "current mode: global");
ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_);
} else {
ESP_LOGCONFIG(TAG, "current mode: segment");
}
}
/**************************************
* MAX6956GPIOPin *
**************************************/
void MAX6956GPIOPin::setup() { pin_mode(flags_); }
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
std::string MAX6956GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
return buffer;
}
} // namespace max6956
} // namespace esphome

View File

@@ -0,0 +1,94 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace max6956 {
/// Modes for MAX6956 pins
enum MAX6956GPIOMode : uint8_t {
MAX6956_LED = 0x00,
MAX6956_OUTPUT = 0x01,
MAX6956_INPUT = 0x02,
MAX6956_INPUT_PULLUP = 0x03
};
/// Range for MAX6956 pins
enum MAX6956GPIORange : uint8_t {
MAX6956_MIN = 4,
MAX6956_MAX = 31,
};
enum MAX6956GPIORegisters {
MAX6956_GLOBAL_CURRENT = 0x02,
MAX6956_CONFIGURATION = 0x04,
MAX6956_TRANSITION_DETECT_MASK = 0x06,
MAX6956_DISPLAY_TEST = 0x07,
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
MAX6956_CURRENT_START = 0x12, // Current054
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 411 (data bits D0D7)
};
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
class MAX6956 : public Component, public i2c::I2CDevice {
public:
MAX6956() = default;
void setup() override;
bool digital_read(uint8_t pin);
void digital_write(uint8_t pin, bool value);
void pin_mode(uint8_t pin, gpio::Flags flags);
void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_brightness_global(uint8_t current);
void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode);
void set_pin_brightness(uint8_t pin, float brightness);
void dump_config() override;
void write_brightness_global();
void write_brightness_mode();
protected:
// read a given register
bool read_reg_(uint8_t reg, uint8_t *value);
// write a value to a given register
bool write_reg_(uint8_t reg, uint8_t value);
max6956::MAX6956CURRENTMODE brightness_mode_;
uint8_t global_brightness_;
private:
int8_t prev_bright_[28] = {0};
};
class MAX6956GPIOPin : public GPIOPin {
public:
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void set_parent(MAX6956 *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
protected:
MAX6956 *parent_;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
} // namespace max6956
} // namespace esphome

View File

@@ -0,0 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output
from esphome.const import CONF_PIN, CONF_ID
from .. import MAX6956, max6956_ns, CONF_MAX6956
DEPENDENCIES = ["max6956"]
MAX6956LedChannel = max6956_ns.class_(
"MAX6956LedChannel", output.FloatOutput, cg.Component
)
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel),
cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956),
cv.Required(CONF_PIN): cv.int_range(min=4, max=31),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
parent = await cg.get_variable(config[CONF_MAX6956])
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await output.register_output(var, config)
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_parent(parent))

View File

@@ -0,0 +1,26 @@
#include "max6956_led_output.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace max6956 {
static const char *const TAG = "max6956_led_channel";
void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); }
void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); }
void MAX6956LedChannel::setup() {
this->parent_->pin_mode(this->pin_, max6956::FLAG_LED);
this->turn_off();
}
void MAX6956LedChannel::dump_config() {
ESP_LOGCONFIG(TAG, "MAX6956 current:");
ESP_LOGCONFIG(TAG, " MAX6956 pin: %d", this->pin_);
LOG_FLOAT_OUTPUT(this);
}
} // namespace max6956
} // namespace esphome

View File

@@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/max6956/max6956.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
namespace max6956 {
class MAX6956;
class MAX6956LedChannel : public output::FloatOutput, public Component {
public:
void set_parent(MAX6956 *parent) { this->parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
void write_state(float state) override;
void write_state(bool state) override;
MAX6956 *parent_;
uint8_t pin_;
};
} // namespace max6956
} // namespace esphome

View File

@@ -93,7 +93,7 @@ async def to_code(config):
cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
cg.add(var.set_flip_x([CONF_FLIP_X]))
cg.add(var.set_flip_x(config[CONF_FLIP_X]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(

View File

@@ -4,10 +4,13 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_SERVICES,
CONF_SERVICE,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
)
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority
from esphome.components.esp32 import add_idf_component
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
@@ -79,6 +82,16 @@ async def to_code(config):
elif CORE.is_rp2040:
cg.add_library("LEAmDNS", None)
if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(
5, 0, 0
):
add_idf_component(
"mdns",
"https://github.com/espressif/esp-protocols.git",
"mdns-v1.0.9",
"components/mdns",
)
if config[CONF_DISABLED]:
return

View File

View File

@@ -0,0 +1,122 @@
#include "mlx90614.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mlx90614 {
static const uint8_t MLX90614_RAW_IR_1 = 0x04;
static const uint8_t MLX90614_RAW_IR_2 = 0x05;
static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06;
static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07;
static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08;
static const uint8_t MLX90614_TOMAX = 0x20;
static const uint8_t MLX90614_TOMIN = 0x21;
static const uint8_t MLX90614_PWMCTRL = 0x22;
static const uint8_t MLX90614_TARANGE = 0x23;
static const uint8_t MLX90614_EMISSIVITY = 0x24;
static const uint8_t MLX90614_CONFIG = 0x25;
static const uint8_t MLX90614_ADDR = 0x2E;
static const uint8_t MLX90614_ID1 = 0x3C;
static const uint8_t MLX90614_ID2 = 0x3D;
static const uint8_t MLX90614_ID3 = 0x3E;
static const uint8_t MLX90614_ID4 = 0x3F;
static const char *const TAG = "mlx90614";
void MLX90614Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MLX90614...");
if (!this->write_emissivity_()) {
ESP_LOGE(TAG, "Communication with MLX90614 failed!");
this->mark_failed();
return;
}
}
bool MLX90614Component::write_emissivity_() {
if (std::isnan(this->emissivity_))
return true;
uint16_t value = (uint16_t) (this->emissivity_ * 65535);
if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) {
return false;
}
delay(10);
if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) {
return false;
}
delay(10);
return true;
}
uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
uint8_t in = data[i];
for (uint8_t j = 0; j < 8; j++) {
bool carry = (crc ^ in) & 0x80;
crc <<= 1;
if (carry)
crc ^= 0x07;
in <<= 1;
}
}
return crc;
}
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
uint8_t buf[5];
buf[0] = this->address_ << 1;
buf[1] = reg;
buf[2] = data & 0xFF;
buf[3] = data >> 8;
buf[4] = this->crc8_pec_(buf, 4);
return this->write_bytes(reg, buf + 2, 3);
}
void MLX90614Component::dump_config() {
ESP_LOGCONFIG(TAG, "MLX90614:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with MLX90614 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Ambient", this->ambient_sensor_);
LOG_SENSOR(" ", "Object", this->object_sensor_);
}
float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; }
void MLX90614Component::update() {
uint8_t emissivity[3];
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
uint8_t raw_object[3];
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
uint8_t raw_ambient[3];
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f;
float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f;
ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient);
if (this->ambient_sensor_ != nullptr && !std::isnan(ambient))
this->ambient_sensor_->publish_state(ambient);
if (this->object_sensor_ != nullptr && !std::isnan(object))
this->object_sensor_->publish_state(object);
this->status_clear_warning();
}
} // namespace mlx90614
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace mlx90614 {
class MLX90614Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
void set_ambient_sensor(sensor::Sensor *ambient_sensor) { ambient_sensor_ = ambient_sensor; }
void set_object_sensor(sensor::Sensor *object_sensor) { object_sensor_ = object_sensor; }
void set_emissivity(float emissivity) { emissivity_ = emissivity; }
protected:
bool write_emissivity_();
uint8_t crc8_pec_(const uint8_t *data, uint8_t len);
bool write_bytes_(uint8_t reg, uint16_t data);
sensor::Sensor *ambient_sensor_{nullptr};
sensor::Sensor *object_sensor_{nullptr};
float emissivity_{NAN};
};
} // namespace mlx90614
} // namespace esphome

View File

@@ -0,0 +1,63 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
CONF_AMBIENT = "ambient"
CONF_EMISSIVITY = "emissivity"
CONF_OBJECT = "object"
mlx90614_ns = cg.esphome_ns.namespace("mlx90614")
MLX90614Component = mlx90614_ns.class_(
"MLX90614Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MLX90614Component),
cv.Optional(CONF_AMBIENT): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_OBJECT): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_EMISSIVITY, default=1.0): cv.percentage,
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x5A))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_AMBIENT in config:
sens = await sensor.new_sensor(config[CONF_AMBIENT])
cg.add(var.set_ambient_sensor(sens))
if CONF_OBJECT in config:
sens = await sensor.new_sensor(config[CONF_OBJECT])
cg.add(var.set_object_sensor(sens))
cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY]))

View File

@@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
JsonArray presets = root.createNestedArray("preset_modes");
if (traits.supports_preset(CLIMATE_PRESET_HOME))
presets.add("home");
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
// away_mode_command_topic
root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
// away_mode_state_topic
root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
if (traits.supports_preset(CLIMATE_PRESET_AWAY))
presets.add("away");
}
if (traits.supports_preset(CLIMATE_PRESET_BOOST))
presets.add("boost");
if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
@@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() {
});
}
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto onoff = parse_on_off(payload.c_str());
auto call = this->device_->make_call();
switch (onoff) {
case PARSE_ON:
call.set_preset(CLIMATE_PRESET_AWAY);
break;
case PARSE_OFF:
call.set_preset(CLIMATE_PRESET_HOME);
break;
case PARSE_TOGGLE:
call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
break;
case PARSE_NONE:
default:
ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str());
return;
}
call.perform();
});
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto call = this->device_->make_call();
@@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() {
success = false;
}
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
if (!this->publish(this->get_away_state_topic(), payload))
success = false;
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
std::string payload;
if (this->device_->preset.has_value()) {

View File

@@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() {
return "";
}
void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string NumberTraits::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace number
} // namespace esphome

View File

@@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
@@ -11,7 +12,7 @@ enum NumberMode : uint8_t {
NUMBER_MODE_SLIDER = 2,
};
class NumberTraits {
class NumberTraits : public EntityBase_DeviceClass {
public:
// Set/get the number value boundaries.
void set_min_value(float min_value) { min_value_ = min_value; }
@@ -32,17 +33,12 @@ class NumberTraits {
void set_mode(NumberMode mode) { this->mode_ = mode; }
NumberMode get_mode() const { return this->mode_; }
// Set/get the device class.
void set_device_class(const std::string &device_class);
std::string get_device_class();
protected:
float min_value_ = NAN;
float max_value_ = NAN;
float step_ = NAN;
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
NumberMode mode_{NUMBER_MODE_AUTO};
optional<std::string> device_class_;
};
} // namespace number

View File

@@ -8,6 +8,10 @@
#include <esp_ota_ops.h>
#include "esphome/components/md5/md5.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include <spi_flash_mmap.h>
#endif
namespace esphome {
namespace ota {
@@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
if (this->partition_ == nullptr) {
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
}
esp_task_wdt_init(15, false); // The following function takes longer than the 5 seconds timeout of WDT
// The following function takes longer than the 5 seconds timeout of WDT
#if ESP_IDF_VERSION_MAJOR >= 5
esp_task_wdt_config_t wdtc;
wdtc.timeout_ms = 15000;
wdtc.idle_core_mask = 0;
wdtc.trigger_panic = false;
esp_task_wdt_reconfigure(&wdtc);
#else
esp_task_wdt_init(15, false);
#endif
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); // Set the WDT back to the configured timeout
// Set the WDT back to the configured timeout
#if ESP_IDF_VERSION_MAJOR >= 5
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S;
esp_task_wdt_reconfigure(&wdtc);
#else
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
#endif
if (err != ESP_OK) {
esp_ota_abort(this->update_handle_);
this->update_handle_ = 0;

View File

@@ -0,0 +1,78 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_NUMBER,
CONF_MODE,
CONF_INVERTED,
CONF_OUTPUT,
CONF_PULLUP,
)
CODEOWNERS = ["@Mat931"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice)
PCA6416AGPIOPin = pca6416a_ns.class_(
"PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent)
)
CONF_PCA6416A = "pca6416a"
CONFIG_SCHEMA = (
cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)})
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x21))
)
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)
def validate_mode(value):
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
raise cv.Invalid("Mode must be either input or output")
if value[CONF_INPUT] and value[CONF_OUTPUT]:
raise cv.Invalid("Mode must be either input or output")
if value[CONF_PULLUP] and not value[CONF_INPUT]:
raise cv.Invalid("Pullup only available with input")
return value
PCA6416A_PIN_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin),
cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
},
validate_mode,
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)
@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA)
async def pca6416a_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_PCA6416A])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -0,0 +1,174 @@
#include "pca6416a.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pca6416a {
enum PCA6416AGPIORegisters {
// 0 side
PCA6416A_INPUT0 = 0x00,
PCA6416A_OUTPUT0 = 0x02,
PCA6416A_INVERT0 = 0x04,
PCA6416A_CONFIG0 = 0x06,
PCAL6416A_PULL_EN0 = 0x46,
PCAL6416A_PULL_DIR0 = 0x48,
// 1 side
PCA6416A_INPUT1 = 0x01,
PCA6416A_OUTPUT1 = 0x03,
PCA6416A_INVERT1 = 0x05,
PCA6416A_CONFIG1 = 0x07,
PCAL6416A_PULL_EN1 = 0x47,
PCAL6416A_PULL_DIR1 = 0x49,
};
static const char *const TAG = "pca6416a";
void PCA6416AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up PCA6416A...");
// Test to see if device exists
uint8_t value;
if (!this->read_register_(PCA6416A_INPUT0, &value)) {
ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_);
this->mark_failed();
return;
}
// Test to see if the device supports pull-up resistors
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) {
this->has_pullup_ = true;
}
// No polarity inversion
this->write_register_(PCA6416A_INVERT0, 0);
this->write_register_(PCA6416A_INVERT1, 0);
// Set all pins to input
this->write_register_(PCA6416A_CONFIG0, 0xff);
this->write_register_(PCA6416A_CONFIG1, 0xff);
// Read current output register state
this->read_register_(PCA6416A_OUTPUT0, &this->output_0_);
this->read_register_(PCA6416A_OUTPUT1, &this->output_1_);
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
}
void PCA6416AComponent::dump_config() {
if (this->has_pullup_) {
ESP_LOGCONFIG(TAG, "PCAL6416A:");
} else {
ESP_LOGCONFIG(TAG, "PCA6416A:");
}
LOG_I2C_DEVICE(this)
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with PCA6416A failed!");
}
}
bool PCA6416AComponent::digital_read(uint8_t pin) {
uint8_t bit = pin % 8;
uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
uint8_t value = 0;
this->read_register_(reg_addr, &value);
return value & (1 << bit);
}
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
this->update_register_(pin, value, reg_addr);
}
void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1;
uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1;
uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1;
if (flags == gpio::FLAG_INPUT) {
this->update_register_(pin, true, io_dir);
if (has_pullup_) {
this->update_register_(pin, true, pull_dir);
this->update_register_(pin, false, pull_en);
}
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
this->update_register_(pin, true, io_dir);
if (has_pullup_) {
this->update_register_(pin, true, pull_dir);
this->update_register_(pin, true, pull_en);
} else {
ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
}
} else if (flags == gpio::FLAG_OUTPUT) {
this->update_register_(pin, false, io_dir);
}
}
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
if (this->is_failed()) {
ESP_LOGD(TAG, "Device marked failed");
return false;
}
if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
return false;
}
this->status_clear_warning();
return true;
}
bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
if (this->is_failed()) {
ESP_LOGD(TAG, "Device marked failed");
return false;
}
if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
return false;
}
this->status_clear_warning();
return true;
}
void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
uint8_t bit = pin % 8;
uint8_t reg_value = 0;
if (reg_addr == PCA6416A_OUTPUT0) {
reg_value = this->output_0_;
} else if (reg_addr == PCA6416A_OUTPUT1) {
reg_value = this->output_1_;
} else {
this->read_register_(reg_addr, &reg_value);
}
if (pin_value) {
reg_value |= 1 << bit;
} else {
reg_value &= ~(1 << bit);
}
this->write_register_(reg_addr, reg_value);
if (reg_addr == PCA6416A_OUTPUT0) {
this->output_0_ = reg_value;
} else if (reg_addr == PCA6416A_OUTPUT1) {
this->output_1_ = reg_value;
}
}
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
void PCA6416AGPIOPin::setup() { pin_mode(flags_); }
void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
std::string PCA6416AGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_);
return buffer;
}
} // namespace pca6416a
} // namespace esphome

View File

@@ -0,0 +1,63 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace pca6416a {
class PCA6416AComponent : public Component, public i2c::I2CDevice {
public:
PCA6416AComponent() = default;
/// Check i2c availability and setup masks
void setup() override;
/// Helper function to read the value of a pin.
bool digital_read(uint8_t pin);
/// Helper function to write the value of a pin.
void digital_write(uint8_t pin, bool value);
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
protected:
bool read_register_(uint8_t reg, uint8_t *value);
bool write_register_(uint8_t reg, uint8_t value);
void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
/// The mask to write as output state - 1 means HIGH, 0 means LOW
uint8_t output_0_{0x00};
uint8_t output_1_{0x00};
/// Storage for last I2C error seen
esphome::i2c::ErrorCode last_error_;
/// Only the PCAL6416A has pull-up resistors
bool has_pullup_{false};
};
/// Helper class to expose a PCA6416A pin as an internal input GPIO pin.
class PCA6416AGPIOPin : public GPIOPin {
public:
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void set_parent(PCA6416AComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
protected:
PCA6416AComponent *parent_;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
} // namespace pca6416a
} // namespace esphome

View File

@@ -81,7 +81,32 @@ void PN532::setup() {
this->turn_off_rf_();
}
bool PN532::powerdown() {
updates_enabled_ = false;
requested_read_ = false;
ESP_LOGI(TAG, "Powering down PN532");
if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup
ESP_LOGE(TAG, "Error writing powerdown command to PN532");
return false;
}
std::vector<uint8_t> response;
if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
return false;
}
if (response[0] != 0x00) {
ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
return false;
}
ESP_LOGV(TAG, "Powerdown successful");
delay(1);
return true;
}
void PN532::update() {
if (!updates_enabled_)
return;
for (auto *obj : this->binary_sensors_)
obj->on_scan_end();

View File

@@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
class PN532BinarySensor;
@@ -30,6 +31,7 @@ class PN532 : public PollingComponent {
float get_setup_priority() const override;
void loop() override;
void on_shutdown() override { powerdown(); }
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
@@ -45,6 +47,7 @@ class PN532 : public PollingComponent {
void clean_mode();
void format_mode();
void write_mode(nfc::NdefMessage *message);
bool powerdown();
protected:
void turn_off_rf_();
@@ -79,6 +82,7 @@ class PN532 : public PollingComponent {
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_();
bool updates_enabled_{true};
bool requested_read_{false};
std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;

View File

@@ -2,7 +2,12 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation, pins
from esphome.components import i2c
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN
from esphome.const import (
CONF_ON_TAG,
CONF_ON_TAG_REMOVED,
CONF_TRIGGER_ID,
CONF_RESET_PIN,
)
CODEOWNERS = ["@glmnet"]
AUTO_LOAD = ["binary_sensor"]
@@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
}
),
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
}
),
}
).extend(cv.polling_component_schema("1s"))
@@ -37,5 +47,10 @@ async def setup_rc522(var, config):
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_trigger(trigger))
cg.add(var.register_ontag_trigger(trigger))
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
for conf in config.get(CONF_ON_TAG_REMOVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_ontagremoved_trigger(trigger))
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)

View File

@@ -256,7 +256,7 @@ void RC522::loop() {
this->current_uid_ = rfid_uid;
for (auto *trigger : this->triggers_)
for (auto *trigger : this->triggers_ontag_)
trigger->process(rfid_uid);
if (report) {
@@ -265,6 +265,11 @@ void RC522::loop() {
break;
}
case STATE_DONE: {
if (!this->current_uid_.empty()) {
ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str());
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(this->current_uid_);
}
this->current_uid_ = {};
state_ = STATE_INIT;
break;

View File

@@ -24,7 +24,8 @@ class RC522 : public PollingComponent {
void loop() override;
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); }
void register_ontag_trigger(RC522Trigger *trig) { this->triggers_ontag_.push_back(trig); }
void register_ontagremoved_trigger(RC522Trigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
@@ -242,7 +243,8 @@ class RC522 : public PollingComponent {
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
std::vector<RC522Trigger *> triggers_ontag_;
std::vector<RC522Trigger *> triggers_ontagremoved_;
std::vector<uint8_t> current_uid_;
enum RC522Error {

View File

@@ -791,6 +791,57 @@ async def raw_action(var, config, args):
cg.add(var.set_carrier_frequency(templ))
# Drayton
(
DraytonData,
DraytonBinarySensor,
DraytonTrigger,
DraytonAction,
DraytonDumper,
) = declare_protocol("Drayton")
DRAYTON_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
}
)
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
def drayton_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
DraytonData,
("address", config[CONF_ADDRESS]),
("channel", config[CONF_CHANNEL]),
("command", config[CONF_COMMAND]),
)
)
)
@register_trigger("drayton", DraytonTrigger, DraytonData)
def drayton_trigger(var, config):
pass
@register_dumper("drayton", DraytonDumper)
def drayton_dumper(var, config):
pass
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
async def drayton_action(var, config, args):
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
cg.add(var.set_address(template_))
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
cg.add(var.set_channel(template_))
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
cg.add(var.set_command(template_))
# RC5
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
RC5_SCHEMA = cv.Schema(

View File

@@ -0,0 +1,213 @@
#include "drayton_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *const TAG = "remote.drayton";
static const uint32_t BIT_TIME_US = 500;
static const uint8_t CARRIER_KHZ = 2;
static const uint8_t NBITS_PREAMBLE = 12;
static const uint8_t NBITS_SYNC = 4;
static const uint8_t NBITS_ADDRESS = 16;
static const uint8_t NBITS_CHANNEL = 5;
static const uint8_t NBITS_COMMAND = 7;
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
static const uint8_t CMD_ON = 0x41;
static const uint8_t CMD_OFF = 0x02;
/*
Drayton Protocol
Using an oscilloscope to capture the data transmitted by the Digistat two
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
has a period of 500us, a bit rate of 2000 baud.
Each packet consists of an initial 1010 pattern to set up the receiver bias.
The number of these bits seen at the receiver varies depending on the state
of the bias when the packet transmission starts. The receiver algoritmn takes
account of this.
The packet appears to be Manchester encoded, with a '10' tranmitted pair
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
begun with a '1100' syncronisation symbol which breaks this rule. Following
the sync are 28 '01' or '10' pairs.
--------------------
Boiler On Command as received:
101010101010110001101001010101101001010101010101100101010101101001011001
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
Boiler Off Command as received:
101010101010110001101001010101101001010101010101010101010110011001011001
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
--------------------
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
capture and retransmit the Digistat packets. RFLink splits each packet into an
ID, SWITCH, and CMD field.
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
--------------------
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
channel and Command values of:
On 6180832 0110000110000000 1000001 10010
address: '0x6180' channel: '0x12' command: '0x41'
Off 6180052 0110000110000000 0000010 10010
address: '0x6180' channel: '0x12' command: '0x02'
These values are slightly different to those used by RFLink (the RFLink
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
is correct. A larger data sample would help (I have only found five different
packet captures online) or definitive information from Drayton.
Splitting each packet in this way works well for me with esphome. Any
corrections or additional data samples would be gratefully received.
marshn
*/
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
uint16_t khz = CARRIER_KHZ;
dst->set_carrier_frequency(khz * 1000);
// Preamble = 101010101010
uint32_t out_data = 0x0AAA;
for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
if (out_data & mask) {
dst->mark(BIT_TIME_US);
} else {
dst->space(BIT_TIME_US);
}
}
// Sync = 1100
out_data = 0x000C;
for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
if (out_data & mask) {
dst->mark(BIT_TIME_US);
} else {
dst->space(BIT_TIME_US);
}
}
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
out_data = data.address;
out_data <<= NBITS_COMMAND;
out_data |= data.command;
out_data <<= NBITS_CHANNEL;
out_data |= data.channel;
ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
if (out_data & mask) {
dst->mark(BIT_TIME_US);
dst->space(BIT_TIME_US);
} else {
dst->space(BIT_TIME_US);
dst->mark(BIT_TIME_US);
}
}
}
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
DraytonData out{
.address = 0,
.channel = 0,
.command = 0,
};
if (src.size() < 45) {
return {};
}
ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
// If first preamble item is a space, skip it
if (src.peek_space_at_least(1)) {
src.advance(1);
}
// Look for sync pulse, after. If sucessful index points to space of sync symbol
for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
(src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
src.advance(preamble + 1);
break;
}
}
// Read data. Index points to space of sync symbol
// Extract first bit
// Checks next bit to leave index pointing correctly
uint32_t out_data = 0;
uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
out_data |= 0 << bit;
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
out_data |= 1 << bit;
} else {
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
return {};
}
// Before/after each bit is read the index points to the transition at the start of the bit period or,
// if there is no transition at the start of the bit period, then the transition in the middle of
// the previous bit period.
while (--bit >= 1) {
ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
(src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
out_data |= 0 << bit;
} else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
out_data |= 1 << bit;
} else {
ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
return {};
}
}
if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
out_data |= 0;
} else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
out_data |= 1;
}
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
out.channel = (uint8_t) (out_data & 0x1F);
out_data >>= NBITS_CHANNEL;
out.command = (uint8_t) (out_data & 0x7F);
out_data >>= NBITS_COMMAND;
out.address = (uint16_t) (out_data & 0xFFFF);
return out;
}
void DraytonProtocol::dump(const DraytonData &data) {
ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
((data.address << 1) & 0xffff), data.channel, data.command);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,44 @@
#pragma once
#include "esphome/core/component.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct DraytonData {
uint16_t address;
uint8_t channel;
uint8_t command;
bool operator==(const DraytonData &rhs) const {
return address == rhs.address && channel == rhs.channel && command == rhs.command;
}
};
class DraytonProtocol : public RemoteProtocol<DraytonData> {
public:
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
optional<DraytonData> decode(RemoteReceiveData src) override;
void dump(const DraytonData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Drayton)
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint16_t, address)
TEMPLATABLE_VALUE(uint8_t, channel)
TEMPLATABLE_VALUE(uint8_t, command)
void encode(RemoteTransmitData *dst, Ts... x) override {
DraytonData data{};
data.address = this->address_.value(x...);
data.channel = this->channel_.value(x...);
data.command = this->command_.value(x...);
DraytonProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -102,7 +102,7 @@ def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/raspberrypi @ {value}"
return f"platformio/raspberrypi@{value}"
except cv.Invalid:
return value
@@ -148,7 +148,7 @@ async def to_code(config):
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
[f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.core", "earlephilhower")

View File

@@ -25,10 +25,12 @@ from esphome.const import (
CONF_STATE_CLASS,
CONF_TO,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT,
CONF_WINDOW_SIZE,
CONF_MQTT_ID,
CONF_FORCE_UPDATE,
CONF_VALUE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, lambda_)
DELTA_SCHEMA = cv.Schema(
{
cv.Required(CONF_VALUE): cv.positive_float,
cv.Optional(CONF_TYPE, default="absolute"): cv.one_of(
"absolute", "percentage", lower=True
),
}
)
def validate_delta(config):
try:
return (cv.positive_float(config), False)
value = cv.positive_float(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"})
except cv.Invalid:
pass
try:
return (cv.percentage(config), True)
value = cv.percentage(config)
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"})
except cv.Invalid:
pass
raise cv.Invalid("Delta filter requires a positive number or percentage value.")
@FILTER_REGISTRY.register("delta", DeltaFilter, validate_delta)
@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta))
async def delta_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, *config)
percentage = config[CONF_TYPE] == "percentage"
return cg.new_Pvariable(
filter_id,
config[CONF_VALUE],
percentage,
)
@FILTER_REGISTRY.register("or", OrFilter, validate_filters)

View File

@@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() {
}
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
std::string Sensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
StateClass Sensor::get_state_class() {
if (this->state_class_.has_value())

View File

@@ -54,7 +54,7 @@ std::string state_class_to_string(StateClass state_class);
*
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
*/
class Sensor : public EntityBase {
class Sensor : public EntityBase, public EntityBase_DeviceClass {
public:
explicit Sensor();
@@ -68,11 +68,6 @@ class Sensor : public EntityBase {
/// Manually set the accuracy in decimals.
void set_accuracy_decimals(int8_t accuracy_decimals);
/// Get the device class, using the manual override if set.
std::string get_device_class();
/// Manually set the device class.
void set_device_class(const std::string &device_class);
/// Get the state class, using the manual override if set.
StateClass get_state_class();
/// Manually set the state class.
@@ -165,7 +160,6 @@ class Sensor : public EntityBase {
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
optional<std::string> device_class_; ///< Device class override
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
bool force_update_{false}; ///< Force update mode
bool has_state_{false};

View File

@@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
switch.switch_schema(
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
SprinklerControllerSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="RESTORE_DEFAULT_OFF",
),
key=CONF_NAME,
),
@@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
cv.Optional(CONF_NAME): cv.string,
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
switch.switch_schema(
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
SprinklerControllerSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="RESTORE_DEFAULT_OFF",
),
key=CONF_NAME,
),
@@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
),
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
switch.switch_schema(
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
SprinklerControllerSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="RESTORE_DEFAULT_OFF",
),
key=CONF_NAME,
),
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
switch.switch_schema(
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
SprinklerControllerSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="RESTORE_DEFAULT_OFF",
),
key=CONF_NAME,
),
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
switch.switch_schema(
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
SprinklerControllerSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="RESTORE_DEFAULT_OFF",
),
key=CONF_NAME,
),

View File

@@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() {
return nullopt;
}
bool Sprinkler::any_controller_is_active() {
if (this->state_ != IDLE) {
return true;
}
for (auto &controller : this->other_controllers_) {
if (controller != this) { // dummy check
if (controller->controller_state() != IDLE) {
return true;
}
}
}
return false;
}
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
if (this->is_a_valid_valve(valve_number)) {
return this->valve_[valve_number].controller_switch;

View File

@@ -406,6 +406,12 @@ class Sprinkler : public Component {
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
optional<uint32_t> time_remaining_current_operation();
/// returns true if this or any sprinkler controller this controller knows about is active
bool any_controller_is_active();
/// returns the current state of the sprinkler controller
SprinklerState controller_state() { return this->state_; };
/// returns a pointer to a valve's control switch object
SprinklerControllerSwitch *control_switch(size_t valve_number);
@@ -503,7 +509,6 @@ class Sprinkler : public Component {
/// callback functions for timers
void valve_selection_callback_();
void sm_timer_callback_();
void pump_stop_delay_callback_();
/// Maximum allowed queue size
const uint8_t max_queue_size_{100};

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