Compare commits

...

285 Commits

Author SHA1 Message Date
feedc0de 57263bb65a goe_charger cleanups and improvements 2021-09-15 21:25:23 +02:00
feedc0de 86533e3599 Added goe_charger 2021-09-13 12:27:15 +02:00
Raman Gupta 37d75e8a03 Allow multiple template.select platform entries (#55908) 2021-09-07 14:50:07 -07:00
Marc Mueller 42f586c585 Fix upnp add_entities (#55904)
* Fix upnp add_entities

* Remove nesting level
2021-09-07 22:27:03 +02:00
Diogo Gomes d705b35ea1 Address comment in integration Riemann sum PR #55875 (#55895)
* https://github.com/home-assistant/core/pull/55875\#discussion_r703334504

* missing test update
2021-09-07 19:40:20 +02:00
Ville Skyttä 0684f8bddf Add date device class (#55887)
* Add date device class

https://github.com/home-assistant/architecture/discussions/610

* Add date device class to sensors device classes list
2021-09-07 16:46:12 +02:00
RDFurman 3aed58f825 Try to avoid rate limiting in honeywell (#55304)
* Limit parallel update and sleep loop

* Use asyncio sleep instead

* Extract sleep to const for testing

* Make loop sleep 0 in test
2021-09-07 16:32:26 +02:00
Maciej Bieniek 86247c93fc Fix available property for Xiaomi Miio fan platform (#55889)
* Fix available

* Suggested change
2021-09-07 11:34:41 +02:00
cnico 53ea24ec15 Add Flipr binary sensor (#53525) 2021-09-07 09:52:42 +02:00
Michael 2f3a11f930 Rewrite re-auth mechanism in Synology DSM integration (#54298) 2021-09-07 09:10:50 +02:00
Erik Montnemery 0d1412ea17 Set state class to total for net utility_meter sensors (#55877)
* Set state class to total for net utility_meter sensors

* Update tests
2021-09-07 08:13:14 +02:00
Diogo Gomes 1ca9deb520 Integration Sensor Initial State (#55875)
* initial state is UNAVAILABLE

* update tests
2021-09-07 08:12:54 +02:00
Sean Vig 789f21c427 Fix assignment of amcrest camera model (#55266) 2021-09-07 04:52:45 +02:00
GitHub Action 9da3fa5d75 [ci skip] Translation update 2021-09-07 00:11:29 +00:00
Paulus Schoutsen 93083513b4 Bump hass-nabucasa 49 (#55823) 2021-09-06 16:05:33 -07:00
Alexei Chetroi c6888e4faf Refactor ZHA tests (#55844)
* Replace ZHA tests FakeDevice

* Refactor ZHA tests to use zigpy devices and endpoints

* Use common consts for zigpy device mocks

Use the same dict key names for device signature mocks as zha quirks.

* Use const for test device list

* Update tests/components/zha/common.py
2021-09-06 19:00:06 -04:00
Erik Montnemery b1dbdec2ea Set state class to total for Integration sensors (#55872) 2021-09-07 00:27:31 +02:00
puddly 6895081595 Use async_update_entry in config unit test instead of modifying data (#55855) 2021-09-06 16:57:59 -04:00
Diogo Gomes 34d54511e8 Integration Sensor unit of measurement overwrite (#55869) 2021-09-06 13:41:01 -07:00
jan iversen 8d4aac618d Allow same IP if ports are different on modbus (#55766) 2021-09-06 13:40:15 -07:00
Tatham Oddie 4fa9871080 Fix logbook entity_matches_only query mode (#55761)
The string matching template needs to match the same compact JSON format
as the data is now written in.
2021-09-06 13:39:39 -07:00
Martin Hjelmare b088ce601c Bump zwave-js-server-python to 0.30.0 (#55831) 2021-09-06 13:37:12 -07:00
Daniel Hjelseth Høyer bcfedeb797 Surepetcare, bug fix (#55842) 2021-09-06 13:36:45 -07:00
Maciej Bieniek 9093819671 Fix target humidity step for Xiaomi MJJSQ humidifiers (#55858) 2021-09-06 13:36:18 -07:00
jan iversen cac3e1acfa Allow same address different register types in modbus (#55767) 2021-09-06 13:35:40 -07:00
J. Nick Koston eba9b61011 Fix exception during rediscovery of ignored zha config entries (#55859)
Fixes #55709
2021-09-06 13:35:24 -07:00
Joshi 0533a9c714 Fix switch name attribute for thinkingcleaner (#55730) 2021-09-06 14:03:46 -05:00
Brandon Rothweiler 12b1f87b35 Upgrade pymazda to 0.2.1 (#55820) 2021-09-06 13:53:03 -05:00
Simone Chemelli 8b6d0ca13f Replace util.get_local_ip in favor of components.network.async_get_source_ip() - part 2 (#53368)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-09-06 08:44:38 -10:00
Erik Montnemery dd7dea9a3f Make scapy imports in DHCP local (#55647) 2021-09-06 10:10:27 -07:00
Erik Montnemery b99a22cd4d Re-add state_class total to sensor (#55103)
* Re-add state_class total to sensor

* Make energy cost sensor enforce state_class total_increasing

* Bump deprecation of last_reset for state_class measurement

* Correct rebase mistakes
2021-09-06 18:28:58 +02:00
starkillerOG 2634949999 Add motion_blinds VerticalBlind and cleanup (#55774) 2021-09-06 16:19:02 +02:00
puddly e671ad41ec Replace zigpy-cc with zigpy-znp (#55828)
* Replace zigpy-cc with zigpy-znp in a ZHA config migration

* Fix failing unit tests
2021-09-06 09:50:54 -04:00
Maciej Bieniek 9ee0d8fefe Fix xiaomi miio Air Quality Monitor initialization (#55773) 2021-09-06 15:30:03 +02:00
mrwhite31 e6a29b6a2a Fix typo in in rfxtrx Barometer sensor (#55839)
Fix typo in sensor.py to fix barometer unavailability
2021-09-06 15:11:12 +02:00
Stefan Agner df928c80b8 Shutdown the container on abnormal signals (#55660)
So far the finish script exits whenever the service terminated by a
signal (indicated by 256 as first argument). This is the intended
behavior when SIGTERM is being sent: SIGTERM is used on regular shutdown
through the supervisor. We don't want the finish script to shutdown
itself while being taken down by the supervisor already.

However, every other signal which lead to a process exit likely means
trouble: SIGSEGV, SIGILL, etc. In those cases we want the container to
exit. The Supervisor (or restart policy of Docker in the container case)
will take care of restarting if appropriate.
2021-09-06 14:37:33 +02:00
Erik Montnemery 05abf1405d Migrate emulated_hue tests from unittest to pytest (#55794)
* Migrate emulated_hue tests from unittest to pytest

* Remove unused variables
2021-09-06 13:24:00 +02:00
Maciej Bieniek 753285eae7 Fix a lazy preset mode update for Xiaomi Miio fans (#55837) 2021-09-06 12:33:34 +02:00
David Bonnes 67b7144703 Fix incomfort min/max temperatures (#55806) 2021-09-06 12:26:20 +02:00
Andre Richter d50b700dc7 Refactor exception handling in Vallox (#55461) 2021-09-06 12:03:45 +02:00
Marc Mueller 4475cf24c8 Use EntityDescription - aqualogic (#55791) 2021-09-06 11:59:03 +02:00
Marc Mueller 3001df99cb Use EntityDescription - poolsense (#55743) 2021-09-06 11:58:47 +02:00
Philip Allgaier 364edbfd8a Add service descriptions for supervisor backup restore services (#52766)
* Add service descriptions for supervisor backup restore

* Add fields to restore services

* Update homeassistant/components/hassio/services.yaml

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-09-06 10:27:11 +02:00
Jan Bouwhuis 755835ee2e Alexa - Remove legacy speed support for fan platform (#55174)
* Remove legacy fan speed support

* remove fan range controller tests

* retrigger tests
2021-09-06 10:19:57 +02:00
Marc Mueller a4e4ffef0a Use EntityDescription - apcupsd (#55790) 2021-09-06 10:19:31 +02:00
Marc Mueller 655399eb7b Use EntityDescription - aemet (#55744) 2021-09-06 10:12:09 +02:00
Marc Mueller 77b60c712e Use EntityDescription - sabnzbd (#55788) 2021-09-06 09:54:07 +02:00
Marc Mueller a4dae0c1e1 Use EntityDescription - meteoclimatic (#55792) 2021-09-06 09:48:12 +02:00
Marc Mueller 96db04213b Use EntityDescription - vultr (#55789) 2021-09-06 09:44:33 +02:00
Marc Mueller cc6a0d2f8d Use EntityDescription - awair (#55747) 2021-09-06 09:40:41 +02:00
Marc Mueller 99ef2ae54d Use EntityDescription - vilfo (#55746) 2021-09-06 09:33:58 +02:00
jan iversen 0dd128af77 Change fix property to _attr for tradfri (#55691) 2021-09-06 08:49:00 +02:00
Greg 1b3530a3f8 Bump envoy_reader API to 0.20.0 (#55822) 2021-09-05 20:32:50 -10:00
Witold Sowa 8565821394 ZHA: Added support for ZigBee Simple Sensor device and Binary Input c… (#55819)
* ZHA: Added support for ZigBee Simple Sensor device and Binary Input cluster

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Alexei Chetroi <lexoid@gmail.com>
2021-09-06 01:41:57 -04:00
Paulus Schoutsen 523998f8a1 Drop logger service fields because keys are dynamic (#55750) 2021-09-05 20:53:12 -07:00
GitHub Action c2b89725be [ci skip] Translation update 2021-09-06 00:12:56 +00:00
Oliver 22961b30d2 Update to denonavr version 0.10.9 (#55805) 2021-09-05 13:28:48 -10:00
Alexei Chetroi aa6cb84b27 Optimize ZHA ZCL attribute reporting configuration (#55796)
* Refactor ZCL attribute reporting configuration

Configure up to 3 attributes in a single request.

* Use constant for attribute reporting configuration

* Update tests

* Cleanup

* Remove irrelevant for this PR section
2021-09-05 17:45:08 -04:00
Ville Skyttä 4e1e7a4a71 Protect Huawei LTE against None ltedl/ulfreq (#54411)
Refs https://github.com/home-assistant/core/issues/54400
2021-09-05 21:42:22 +03:00
Chris Browet 5a2bcd2763 ADD: generalize regex_findall (#54584) 2021-09-05 12:41:39 +02:00
GitHub Action f8ebc31576 [ci skip] Translation update 2021-09-05 00:11:36 +00:00
Paulus Schoutsen cce0ca5688 Tag Hue errors as format strings (#55751) 2021-09-04 15:38:14 -07:00
Simone Chemelli d39b861110 Fix SamsungTV sendkey when not connected (#55723) 2021-09-04 13:58:34 -07:00
Simone Chemelli 715ce3185b Handle Fritz InternalError (#55711) 2021-09-04 13:56:59 -07:00
Brian Egge c81a319346 Handle unknown preset mode in generic thermostat (#55588)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-09-04 13:17:57 -07:00
Marc Mueller f5a543b220 Remove deprecated device_state_attributes (#55734) 2021-09-04 13:16:01 -07:00
starkillerOG 58da58c008 Bump motion_blinds to 0.5.5 (#55710) 2021-09-04 22:06:50 +02:00
jan iversen 6348bf70ac Add caplog setup fixture. (#55714) 2021-09-04 07:09:55 -07:00
Marc Mueller 0700108278 Use NamedTuple for device_automation details (#55697) 2021-09-04 13:42:36 +02:00
Anders Melchiorsen d8b85b2067 Fix LIFX firmware version information (#55713) 2021-09-04 13:18:23 +02:00
Simone Chemelli b7e8348c30 Add bluez to the devcontainer (#55469)
* Fix fjaraskupan dependency for tests

* update package list

* Typo

* hadolint fixes

* hadolint fixes #2

* Cleanup

* Rewording
2021-09-04 12:16:06 +02:00
Erik Montnemery 38d42de2c0 Handle negative numbers in sensor long term statistics (#55708)
* Handle negative numbers in sensor long term statistics

* Use negative states in tests
2021-09-04 10:47:42 +02:00
Marc Mueller b3181a0ab2 Use NamedTuple for RGBColor (#55698) 2021-09-04 09:25:25 +02:00
Paulus Schoutsen 10317fba17 better detect legacy eagly devices (#55706) 2021-09-04 09:23:35 +02:00
jan iversen 7aa454231f Update template/test_sensor.py to use pytest (#55288) 2021-09-03 22:56:12 -07:00
Paulus Schoutsen 19c54b8cbf Drop unused ruamel (#55672) 2021-09-03 22:17:10 -07:00
J. Nick Koston 195ee2a188 Avoid creating sockets in homekit port available tests (#55668)
* Avoid creating sockets in homekit port available tests

* prevent new bridge from being setup -- its too fast now that the executor job is gone and it revealed an unpatched setup
2021-09-03 17:15:28 -10:00
Michael 0dc8fb497b Delay state update after switch is toggled for TP-Link HS210 device (#55671)
* delay state update for HS210

* Update workaround comment
2021-09-04 03:01:48 +02:00
Ville Skyttä b10fc89a6b Automation trigger info type hint improvements (#55402)
* Make automation trigger info a TypedDict

* zwave_js trigger type hint fixes

* Remove redundant automation trigger info field presence checks

* Use async_initialize_triggers in mqtt and tasmota device_trigger tests
2021-09-04 02:25:51 +02:00
epenet 0749e045bb Add reauth to Renault config flow (#55547)
* Add reauth flow to async_setup_entry

* Add reauth flow to config_flow

* Add reauth tests

* Split reauth/reauth_confirm

* unindent code

* Add entry_id and unique_id to reauth flow testing

* Use description_placeholders for username

* fix typo
2021-09-04 02:17:24 +02:00
GitHub Action 19dcb19d07 [ci skip] Translation update 2021-09-04 00:13:17 +00:00
Tomasz Wieczorek 501e7c84be Type scaffold PLATFORMS (#55699)
* Added template base type

Proposition to add typing, as pre-commit test on newly created integrations fails on it automatically:

```
homeassistant/components/<my_integration>/__init__.py:11: error: Need type annotation for "PLATFORMS" (hint: "PLATFORMS: List[<type>] = ...")  [var-annotated]
Found 1 error in 1 file (checked 4 source files)
```

I believe there shouldn't be other type than text, hence the proposition.

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-09-04 01:21:18 +02:00
Marc Mueller a54b9502ef Use NamedTuple for light color mode mapping (#55696) 2021-09-04 00:48:14 +02:00
Marc Mueller 617e8544c0 Use NamedTuple for touchline preset mode settings (#55695) 2021-09-04 00:44:16 +02:00
Marc Mueller edddeaf5ab Use NamedTuple for api endpoint settings (#55694) 2021-09-04 00:44:01 +02:00
jan iversen a756308e79 Update template/test_binary_sensor.py to use pytest (#55220) 2021-09-03 15:43:07 -07:00
Aidan Timson cd51d994b1 System Bridge - Set device class for binary sensor (#55688) 2021-09-03 23:48:11 +02:00
Marc Mueller 4eba2ccebc Use EntityDescription - synology_dsm (#55407) 2021-09-03 23:35:28 +02:00
Marc Mueller f5cd321185 Use EntityDescription - airnow (#55684) 2021-09-03 22:36:24 +02:00
Marc Mueller bbd9c6eb5b Use EntityDescription - discogs (#55683) 2021-09-03 22:36:17 +02:00
Marc Mueller ce6921d73c Use EntityDescription - picnic (#55682)
* Use EntityDescription - picnic

* Change _attr_extra_state_attributes to be static

* Fix tests
2021-09-03 22:35:59 +02:00
Brian Rogers 9db13a3e74 Fix Rachio service missing with 1st generation controllers (#55679) 2021-09-03 22:35:33 +02:00
Marc Mueller 1e4233fe20 Use EntityDescription - iperf3 (#55681) 2021-09-03 22:35:12 +02:00
Marc Mueller 76ce0f6ea7 Use EntityDescription - econet (#55680)
* Use EntityDescription - econet

* Resolve name constants
2021-09-03 22:34:51 +02:00
Marc Mueller 798f487ea4 Use EntityDescription - faa_delays (#55678)
* Use EntityDescription - faa_delays

* Update binary_sensor.py
2021-09-03 22:34:29 +02:00
Marc Mueller 3c0a34dd01 Use EntityDescription - luftdaten (#55676)
* Use EntityDescription - luftdaten

* Fix name attribute

* Remove default values

* Move SensorTypes back to __init__
2021-09-03 22:34:01 +02:00
Marc Mueller fbf812a845 Use EntityDescription - freebox (#55675)
* Use EntityDescription - freebox

* Remove default values
2021-09-03 22:33:26 +02:00
Paulus Schoutsen 7111fc47c4 Better handle invalid trigger config (#55637) 2021-09-03 10:15:57 -07:00
Paulus Schoutsen e0f640c0f8 Guard for doRollover failing (#55669) 2021-09-03 09:53:47 -07:00
Joakim Sørensen 7caa985a59 Fix hdmi_cec switches (#55666) 2021-09-03 09:17:41 -07:00
J. Nick Koston 25b39b36e7 Ignore missing devices when in ssdp unsee (#55553) 2021-09-03 09:06:07 -07:00
Paulus Schoutsen 418d6a6a41 Guard for unexpected exceptions in device automation (#55639)
* Guard for unexpected exceptions in device automation

* merge

Co-authored-by: J. Nick Koston <nick@koston.org>
2021-09-03 09:04:50 -07:00
Marc Mueller a234f2ab31 Remove dead fritzbox code (#55617)
* EntityInfo has been replaced by EntityDescription (#55104)
* Extra switch attributes have been replaced by dedicated sensors (#52562)
2021-09-03 17:48:48 +02:00
Marc Mueller 7461af68b9 Use NamedTuple for color temperature range (#55626) 2021-09-03 17:41:32 +02:00
Joakim Sørensen 2171922265 Always show state for the updater binary_sensor (#55584) 2021-09-03 17:40:07 +02:00
ehendrix23 4310a7d814 Add upnp sensor for IP, Status, and Uptime (#54780)
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-09-03 17:15:28 +02:00
Michael ae9e3c237a Fix CONFIG_SCHEMA validation in Speedtest.net (#55612) 2021-09-03 14:11:19 +02:00
Nikolay Vasilchuk b4d4fe4ef8 Fix Starline sensor state AttributeError (#55654)
* Fix starline sensors state

* Black
2021-09-03 14:04:56 +02:00
Pascal Vizeli 70338da50e Remove wheels for alpine 3.13 (#55650) 2021-09-03 11:22:41 +02:00
Yuval Aboulafia 173b87e675 Clean holiday attributes code in Jewish calendar (#55080)
* Clean repetitive code in jewish calendar

* do not return none

* fix holiday
2021-09-03 12:07:53 +03:00
Yuval Aboulafia 91cd6951f3 Minor cleanup in Waze travel times (#55422)
* reduce imports and clean the attriburts

* add icons

* use entity class attributes

* fix icon

* add misc types

* fix update

* do not change icon yet

* address review
2021-09-03 11:54:32 +03:00
Erik Montnemery 4684ea2d14 Prevent 3rd party lib from opening sockets in broadlink tests (#55636) 2021-09-03 10:13:35 +02:00
Paulus Schoutsen 0c2772e0be Fix template sensor availability (#55635) 2021-09-03 09:02:45 +02:00
Pascal Vizeli 8319f232b8 Disable observer for USB on containers (#55570)
* Disable observer for USB on containers

* remove operating system test

Co-authored-by: J. Nick Koston <nick@koston.org>
2021-09-03 08:05:37 +02:00
J. Nick Koston d8a81a54d8 Narrow zwave_js USB discovery (#55613)
- Avoid triggering discovery when we can know in advance the
  device is not a Z-Wave stick
2021-09-03 05:11:03 +02:00
GitHub Action 8af0cb9e65 [ci skip] Translation update 2021-09-03 00:16:18 +00:00
bsmappee 02db4dbe5e Bump pysmappee to 0.2.27 (#55257)
* bump

* bump
2021-09-02 21:01:16 +02:00
Erik Montnemery 7dbe8070f7 Mock out network.util.async_get_source_ip in tests (#55592) 2021-09-02 20:44:50 +02:00
J. Nick Koston 363320eedb Mock sockets in the network integration tests (#55594) 2021-09-02 20:44:42 +02:00
Erik Montnemery 2e5c1236f9 Prevent 3rd party lib from opening sockets in freedompro tests (#55596) 2021-09-02 19:32:19 +02:00
Erik Montnemery 348bdca647 Prevent 3rd party lib from opening sockets in epson tests (#55595) 2021-09-02 19:30:53 +02:00
Joakim Sørensen 4f33679255 Fix url lookup in telegram_bot webhook (#55587) 2021-09-02 19:17:33 +02:00
Erik Montnemery cabb9c0ea4 Prevent 3rd party lib from opening sockets in broadlink tests (#55593) 2021-09-02 19:03:24 +02:00
Pascal Vizeli d4a2b36638 Downgrade sqlite-libs on docker image (#55591) 2021-09-02 18:09:30 +02:00
Erik Montnemery bfd799dc04 Use hass_client_no_auth test fixture in integrations s-x (#55585) 2021-09-02 14:50:10 +02:00
Erik Montnemery acdddabe1f Use hass_client_no_auth test fixture in integrations h-p (#55583) 2021-09-02 14:49:40 +02:00
Erik Montnemery d5b6dc4f26 Use hass_client_no_auth test fixture in integrations a-g (#55581) 2021-09-02 14:49:20 +02:00
jan iversen 69aba2a6a1 Correct duplicate address. (#55578) 2021-09-02 13:53:38 +02:00
Erik Montnemery cdaba62d2c Add test fixture for unauthenticated HTTP client (#55561)
* Add test fixture for unauthenticated HTTP client

* Remove things from the future
2021-09-02 13:09:16 +02:00
Joakim Sørensen b3b9fb0a7c Bump pyuptimerobot to 21.9.0 (#55546) 2021-09-02 11:40:32 +02:00
Alexei Chetroi cb1e0666c8 Pick right coordinator (#55555) 2021-09-01 22:54:35 -04:00
GitHub Action 6b4f2e6f8f [ci skip] Translation update 2021-09-02 00:20:52 +00:00
Teemu R aef4a69cd0 xiaomi_miio: bump python-miio dependency (#55549) 2021-09-02 00:18:12 +02:00
Raman Gupta 02eba22068 Add additional test coverage for zwave_js meter sensors (#55465) 2021-09-01 17:22:17 -04:00
Erik Montnemery 7dbd0e5274 Fix zeroconf mock and use it in CI group 1's tests (#55526)
* Fix zeroconf mock and use it in CI group 1's tests

* Mock HaAsyncServiceBrowser
2021-09-01 22:38:00 +02:00
Erik Montnemery e631671832 Use respx.mock in generic camera tests (#55521) 2021-09-01 20:45:29 +02:00
Paulus Schoutsen 27e29b714c Bump cloud to 0.47.1 (#55312)
* Bump cloud to 0.47.0

* Bump reqs

* Bump to 0.47.1

* Do not load hass_nabucasa during http startup

* fix some tests

* Fix test

Co-authored-by: Ludeeus <ludeeus@ludeeus.dev>
2021-09-01 09:54:54 -07:00
Daniel Hjelseth Høyer c68e87c40e OpenGarage, change to attributes (#55528)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2021-09-01 18:33:56 +02:00
Daniel Hjelseth Høyer 80af2f4279 Open garage, add closing and opening to state (#55372) 2021-09-01 08:16:10 -05:00
epenet f8ec85686a Add select platform to Renault integration (#55494)
* Add select platform to Renault integration

* Fix pylint
2021-09-01 14:44:10 +02:00
Joakim Sørensen 33fb080c1e Add remote server to cloud system health (#55506)
* Add sintun server to cloud system health

* Update name

* Adjust test
2021-09-01 13:23:50 +02:00
Paulus Schoutsen 9284f7b147 Tweaks for the iotawatt integration (#55510) 2021-09-01 13:18:50 +02:00
epenet bcf97cb308 Add device tracker platform to Renault integration (#54745) 2021-09-01 13:10:48 +02:00
Stefan 04a052a37d Fix moon phases (#55518) 2021-09-01 12:26:56 +02:00
Joakim Sørensen befcafbc49 Mock setup in spotify tests (#55515) 2021-09-01 11:27:21 +02:00
epenet 02b7356596 Add services to Renault integration (#54820)
* Add services

* Add tests

* Cleanup async

* Fix pylint

* Update services.yaml

* Add extra schema validation

* Rename constants

* Simplify code

* Move constants

* Fix pylint

* Cleanup constants

* Drop charge_set_mode as moved to select platform

* Only register the services if no config entry has registered them yet

* Replace VIN with device selector to select vehicle

* Update logging

* Adjust type checking

* Use a shared base SERVICE_VEHICLE_SCHEMA

* Add selectors for ac_start (temperature/when)

* Add object selector for charge_set_schedules service
2021-09-01 11:23:54 +02:00
Otto Winter 46159c3f18 ESPHome light color mode use capabilities (#55206)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-09-01 10:03:41 +02:00
mbo18 a28593f133 Add vacation mode to manual alarm_control_panel (#55340)
* Add vacation mode

* Add vacation to demo

* Deduplicate code in tests
2021-09-01 09:34:21 +02:00
Brian Egge 889aced3b6 Fix None support_color_modes TypeError (#55497)
* Fix None support_color_modes TypeError 

https://github.com/home-assistant/core/issues/55451

* Update __init__.py
2021-09-01 08:26:09 +02:00
Brett Adams 36b37b6db3 Add missing device class for temperature sensor in Advantage Air (#55508) 2021-08-31 22:50:32 -07:00
Felipe Martins Diel 3bc58f9750 Fix BroadlinkSwitch._attr_assumed_state (#55505) 2021-08-31 22:49:56 -07:00
muppet3000 343054494c Added trailing slash to US growatt URL (#55504) 2021-09-01 07:18:20 +02:00
Erik Montnemery 93c086d830 Correct sum statistics when only last_reset has changed (#55498)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-31 21:30:52 -07:00
GitHub Action 9e41a37284 [ci skip] Translation update 2021-09-01 00:19:48 +00:00
gjong ff229dd599 Increase YouLess polling interval (#55490) 2021-08-31 12:24:09 -07:00
gjong cc4b2fbcfa Remove Youless native unit of measurement (#55492) 2021-08-31 19:22:00 +02:00
Erik Montnemery 5d1a193eca Improve log for sum statistics (#55502) 2021-08-31 19:15:22 +02:00
uvjustin 71c6f99d31 Fix ArestSwitchBase missing is on attribute (#55483) 2021-08-31 17:30:05 +02:00
Ernst Klamer bd60a58765 Improvements to the solarlog integration (#55405) 2021-08-31 16:46:19 +02:00
Maciej Bieniek 08a0377dcb Add support for Xiaomi Miio Air Purifier 3C (#55484) 2021-08-31 16:44:13 +02:00
Raman Gupta 4d98a7e156 Allow device_id template function to use device name as input (#55474) 2021-08-31 14:56:47 +02:00
Joakim Sørensen 3e38dc0fd9 Add cache-control headers to supervisor entrypoint (#55493) 2021-08-31 14:45:28 +02:00
JasperPlant afc0a1f376 Add TLX daily power meter. for Growatt (#55445) 2021-08-31 11:55:23 +02:00
epenet 1849eae0ff Renault code quality improvements (#55454) 2021-08-31 11:06:54 +02:00
Erik Montnemery f9225bad5f Make new cycles for sensor sum statistics start with 0 as zero-point (#55473) 2021-08-31 10:45:17 +02:00
Eric Severance 88a08fdf57 Wemo Insight devices need polling when off (#55348) 2021-08-31 09:32:26 +02:00
Paulus Schoutsen d277e0fb03 Add Eagle 200 name back (#55477)
* Add Eagle 200 name back

* add comment

* update tests
2021-08-31 08:45:35 +02:00
Feliksas The Lion 13b001cd9b Fix Zone 2 and Zone 3 detection in onkyo (#55471) 2021-08-30 20:33:52 -07:00
Matthew Garrett dd21bf73fc Assistant sensors (#55480) 2021-08-30 20:33:06 -07:00
GitHub Action 368cac7e5d [ci skip] Translation update 2021-08-31 00:17:01 +00:00
Erik Montnemery 18c03e2f8d Fix race in MQTT sensor when last_reset_topic is configured (#55463) 2021-08-30 23:32:35 +02:00
Bram Kragten 9b3346bc80 Update frontend to 20210830.0 (#55472) 2021-08-30 23:32:19 +02:00
Aaron Bach 76f21452ee Bump aioambient to 1.3.0 (#55468) 2021-08-30 23:05:28 +02:00
ha0y 433775cf4b Add input_select and select domain support for HomeKit (#54760)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-08-30 15:28:26 -05:00
Aaron Bach 46f05ca279 Bump pyopenuv to 2.2.0 (#55464) 2021-08-30 13:12:27 -07:00
Aaron Bach 3d9d104482 Bump pyiqvia to 1.1.0 (#55466) 2021-08-30 13:12:09 -07:00
Raman Gupta 1d1b5ab345 Fix area_id and area_name template functions (#55470) 2021-08-30 13:09:41 -07:00
Marc Mueller 1c01ff401f Use EntityDescription - qnap (#55410)
* Use EntityDescription - qnap

* Remove default values
2021-08-30 21:59:50 +02:00
Greg 3bd9be2f6d Add IoTaWatt integration (#55364)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-30 12:52:29 -07:00
Aaron Bach daa9c8d856 Add -term statistics for Notion sensors (#55414) 2021-08-30 19:07:05 +02:00
Aaron Bach 1aa30ea87b Add long-term statistics for SimpliSafe sensors (#55419) 2021-08-30 19:04:21 +02:00
Raman Gupta 331726ec2f Bump zwave-js-server-python to 0.29.1 (#55460) 2021-08-30 09:40:56 -07:00
J. Nick Koston 27ecd43da3 Bump zeroconf to 0.36.2 (#55459)
- Now sends NSEC records when requesting non-existent address types
  Implements RFC6762 sec 6.2 (http://datatracker.ietf.org/doc/html/rfc6762#section-6.2)

- This solves a case where a HomeKit bridge can take a while to update
  because it is waiting to see if an AAAA (IPv6) address is available
2021-08-30 08:59:41 -07:00
Raman Gupta d62a78ae61 Don't set zwave_js sensor device class to energy when unit is wrong (#55434) 2021-08-30 08:48:36 -07:00
Simone Chemelli fa7873dc6d Fix noise/attenuation units to UI display for Fritz (#55447) 2021-08-30 08:43:11 -07:00
Ian de5a22953d Whole-string match reqs in comment_requirement (#55192)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-30 08:20:02 -07:00
Andre Richter cbc68e45cd Refactor vallox constants (#55456) 2021-08-30 17:01:45 +02:00
Christopher Kochan c4235edc41 Add Sense energy sensors (#54833)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-30 08:01:26 -07:00
Erik Montnemery ed53bb1d91 Revert "Deprecate last_reset options in MQTT sensor" (#55457)
This reverts commit f9fa5fa804.
2021-08-30 16:58:48 +02:00
Joakim Sørensen a668300c2e Use AwesomeVersion for account link service check (#55449) 2021-08-30 14:11:07 +02:00
Erik Montnemery 722aa0895e Improve statistics error messages when sensor's unit is changing (#55436)
* Improve error messages when sensor's unit is changing

* Improve test coverage
2021-08-30 12:51:46 +02:00
Erik Montnemery 7e9f8de7e0 Fix exception when shutting down DSMR (#55441)
* Fix exception when shutting down DSMR

* Update homeassistant/components/dsmr/sensor.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-08-30 12:08:21 +02:00
Erik Montnemery 8faec3da8d Correct setup of system_bridge sensors (#55442) 2021-08-30 12:06:24 +02:00
Erik Montnemery 1060630bbd Fix crash in buienradar sensor due to self.hass not set (#55438) 2021-08-30 10:29:39 +02:00
Aidan Timson 25273c694a System Bridge 2.5.0 - Additional Sensors (#53892)
* Update package to 2.1.0

* Add version sensor

* Add graphics memory sensors

* Change graphics memory data from MB

* Add GPU usage sensor

* Add gpu clock speed sensors

* GPU sensors

* GPU power usage

* enumerate instead of range len

* Updates from rebase

* Add graphics

* Add Per CPU load sensor

* Cleanup

* Use super class attributes

* Suggested changes and fix

* User, System, Idle sensors

* Average, User, System and idle sensors instead of attrs

* Remove unused attrs

* Remove null/none sensor

* Sensor entity descriptions

* Fix index out of range error

* Set state class

* Use entity_registry_enabled_default

* Use built in entity_registry_enabled_default

* Use built in icon

* Fix

* Use binary sensor entity description

* Fix pylint

* Fix uom

* Add to coveragerc

* is_on

* Move entity descriptions to platforms

* Clearout default values

* Fix docstring

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Cleanup and catch

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-08-30 10:26:48 +02:00
uvjustin 071fcee9a9 Remove byte-range addressed parts in stream (#55396)
Add individually addressed parts
2021-08-30 13:20:19 +08:00
J. Nick Koston 5549a925b8 Implement import of consider_home in nmap_tracker to avoid breaking change (#55379) 2021-08-29 20:38:41 -07:00
Marc Mueller be04d7b92e Fix device_class - qnap drive_temp sensor (#55409) 2021-08-29 20:30:54 -07:00
J. Nick Koston f37c541a50 Bump zeroconf to 0.36.1 (#55425)
- Fixes duplicate records in the cache

- Changelog: https://github.com/jstasiak/python-zeroconf/compare/0.36.0...0.36.1
2021-08-29 20:29:46 -07:00
Klaas Schoute 6823b14d4c Update entity names for P1 Monitor integration (#55430) 2021-08-29 20:29:37 -07:00
Aaron Bach 94e0db8ec4 Ensure ReCollect Waste shows pickups for midnight on the actual day (#55424) 2021-08-29 20:27:34 -07:00
Raman Gupta ebc2a0103e Make zwave_js discovery log message more descriptive (#55432) 2021-08-29 20:25:47 -07:00
GitHub Action ea7f3c8bb3 [ci skip] Translation update 2021-08-30 00:11:40 +00:00
Aaron Bach 32df2f7d8b Deprecate YAML config for ReCollect Waste (#55426) 2021-08-29 14:03:44 -06:00
Aaron Bach b43c80ca21 Give ReCollect Waste sensor a friendlier label (#55427) 2021-08-29 14:03:09 -06:00
Andre Richter fa201b6c2b Add myself to Vallox codeowners (#55428) 2021-08-29 14:02:52 -06:00
Robert Svensson 76ce33dc24 Only return not return None (#55423) 2021-08-29 13:10:18 -06:00
Erik Montnemery 8b436c43f7 Enable basic type checking for cert_expiry (#55335) 2021-08-29 10:57:18 -06:00
Matt Krasowski fd66120d6d Handle incorrect values reported by some Shelly devices (#55042) 2021-08-29 14:52:12 +02:00
J. Nick Koston 43b8353566 Show device_id in HomeKit when the device registry entry is missing a name (#55391)
- Reported at: https://community.home-assistant.io/t/homekit-unknown-error-occurred/333385
2021-08-29 09:01:04 +02:00
Aaron Bach 4aed0b6ccf Use EntityDescription - ambient_station (#55366) 2021-08-28 21:31:18 -06:00
Aidan Timson 3647ada143 OVO Energy - Post #54952 Cleanup (#55393) 2021-08-28 22:31:07 -05:00
Aaron Bach 2dddd31d97 Simplify calcuation of Notion binary sensor state (#55387) 2021-08-28 21:30:44 -06:00
uvjustin 923158cfba Add ll hls to stream (#49608) 2021-08-29 09:53:41 +08:00
GitHub Action 291a2d6258 [ci skip] Translation update 2021-08-29 00:11:57 +00:00
J. Nick Koston 43288d3e1f Prevent storage loads from monopolizing the executor pool (#55389)
* Prevent storage loads from monopolizing the executor pool

- At startup there is an increasing demand to load data
  from storage. Similar to #49451 and #43085, we now prevent
  the thread pool from being monopolized by storage loads and
  allow other consumers that are doing network I/O to proceed
  without having to wait for a free executor thread.

* Only create Semaphore instance when one is not already there
2021-08-28 18:30:20 -05:00
J. Nick Koston d41fa66bca Remove legacy discovery after_dependencies from apple_tv (#55390)
- apple_tv devices are now discovered by zeroconf, and legacy discovery
  is no longer needed
2021-08-28 18:30:07 -05:00
Michael f1ba98927c Address late fritzbox comments (#55388)
* correct imports

* move platform specifics into platforms

* move descriptions into platforms
2021-08-28 23:07:06 +02:00
jan iversen f91cc21bbd Solve modbus shutdown racing condition (#55373) 2021-08-28 23:04:33 +02:00
Michael 13cc671844 Re-configuration possibilities for Synology DSM (#53285)
* add automated host/ip reconfig via SSDP

* add reconfig of existing entry

* adjust tests

* adjust tests again

* use self._async_current_entries()

* _async_get_existing_entry()

* apply suggestions
2021-08-28 14:11:51 -05:00
Matthieu 979797136a Add select entity to Logitech Harmony (#53943)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-08-28 14:10:19 -05:00
Aaron Bach 778fa2e3fe Bump simplisafe-python to 11.0.6 (#55385) 2021-08-28 12:57:02 -06:00
Daniel Hjelseth Høyer 6a93f5b7ad Tractive name (#55342) 2021-08-28 08:57:57 -07:00
Maciej Bieniek 19873e6547 Address late review for Tractive integration (#55371) 2021-08-28 17:49:34 +02:00
Joakim Sørensen 2fcd77098d Pin regex to 2021.8.28 (#55368) 2021-08-28 15:00:14 +02:00
jan iversen d1965eef8b Activate mypy for sonar (#55327)
* Please mypy.
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-08-28 12:05:48 +02:00
jan iversen 16351ef3c2 Add shutdown test. (#55357) 2021-08-28 08:11:58 +02:00
Nathan Spencer e2f257cb63 Bump pylitterbot to 2021.8.1 (#55360) 2021-08-27 20:58:21 -07:00
Maciej Bieniek ea8702b0df Address late review for Xiaomi Miio number platform (#55275) 2021-08-27 21:41:15 -05:00
Joakim Sørensen 470aa7e871 Add data update coordinator to the Tautulli integration (#54706)
* Add data update coordinator to the Tautulli integration

* update .coveragerc

* Add guard for UpdateFailed

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>

* ignore issues

Co-authored-by: Chris Talkington <chris@talkingtontech.com>
2021-08-27 20:39:12 -05:00
Jason Hunter 61a7ce173c close connection on connection retry, bump onvif lib (#55363) 2021-08-27 17:34:32 -07:00
GitHub Action b0c52220bc [ci skip] Translation update 2021-08-28 00:11:00 +00:00
Raman Gupta 714564eaa6 Listen to node events in the zwave_js node status sensor (#55341) 2021-08-27 15:01:20 -07:00
Aaron Bach 1f37c215f6 Ensure ReCollect Waste starts up even if no future pickup is found (#55349) 2021-08-27 15:00:17 -07:00
Paulus Schoutsen 46d0523f98 Convert solarlog to coordinator (#55345) 2021-08-27 14:59:55 -07:00
Paulus Schoutsen eb458fb1d5 Fix wolflink super call (#55359) 2021-08-27 14:59:28 -07:00
J. Nick Koston 10fa63775d Ensure yeelights resync state if they are busy on first connect (#55333) 2021-08-27 12:43:53 -05:00
Anders Melchiorsen ed19fdd462 Upgrade aiolifx to 0.6.10 (#55344) 2021-08-27 09:53:42 -07:00
J. Nick Koston 2cc87cb7ab Retrigger config flow when the ssdp location changes for a UDN (#55343)
Fixes #55229
2021-08-27 09:53:29 -07:00
jan iversen 7ac72ebf38 Add modbus name to log_error (#55336) 2021-08-27 09:26:57 -07:00
Robert Hillis 98c8782c2b Fix sonos alarm schema (#55318) 2021-08-27 09:25:40 -07:00
realPy 7bd7d644a0 Correct flash light livarno when use hue (#55294)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-27 09:25:27 -07:00
prwood80 3f2fad1a27 Improve performance of ring camera still images (#53803)
Co-authored-by: Pat Wood <prwood80@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-27 09:22:49 -07:00
Chris Talkington 819fd811af Fix reauth for sonarr (#55329)
* fix reauth for sonarr

* Update config_flow.py

* Update config_flow.py

* Update config_flow.py

* Update test_config_flow.py

* Update config_flow.py

* Update test_config_flow.py

* Update config_flow.py
2021-08-27 11:04:07 -05:00
Michael e2dac31471 Use EntityDescription - fritzbox (#55104)
* Use sensor entity description

* check if not none instead if callable

* List comprehension in switch and climate

* change state to native_value in description

* merge FritzBoxSensorEntity into FritzBoxEntity

* rename SENSOR_DESCRIPTIONS to SENSOR_TYPES

* use mixins for descriptions

* use comprehension in async_setup_entry()

* improve extra_state_attributes
2021-08-27 17:09:34 +02:00
Erik Montnemery 7e70252de5 Handle statistics for sensor with changing state class (#55316) 2021-08-27 16:18:49 +02:00
J. Nick Koston cc857abfd2 Adjust zha comment to be readable (#55330) 2021-08-27 16:01:26 +02:00
epenet adab367f0e Renault code quality improvements (#55313)
* Use constants

* Drop entity_class for binary_sensor

* Move data property from RenaultDataEntity to RenaultSensor

* Update function description
2021-08-27 12:12:09 +02:00
rikroe 259eeb3169 Bump bimmer_connected to 0.7.20 (#55299) 2021-08-27 12:03:25 +02:00
J. Nick Koston efb1fb9978 Always send powerview move command in case shade is out of sync (#55308) 2021-08-27 08:49:50 +02:00
Aaron Bach 5693f9ff9b Bump simplisafe-python to 11.0.5 (#55306) 2021-08-27 08:48:20 +02:00
epenet 9ba504cd78 Address late review for Renault integration (#55230)
* Add type hints

* Fix isort

* tweaks to state attributes

* Move lambdas to regular functions

* Split CHECK_ATTRIBUTES into DYNAMIC_ATTRIBUTES and FIXED_ATTRIBUTES

* Clarify tests

* Fix typo
2021-08-27 07:22:23 +02:00
J. Nick Koston 176fd39e0b Fix lifx model to be a string (#55309)
Fixes #55307
2021-08-27 06:37:28 +02:00
Matthias Alphart cd0ae66d58 Add CONF_STATE_CLASS to sensor/__init__.py (#54106)
* add CONF_STATE_CLASS to const.py

* move to `sensor/__init__.py`

* move to sensor/const.py

* Revert "move to sensor/const.py"

This reverts commit 604d0d066b.

* move it to `sensor/const.py` but import it from `sensor/__init__.py`

* add Modbus
2021-08-27 05:54:50 +02:00
GitHub Action 65d14909ee [ci skip] Translation update 2021-08-27 00:14:42 +00:00
J. Nick Koston 5393a16c44 Set yeelight capabilities from external discovery (#55280) 2021-08-26 19:04:12 -05:00
Aaron Bach cbd65efe52 Bump aiorecollect to 1.0.8 (#55300) 2021-08-26 16:59:27 -06:00
J. Nick Koston ef10773202 Fix creation of new nmap tracker entities (#55297) 2021-08-26 15:02:49 -07:00
J. Nick Koston dfc2556669 Gracefully handle pyudev failing to filter on WSL (#55286)
* Gracefully handle pyudev failing to filter on WSL

* add debug message

* add mocks so we reach the new check
2021-08-26 15:47:10 -05:00
Chris 14aa19b814 Fix unique_id conflict in smarttthings (#55235) 2021-08-26 13:43:26 -07:00
J. Nick Koston c3972b22fd Fix yeelight brightness when nightlight switch is disabled (#55278) 2021-08-26 15:18:36 -05:00
J. Nick Koston ae1d2926cf Fix some yeelights showing wrong state after on/off (#55279) 2021-08-26 13:25:26 -05:00
J. Nick Koston 089dfad78a Ensure yeelight model is set in the config entry (#55281)
* Ensure yeelight model is set in the config entry

- If the model was not set in the config entry the light could
  be sent commands it could not handle

* update tests

* fix test
2021-08-26 13:02:59 -05:00
Paulus Schoutsen f6bb5c77a0 Bump ring to 0.7.1 (#55282) 2021-08-26 10:37:53 -07:00
Maciej Bieniek eb9d242ade Move AirlySensorEntityDescription to sensor platform (#55277) 2021-08-26 18:40:42 +02:00
J. Nick Koston fbcf21412d Only warn once per entity when the async_camera_image signature needs to be updated (#55238) 2021-08-26 09:36:25 -07:00
jjlawren b3e84c6ee8 Set up polling task with subscriptions in Sonos (#54355) 2021-08-26 09:35:35 -07:00
Florian Gareis c3316df31d Don't create DSL sensor for devices that don't support DSL (#55269) 2021-08-26 09:33:41 -07:00
Maciej Bieniek f942cb03a4 Fix AttributeError for non-MIOT Xiaomi Miio purifiers (#55271) 2021-08-26 09:29:25 -07:00
Alexei Chetroi d3ac72d013 Bump up ZHA dependencies (#55242)
* Bump up ZHA dependencies

* Bump up zha-device-handlers
2021-08-26 11:38:35 -04:00
J. Nick Koston d59ea5329e Abort zha usb discovery if deconz is setup (#55245)
* Abort zha usb discovery if deconz is setup

* Update tests/components/zha/test_config_flow.py

* add deconz domain const

* Update homeassistant/components/zha/config_flow.py

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
2021-08-26 10:00:32 -04:00
J. Nick Koston d4fa625a7f Defer zha auto configure probe until after clicking configure (#55239) 2021-08-26 09:59:41 -04:00
J. Nick Koston a89057ece5 Limit USB discovery to specific manufacturer/description/serial_number matches (#55236)
* Limit USB discovery to specific manufacturer/description/serial_number matches

* test for None case
2021-08-26 09:59:02 -04:00
jan iversen 2d5176eee9 Change entity_timers to be a local variable. (#55258)
Ensure outstanding pymodbus calls are handled before closing.
2021-08-26 15:23:00 +02:00
Erik Montnemery 0a07ff4d23 Warn if a sensor with state_class_total has a decreasing value twice (#55251) 2021-08-26 14:27:14 +02:00
Franck Nijhof 96303a1d80 Fix MQTT add-on discovery to be ignorable (#55250) 2021-08-26 11:14:42 +02:00
Joakim Sørensen 03d3bbfba1 Only postfix image name for container (#55248) 2021-08-26 10:54:42 +02:00
Eric Severance 56246056ce Be tolerant of Wemo insight_param keys that might not exist (#55232) 2021-08-26 10:27:06 +02:00
Erik Montnemery 6d4a47a53d Fix double precision float for postgresql (#55249) 2021-08-26 10:06:53 +02:00
Milan Meulemans e6d710c203 Remove option and range checks in Rituals integration (#55222)
* Fix number

* Fix select
2021-08-26 08:37:27 +02:00
Sean Vig b45c985d58 Remove un-needed asserts on hass in Amecrest (#55244) 2021-08-26 08:34:58 +02:00
GitHub Action 3d7bfa8357 [ci skip] Translation update 2021-08-26 00:13:27 +00:00
Marc Mueller b81c2806bb Remove temperature conversion - tado (#55231) 2021-08-26 00:37:18 +02:00
Franck Nijhof 06a30c882f Bump version to 2021.10.0dev0 (#55227) 2021-08-25 23:19:14 +02:00
epenet 9315f3bdd9 Use EntityDescription - renault (#55061)
* Cleanup sensor.py

* Add EntityDescription

* Add checks for state attributes

* Fix pylint

* Simplify checks

* Add icon checks

* Update data type

* Use mixin for required keys, and review class initialisation

* Add constraint to TypeVar("T")

* Enable lambda for icon handling

* Enable lambda for value handling

* Enable lambda for value handling
2021-08-25 23:15:49 +02:00
1025 changed files with 21727 additions and 12807 deletions
+2
View File
@@ -1032,6 +1032,8 @@ omit =
homeassistant/components/tank_utility/sensor.py
homeassistant/components/tankerkoenig/*
homeassistant/components/tapsaff/binary_sensor.py
homeassistant/components/tautulli/const.py
homeassistant/components/tautulli/coordinator.py
homeassistant/components/tautulli/sensor.py
homeassistant/components/ted5000/sensor.py
homeassistant/components/telegram/notify.py
-2
View File
@@ -65,7 +65,6 @@ jobs:
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
@@ -106,7 +105,6 @@ jobs:
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
+4 -1
View File
@@ -192,6 +192,7 @@ homeassistant/components/github/* @timmo001 @ludeeus
homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/goalzero/* @tkdrob
homeassistant/components/goe_charger/* @0xFEEDC0DE64
homeassistant/components/gogogate2/* @vangorra @bdraco
homeassistant/components/google_assistant/* @home-assistant/cloud
homeassistant/components/google_cloud/* @lufton
@@ -202,7 +203,7 @@ homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
homeassistant/components/guardian/* @bachya
homeassistant/components/habitica/* @ASMfreaK @leikoilja
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
homeassistant/components/hassio/* @home-assistant/supervisor
homeassistant/components/heatmiser/* @andylockran
homeassistant/components/heos/* @andrewsayre
@@ -248,6 +249,7 @@ homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo
homeassistant/components/ios/* @robbiet480
homeassistant/components/iotawatt/* @gtdiehl
homeassistant/components/iperf3/* @rohankapoorcom
homeassistant/components/ipma/* @dgomes @abmantis
homeassistant/components/ipp/* @ctalkington
@@ -552,6 +554,7 @@ homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/vallox/* @andre-richter
homeassistant/components/velbus/* @Cereal2nd @brefra
homeassistant/components/velux/* @Julius2342
homeassistant/components/vera/* @pavoni
+15
View File
@@ -16,6 +16,21 @@ RUN \
-e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant
# Fix Bug with Alpine 3.14 and sqlite 3.35
# https://gitlab.alpinelinux.org/alpine/aports/-/issues/12524
ARG BUILD_ARCH
RUN \
if [ "${BUILD_ARCH}" = "amd64" ]; then \
export APK_ARCH=x86_64; \
elif [ "${BUILD_ARCH}" = "i386" ]; then \
export APK_ARCH=x86; \
else \
export APK_ARCH=${BUILD_ARCH}; \
fi \
&& curl -O http://dl-cdn.alpinelinux.org/alpine/v3.13/main/${APK_ARCH}/sqlite-libs-3.34.1-r0.apk \
&& apk add --no-cache sqlite-libs-3.34.1-r0.apk \
&& rm -f sqlite-libs-3.34.1-r0.apk
# Home Assistant S6-Overlay
COPY rootfs /
+2
View File
@@ -6,6 +6,8 @@ RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# Additional library needed by some tests and accordingly by VScode Tests Discovery
bluez \
libudev-dev \
libavformat-dev \
libavcodec-dev \
-8
View File
@@ -118,14 +118,6 @@ homeassistant.util.pressure
:undoc-members:
:show-inheritance:
homeassistant.util.ruamel\_yaml
-------------------------------
.. automodule:: homeassistant.util.ruamel_yaml
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.ssl
----------------------
+5 -1
View File
@@ -342,7 +342,11 @@ def async_enable_logging(
err_log_path, backupCount=1
)
err_handler.doRollover()
try:
err_handler.doRollover()
except OSError as err:
_LOGGER.error("Error rolling over log file: %s", err)
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
@@ -2,7 +2,7 @@
"config": {
"abort": {
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi",
"single_instance_allowed": "D\u00e9ja configur\u00e9. Une seule configuration possible."
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -14,7 +14,7 @@
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom de l'int\u00e9gration"
"name": "Nom"
},
"description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration.",
"title": "AccuWeather"
@@ -3,7 +3,9 @@
"step": {
"user": {
"data": {
"account_id": "ID de la cuenta"
"account_id": "ID de la cuenta",
"host": "Anfitri\u00f3n",
"password": "Contrase\u00f1a"
}
}
}
@@ -17,9 +17,9 @@
"host": "H\u00f4te",
"password": "Mot de passe",
"port": "Port",
"ssl": "AdGuard Home utilise un certificat SSL",
"ssl": "Utilise un certificat SSL",
"username": "Nom d'utilisateur",
"verify_ssl": "AdGuard Home utilise un certificat appropri\u00e9"
"verify_ssl": "V\u00e9rifier le certificat SSL"
},
"description": "Configurez votre instance AdGuard Home pour permettre la surveillance et le contr\u00f4le."
}
@@ -1,7 +1,11 @@
"""Sensor platform for Advantage Air integration."""
import voluptuous as vol
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform
@@ -138,11 +142,11 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor."""
"""Representation of Advantage Air Zone temperature sensor."""
_attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_icon = "mdi:thermometer"
_attr_entity_registry_enabled_default = False
def __init__(self, instance, ac_key, zone_key):
+141 -115
View File
@@ -1,5 +1,7 @@
"""Constant values for the AEMET OpenData component."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@@ -40,9 +42,6 @@ DEFAULT_NAME = "AEMET"
DOMAIN = "aemet"
ENTRY_NAME = "name"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
SENSOR_NAME = "sensor_name"
SENSOR_UNIT = "sensor_unit"
SENSOR_DEVICE_CLASS = "sensor_device_class"
ATTR_API_CONDITION = "condition"
ATTR_API_FORECAST_DAILY = "forecast-daily"
@@ -200,118 +199,145 @@ FORECAST_MODE_ATTR_API = {
FORECAST_MODE_HOURLY: ATTR_API_FORECAST_HOURLY,
}
FORECAST_SENSOR_TYPES = {
ATTR_FORECAST_CONDITION: {
SENSOR_NAME: "Condition",
},
ATTR_FORECAST_PRECIPITATION: {
SENSOR_NAME: "Precipitation",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_FORECAST_PRECIPITATION_PROBABILITY: {
SENSOR_NAME: "Precipitation probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_FORECAST_TEMP: {
SENSOR_NAME: "Temperature",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_FORECAST_TEMP_LOW: {
SENSOR_NAME: "Temperature Low",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_FORECAST_TIME: {
SENSOR_NAME: "Time",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_FORECAST_WIND_BEARING: {
SENSOR_NAME: "Wind bearing",
SENSOR_UNIT: DEGREE,
},
ATTR_FORECAST_WIND_SPEED: {
SENSOR_NAME: "Wind speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
}
WEATHER_SENSOR_TYPES = {
ATTR_API_CONDITION: {
SENSOR_NAME: "Condition",
},
ATTR_API_HUMIDITY: {
SENSOR_NAME: "Humidity",
SENSOR_UNIT: PERCENTAGE,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
},
ATTR_API_PRESSURE: {
SENSOR_NAME: "Pressure",
SENSOR_UNIT: PRESSURE_HPA,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
},
ATTR_API_RAIN: {
SENSOR_NAME: "Rain",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_API_RAIN_PROB: {
SENSOR_NAME: "Rain probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_SNOW: {
SENSOR_NAME: "Snow",
SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR,
},
ATTR_API_SNOW_PROB: {
SENSOR_NAME: "Snow probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_STATION_ID: {
SENSOR_NAME: "Station ID",
},
ATTR_API_STATION_NAME: {
SENSOR_NAME: "Station name",
},
ATTR_API_STATION_TIMESTAMP: {
SENSOR_NAME: "Station timestamp",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_API_STORM_PROB: {
SENSOR_NAME: "Storm probability",
SENSOR_UNIT: PERCENTAGE,
},
ATTR_API_TEMPERATURE: {
SENSOR_NAME: "Temperature",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_API_TEMPERATURE_FEELING: {
SENSOR_NAME: "Temperature feeling",
SENSOR_UNIT: TEMP_CELSIUS,
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
},
ATTR_API_TOWN_ID: {
SENSOR_NAME: "Town ID",
},
ATTR_API_TOWN_NAME: {
SENSOR_NAME: "Town name",
},
ATTR_API_TOWN_TIMESTAMP: {
SENSOR_NAME: "Town timestamp",
SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP,
},
ATTR_API_WIND_BEARING: {
SENSOR_NAME: "Wind bearing",
SENSOR_UNIT: DEGREE,
},
ATTR_API_WIND_MAX_SPEED: {
SENSOR_NAME: "Wind max speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
ATTR_API_WIND_SPEED: {
SENSOR_NAME: "Wind speed",
SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR,
},
}
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_FORECAST_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION,
name="Precipitation",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION_PROBABILITY,
name="Precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP_LOW,
name="Temperature Low",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TIME,
name="Time",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
)
WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_API_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=ATTR_API_PRESSURE,
name="Pressure",
native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=ATTR_API_RAIN,
name="Rain",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_RAIN_PROB,
name="Rain probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_SNOW,
name="Snow",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_SNOW_PROB,
name="Snow probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_STATION_ID,
name="Station ID",
),
SensorEntityDescription(
key=ATTR_API_STATION_NAME,
name="Station name",
),
SensorEntityDescription(
key=ATTR_API_STATION_TIMESTAMP,
name="Station timestamp",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_API_STORM_PROB,
name="Storm probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_API_TOWN_ID,
name="Town ID",
),
SensorEntityDescription(
key=ATTR_API_TOWN_NAME,
name="Town name",
),
SensorEntityDescription(
key=ATTR_API_TOWN_TIMESTAMP,
name="Town timestamp",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,
name="Wind max speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_API_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),
)
WIND_BEARING_MAP = {
"C": None,
+41 -53
View File
@@ -1,5 +1,7 @@
"""Support for the AEMET OpenData service."""
from homeassistant.components.sensor import SensorEntity
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -14,9 +16,6 @@ from .const import (
FORECAST_MONITORED_CONDITIONS,
FORECAST_SENSOR_TYPES,
MONITORED_CONDITIONS,
SENSOR_DEVICE_CLASS,
SENSOR_NAME,
SENSOR_UNIT,
WEATHER_SENSOR_TYPES,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
@@ -28,37 +27,30 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
name = domain_data[ENTRY_NAME]
weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR]
weather_sensor_types = WEATHER_SENSOR_TYPES
forecast_sensor_types = FORECAST_SENSOR_TYPES
entities = []
for sensor_type in MONITORED_CONDITIONS:
unique_id = f"{config_entry.unique_id}-{sensor_type}"
entities.append(
AemetSensor(
name,
unique_id,
sensor_type,
weather_sensor_types[sensor_type],
unique_id = config_entry.unique_id
entities: list[AbstractAemetSensor] = [
AemetSensor(name, unique_id, weather_coordinator, description)
for description in WEATHER_SENSOR_TYPES
if description.key in MONITORED_CONDITIONS
]
entities.extend(
[
AemetForecastSensor(
name_prefix,
unique_id_prefix,
weather_coordinator,
mode,
description,
)
)
for mode in FORECAST_MODES:
name = f"{domain_data[ENTRY_NAME]} {mode}"
for sensor_type in FORECAST_MONITORED_CONDITIONS:
unique_id = f"{config_entry.unique_id}-forecast-{mode}-{sensor_type}"
entities.append(
AemetForecastSensor(
f"{name} Forecast",
unique_id,
sensor_type,
forecast_sensor_types[sensor_type],
weather_coordinator,
mode,
)
for mode in FORECAST_MODES
if (
(name_prefix := f"{domain_data[ENTRY_NAME]} {mode} Forecast")
and (unique_id_prefix := f"{unique_id}-forecast-{mode}")
)
for description in FORECAST_SENSOR_TYPES
if description.key in FORECAST_MONITORED_CONDITIONS
]
)
async_add_entities(entities)
@@ -72,20 +64,14 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
coordinator: WeatherUpdateCoordinator,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(coordinator)
self._name = name
self._unique_id = unique_id
self._sensor_type = sensor_type
self._sensor_name = sensor_configuration[SENSOR_NAME]
self._attr_name = f"{self._name} {self._sensor_name}"
self._attr_unique_id = self._unique_id
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
self._attr_native_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
self.entity_description = description
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = unique_id
class AemetSensor(AbstractAemetSensor):
@@ -95,20 +81,21 @@ class AemetSensor(AbstractAemetSensor):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
weather_coordinator: WeatherUpdateCoordinator,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(
name, unique_id, sensor_type, sensor_configuration, weather_coordinator
name=name,
unique_id=f"{unique_id}-{description.key}",
coordinator=weather_coordinator,
description=description,
)
self._weather_coordinator = weather_coordinator
@property
def native_value(self):
"""Return the state of the device."""
return self._weather_coordinator.data.get(self._sensor_type)
return self.coordinator.data.get(self.entity_description.key)
class AemetForecastSensor(AbstractAemetSensor):
@@ -118,16 +105,17 @@ class AemetForecastSensor(AbstractAemetSensor):
self,
name,
unique_id,
sensor_type,
sensor_configuration,
weather_coordinator: WeatherUpdateCoordinator,
forecast_mode,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(
name, unique_id, sensor_type, sensor_configuration, weather_coordinator
name=name,
unique_id=f"{unique_id}-{description.key}",
coordinator=weather_coordinator,
description=description,
)
self._weather_coordinator = weather_coordinator
self._forecast_mode = forecast_mode
self._attr_entity_registry_enabled_default = (
self._forecast_mode == FORECAST_MODE_DAILY
@@ -137,9 +125,9 @@ class AemetForecastSensor(AbstractAemetSensor):
def native_value(self):
"""Return the state of the device."""
forecast = None
forecasts = self._weather_coordinator.data.get(
forecasts = self.coordinator.data.get(
FORECAST_MODE_ATTR_API[self._forecast_mode]
)
if forecasts:
forecast = forecasts[0].get(self._sensor_type)
forecast = forecasts[0].get(self.entity_description.key)
return forecast
@@ -4,13 +4,13 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"already_in_progress": "La configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"cannot_connect": "\u00c9chec de connexion"
},
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP",
"host": "H\u00f4te",
"port": "Port"
},
"title": "Configurer l'agent DVR"
-70
View File
@@ -3,23 +3,6 @@ from __future__ import annotations
from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
PERCENTAGE,
PRESSURE_HPA,
TEMP_CELSIUS,
)
from .model import AirlySensorEntityDescription
ATTR_API_ADVICE: Final = "ADVICE"
ATTR_API_CAQI: Final = "CAQI"
ATTR_API_CAQI_DESCRIPTION: Final = "DESCRIPTION"
@@ -49,56 +32,3 @@ MANUFACTURER: Final = "Airly sp. z o.o."
MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription(
key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI,
native_unit_of_measurement="CAQI",
),
AirlySensorEntityDescription(
key=ATTR_API_PM1,
device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM25,
device_class=DEVICE_CLASS_PM25,
name="PM2.5",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM10,
device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(),
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
AirlySensorEntityDescription(
key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(),
native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(),
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
)
-14
View File
@@ -1,14 +0,0 @@
"""Type definitions for Airly integration."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from homeassistant.components.sensor import SensorEntityDescription
@dataclass
class AirlySensorEntityDescription(SensorEntityDescription):
"""Class describing Airly sensor entities."""
value: Callable = round
+87 -5
View File
@@ -1,11 +1,30 @@
"""Support for the Airly sensor service."""
from __future__ import annotations
from typing import Any, cast
from dataclasses import dataclass
from typing import Any, Callable, cast
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONF_NAME,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
PERCENTAGE,
PRESSURE_HPA,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -18,8 +37,12 @@ from .const import (
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
ATTR_API_HUMIDITY,
ATTR_API_PM1,
ATTR_API_PM10,
ATTR_API_PM25,
ATTR_API_PRESSURE,
ATTR_API_TEMPERATURE,
ATTR_DESCRIPTION,
ATTR_LEVEL,
ATTR_LIMIT,
@@ -28,15 +51,74 @@ from .const import (
DEFAULT_NAME,
DOMAIN,
MANUFACTURER,
SENSOR_TYPES,
SUFFIX_LIMIT,
SUFFIX_PERCENT,
)
from .model import AirlySensorEntityDescription
PARALLEL_UPDATES = 1
@dataclass
class AirlySensorEntityDescription(SensorEntityDescription):
"""Class describing Airly sensor entities."""
value: Callable = round
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription(
key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI,
native_unit_of_measurement="CAQI",
),
AirlySensorEntityDescription(
key=ATTR_API_PM1,
device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM25,
device_class=DEVICE_CLASS_PM25,
name="PM2.5",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM10,
device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(),
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
AirlySensorEntityDescription(
key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(),
native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(),
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "L'int\u00e9gration des coordonn\u00e9es d'Airly est d\u00e9j\u00e0 configur\u00e9."
"already_configured": "L'emplacement est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"invalid_api_key": "Cl\u00e9 API invalide",
@@ -13,7 +13,7 @@
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom de l'int\u00e9gration"
"name": "Nom"
},
"description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air Airly. Pour g\u00e9n\u00e9rer une cl\u00e9 API, rendez-vous sur https://developer.airly.eu/register.",
"title": "Airly"
+40 -39
View File
@@ -1,14 +1,15 @@
"""Support for the AirNow sensor service."""
from homeassistant.components.sensor import SensorEntity
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirNowDataUpdateCoordinator
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
@@ -22,69 +23,69 @@ from .const import (
ATTRIBUTION = "Data provided by AirNow"
ATTR_LABEL = "label"
ATTR_UNIT = "unit"
PARALLEL_UPDATES = 1
SENSOR_TYPES = {
ATTR_API_AQI: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_AQI,
ATTR_UNIT: "aqi",
},
ATTR_API_PM25: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_PM25,
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
},
ATTR_API_O3: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_O3,
ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION,
},
}
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_AQI,
icon="mdi:blur",
name=ATTR_API_AQI,
native_unit_of_measurement="aqi",
),
SensorEntityDescription(
key=ATTR_API_PM25,
icon="mdi:blur",
name=ATTR_API_PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
SensorEntityDescription(
key=ATTR_API_O3,
icon="mdi:blur",
name=ATTR_API_O3,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up AirNow sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
sensors = []
for sensor in SENSOR_TYPES:
sensors.append(AirNowSensor(coordinator, sensor))
entities = [AirNowSensor(coordinator, description) for description in SENSOR_TYPES]
async_add_entities(sensors, False)
async_add_entities(entities, False)
class AirNowSensor(CoordinatorEntity, SensorEntity):
"""Define an AirNow sensor."""
def __init__(self, coordinator, kind):
coordinator: AirNowDataUpdateCoordinator
def __init__(
self,
coordinator: AirNowDataUpdateCoordinator,
description: SensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.kind = kind
self.entity_description = description
self._state = None
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
self._attr_native_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
self._attr_name = f"AirNow {description.name}"
self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{description.key.lower()}"
)
@property
def native_value(self):
"""Return the state."""
self._state = self.coordinator.data[self.kind]
self._state = self.coordinator.data[self.entity_description.key]
return self._state
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self.kind == ATTR_API_AQI:
if self.entity_description.key == ATTR_API_AQI:
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[
ATTR_API_AQI_DESCRIPTION
]
@@ -4,7 +4,7 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec \u00e0 la connexion",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"invalid_location": "Aucun r\u00e9sultat trouv\u00e9 pour cet emplacement",
"unknown": "Erreur inattendue"
@@ -12,7 +12,7 @@
"step": {
"user": {
"data": {
"api_key": "Cl\u00e9 API",
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"radius": "Rayon d'action de la station (en miles, facultatif)"
@@ -0,0 +1,9 @@
{
"config": {
"step": {
"user": {
"title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 {intergration}."
}
}
}
}
@@ -0,0 +1,15 @@
{
"config": {
"error": {
"no_units": "No se pudo encontrar ning\u00fan grupo AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Anfitri\u00f3n"
},
"title": "Configura los detalles de conexi\u00f3n de tu AirTouch 4."
}
}
}
}
@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion"
},
"step": {
"user": {
"data": {
"host": "H\u00f4te"
}
}
}
}
}
@@ -13,7 +13,7 @@
"step": {
"geography_by_coords": {
"data": {
"api_key": "Clef d'API",
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude"
},
@@ -22,7 +22,7 @@
},
"geography_by_name": {
"data": {
"api_key": "Clef d'API",
"api_key": "Cl\u00e9 d'API",
"city": "Ville",
"country": "Pays",
"state": "Etat"
@@ -9,11 +9,11 @@
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bien",
"hazardous": "Peligroso",
"good": "Bueno",
"hazardous": "Da\u00f1ino",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Incorrecto para grupos sensibles",
"unhealthy_sensitive": "Insalubre para grupos sensibles",
"very_unhealthy": "Muy poco saludable"
}
}
@@ -1,12 +1,12 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Tlenek w\u0119gla",
"n2": "Dwutlenek azotu",
"o3": "Ozon",
"co": "tlenek w\u0119gla",
"n2": "dwutlenek azotu",
"o3": "ozon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Dwutlenek siarki"
"s2": "dwutlenek siarki"
},
"airvisual__pollutant_level": {
"good": "dobry",
@@ -11,7 +11,10 @@ from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
)
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
@@ -129,7 +132,7 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
if config[CONF_TYPE] == "triggered":
@@ -4,7 +4,7 @@
"arm_away": "Schakel {entity_name} in voor vertrek",
"arm_home": "Schakel {entity_name} in voor thuis",
"arm_night": "Schakel {entity_name} in voor 's nachts",
"arm_vacation": "Schakel {entity_name} in op vakantie",
"arm_vacation": "Schakel {entity_name} in voor vakantie",
"disarm": "Schakel {entity_name} uit",
"trigger": "Laat {entity_name} afgaan"
},
@@ -12,7 +12,7 @@
"is_armed_away": "{entity_name} ingeschakeld voor vertrek",
"is_armed_home": "{entity_name} ingeschakeld voor thuis",
"is_armed_night": "{entity_name} is ingeschakeld voor 's nachts",
"is_armed_vacation": "{entity_name} is in vakantie geschakeld",
"is_armed_vacation": "{entity_name} is ingeschakeld voor vakantie",
"is_disarmed": "{entity_name} is uitgeschakeld",
"is_triggered": "{entity_name} gaat af"
},
@@ -20,7 +20,7 @@
"armed_away": "{entity_name} ingeschakeld voor vertrek",
"armed_home": "{entity_name} ingeschakeld voor thuis",
"armed_night": "{entity_name} ingeschakeld voor 's nachts",
"armed_vacation": "{entity_name} schakelde vakantie in",
"armed_vacation": "{entity_name} schakelde in voor vakantie",
"disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} afgegaan"
}
@@ -40,5 +40,5 @@
"triggered": "Gaat af"
}
},
"title": "Alarm bedieningspaneel"
"title": "Alarmbedieningspaneel"
}
@@ -1483,16 +1483,6 @@ class AlexaRangeController(AlexaCapability):
if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
# Fan Speed
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed_list = self.entity.attributes.get(fan.ATTR_SPEED_LIST)
speed = self.entity.attributes.get(fan.ATTR_SPEED)
if speed_list is not None and speed is not None:
speed_index = next(
(i for i, v in enumerate(speed_list) if v == speed), None
)
return speed_index
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION)
@@ -535,10 +535,6 @@ class FanCapabilities(AlexaEntity):
if supported & fan.SUPPORT_SET_SPEED:
yield AlexaPercentageController(self.entity)
yield AlexaPowerLevelController(self.entity)
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
yield AlexaRangeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
)
if supported & fan.SUPPORT_OSCILLATE:
yield AlexaToggleController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
+2 -39
View File
@@ -1091,24 +1091,8 @@ async def async_api_set_range(hass, config, directive, context):
data = {ATTR_ENTITY_ID: entity.entity_id}
range_value = directive.payload["rangeValue"]
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_value = int(range_value)
service = fan.SERVICE_SET_SPEED
speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
speed = next((v for i, v in enumerate(speed_list) if i == range_value), None)
if not speed:
msg = "Entity does not support value"
raise AlexaInvalidValueError(msg)
if speed == fan.SPEED_OFF:
service = fan.SERVICE_TURN_OFF
data[fan.ATTR_SPEED] = speed
# Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_value = int(range_value)
if range_value == 0:
service = cover.SERVICE_CLOSE_COVER
@@ -1184,29 +1168,8 @@ async def async_api_adjust_range(hass, config, directive, context):
range_delta_default = bool(directive.payload["rangeValueDeltaDefault"])
response_value = 0
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_delta = int(range_delta)
service = fan.SERVICE_SET_SPEED
speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
current_speed = entity.attributes[fan.ATTR_SPEED]
current_speed_index = next(
(i for i, v in enumerate(speed_list) if v == current_speed), 0
)
new_speed_index = min(
len(speed_list) - 1, max(0, current_speed_index + range_delta)
)
speed = next(
(v for i, v in enumerate(speed_list) if i == new_speed_index), None
)
if speed == fan.SPEED_OFF:
service = fan.SERVICE_TURN_OFF
data[fan.ATTR_SPEED] = response_value = speed
# Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)
@@ -1,8 +1,8 @@
{
"config": {
"abort": {
"cannot_connect": "Impossible de se connecter au serveur Almond",
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond.",
"cannot_connect": "\u00c9chec de connexion",
"missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.",
"no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )",
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
},
@@ -1,22 +1,22 @@
{
"config": {
"abort": {
"reauth_successful": "R\u00e9-authentification r\u00e9ussie"
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_api_key": "Cl\u00e9 API non valide"
"invalid_api_key": "Cl\u00e9 API invalide"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "cl\u00e9 API",
"api_key": "Cl\u00e9 d'API",
"description": "R\u00e9-authentifiez-vous avec votre compte Ambee."
}
},
"user": {
"data": {
"api_key": "cl\u00e9 API",
"api_key": "Cl\u00e9 d'API",
"latitude": "Latitude",
"longitude": "Longitude",
"name": "Nom"
@@ -1,10 +1,10 @@
{
"state": {
"ambee__risk": {
"high": "Wysoki",
"low": "Niski",
"moderate": "Umiarkowany",
"very high": "Bardzo wysoki"
"high": "wysoki",
"low": "niski",
"moderate": "umiarkowany",
"very high": "bardzo wysoki"
}
}
}
@@ -6,7 +6,7 @@
"missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation."
},
"create_entry": {
"default": "Authentifi\u00e9 avec succ\u00e8s avec Ambiclimate"
"default": "Authentification r\u00e9ussie"
},
"error": {
"follow_link": "Veuillez suivre le lien et vous authentifier avant d'appuyer sur Soumettre.",
@@ -1,38 +1,17 @@
"""Support for Ambient Weather Station Service."""
from __future__ import annotations
from typing import Any
from aioambient import Client
from aioambient.errors import WebsocketError
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_CONNECTIVITY,
DOMAIN as BINARY_SENSOR,
)
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_LOCATION,
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONF_API_KEY,
DEGREE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
EVENT_HOMEASSISTANT_STOP,
IRRADIATION_WATTS_PER_SQUARE_METER,
LIGHT_LUX,
PERCENTAGE,
PRECIPITATION_INCHES,
PRECIPITATION_INCHES_PER_HOUR,
PRESSURE_INHG,
SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
@@ -41,266 +20,43 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.event import async_call_later
from .const import (
ATTR_LAST_DATA,
ATTR_MONITORED_CONDITIONS,
CONF_APP_KEY,
DATA_CLIENT,
DOMAIN,
LOGGER,
TYPE_SOLARRADIATION,
TYPE_SOLARRADIATION_LX,
)
PLATFORMS = [BINARY_SENSOR, SENSOR]
PLATFORMS = ["binary_sensor", "sensor"]
DATA_CONFIG = "config"
DEFAULT_SOCKET_MIN_RETRY = 15
TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin"
TYPE_BAROMRELIN = "baromrelin"
TYPE_BATT1 = "batt1"
TYPE_BATT10 = "batt10"
TYPE_BATT2 = "batt2"
TYPE_BATT3 = "batt3"
TYPE_BATT4 = "batt4"
TYPE_BATT5 = "batt5"
TYPE_BATT6 = "batt6"
TYPE_BATT7 = "batt7"
TYPE_BATT8 = "batt8"
TYPE_BATT9 = "batt9"
TYPE_BATT_CO2 = "batt_co2"
TYPE_BATTOUT = "battout"
TYPE_CO2 = "co2"
TYPE_DAILYRAININ = "dailyrainin"
TYPE_DEWPOINT = "dewPoint"
TYPE_EVENTRAININ = "eventrainin"
TYPE_FEELSLIKE = "feelsLike"
TYPE_HOURLYRAININ = "hourlyrainin"
TYPE_HUMIDITY = "humidity"
TYPE_HUMIDITY1 = "humidity1"
TYPE_HUMIDITY10 = "humidity10"
TYPE_HUMIDITY2 = "humidity2"
TYPE_HUMIDITY3 = "humidity3"
TYPE_HUMIDITY4 = "humidity4"
TYPE_HUMIDITY5 = "humidity5"
TYPE_HUMIDITY6 = "humidity6"
TYPE_HUMIDITY7 = "humidity7"
TYPE_HUMIDITY8 = "humidity8"
TYPE_HUMIDITY9 = "humidity9"
TYPE_HUMIDITYIN = "humidityin"
TYPE_LASTRAIN = "lastRain"
TYPE_MAXDAILYGUST = "maxdailygust"
TYPE_MONTHLYRAININ = "monthlyrainin"
TYPE_PM25 = "pm25"
TYPE_PM25_24H = "pm25_24h"
TYPE_PM25_BATT = "batt_25"
TYPE_PM25_IN = "pm25_in"
TYPE_PM25_IN_24H = "pm25_in_24h"
TYPE_PM25IN_BATT = "batt_25in"
TYPE_RELAY1 = "relay1"
TYPE_RELAY10 = "relay10"
TYPE_RELAY2 = "relay2"
TYPE_RELAY3 = "relay3"
TYPE_RELAY4 = "relay4"
TYPE_RELAY5 = "relay5"
TYPE_RELAY6 = "relay6"
TYPE_RELAY7 = "relay7"
TYPE_RELAY8 = "relay8"
TYPE_RELAY9 = "relay9"
TYPE_SOILHUM1 = "soilhum1"
TYPE_SOILHUM10 = "soilhum10"
TYPE_SOILHUM2 = "soilhum2"
TYPE_SOILHUM3 = "soilhum3"
TYPE_SOILHUM4 = "soilhum4"
TYPE_SOILHUM5 = "soilhum5"
TYPE_SOILHUM6 = "soilhum6"
TYPE_SOILHUM7 = "soilhum7"
TYPE_SOILHUM8 = "soilhum8"
TYPE_SOILHUM9 = "soilhum9"
TYPE_SOILTEMP1F = "soiltemp1f"
TYPE_SOILTEMP10F = "soiltemp10f"
TYPE_SOILTEMP2F = "soiltemp2f"
TYPE_SOILTEMP3F = "soiltemp3f"
TYPE_SOILTEMP4F = "soiltemp4f"
TYPE_SOILTEMP5F = "soiltemp5f"
TYPE_SOILTEMP6F = "soiltemp6f"
TYPE_SOILTEMP7F = "soiltemp7f"
TYPE_SOILTEMP8F = "soiltemp8f"
TYPE_SOILTEMP9F = "soiltemp9f"
TYPE_SOLARRADIATION = "solarradiation"
TYPE_SOLARRADIATION_LX = "solarradiation_lx"
TYPE_TEMP10F = "temp10f"
TYPE_TEMP1F = "temp1f"
TYPE_TEMP2F = "temp2f"
TYPE_TEMP3F = "temp3f"
TYPE_TEMP4F = "temp4f"
TYPE_TEMP5F = "temp5f"
TYPE_TEMP6F = "temp6f"
TYPE_TEMP7F = "temp7f"
TYPE_TEMP8F = "temp8f"
TYPE_TEMP9F = "temp9f"
TYPE_TEMPF = "tempf"
TYPE_TEMPINF = "tempinf"
TYPE_TOTALRAININ = "totalrainin"
TYPE_UV = "uv"
TYPE_WEEKLYRAININ = "weeklyrainin"
TYPE_WINDDIR = "winddir"
TYPE_WINDDIR_AVG10M = "winddir_avg10m"
TYPE_WINDDIR_AVG2M = "winddir_avg2m"
TYPE_WINDGUSTDIR = "windgustdir"
TYPE_WINDGUSTMPH = "windgustmph"
TYPE_WINDSPDMPH_AVG10M = "windspdmph_avg10m"
TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m"
TYPE_WINDSPEEDMPH = "windspeedmph"
TYPE_YEARLYRAININ = "yearlyrainin"
SENSOR_TYPES = {
TYPE_24HOURRAININ: ("24 Hr Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, SENSOR, DEVICE_CLASS_PRESSURE),
TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, SENSOR, DEVICE_CLASS_PRESSURE),
TYPE_BATT10: ("Battery 10", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT1: ("Battery 1", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT2: ("Battery 2", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT3: ("Battery 3", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT4: ("Battery 4", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT5: ("Battery 5", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT6: ("Battery 6", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT7: ("Battery 7", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_BATT_CO2: ("CO2 Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, DEVICE_CLASS_CO2),
TYPE_DAILYRAININ: ("Daily Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_EVENTRAININ: ("Event Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_HOURLYRAININ: (
"Hourly Rain Rate",
PRECIPITATION_INCHES_PER_HOUR,
SENSOR,
None,
),
TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITY: ("Humidity", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_LASTRAIN: ("Last Rain", None, SENSOR, DEVICE_CLASS_TIMESTAMP),
TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_MONTHLYRAININ: ("Monthly Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_PM25_24H: (
"PM25 24h Avg",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25_BATT: ("PM25 Battery", None, BINARY_SENSOR, DEVICE_CLASS_BATTERY),
TYPE_PM25_IN: (
"PM25 Indoor",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25_IN_24H: (
"PM25 Indoor 24h Avg",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SENSOR,
None,
),
TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, SENSOR, None),
TYPE_PM25IN_BATT: (
"PM25 Indoor Battery",
None,
BINARY_SENSOR,
DEVICE_CLASS_BATTERY,
),
TYPE_RELAY10: ("Relay 10", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY1: ("Relay 1", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY2: ("Relay 2", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY3: ("Relay 3", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY4: ("Relay 4", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY5: ("Relay 5", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY6: ("Relay 6", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY7: ("Relay 7", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY8: ("Relay 8", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_RELAY9: ("Relay 9", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY),
TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, SENSOR, DEVICE_CLASS_HUMIDITY),
TYPE_SOILTEMP10F: (
"Soil Temp 10",
TEMP_FAHRENHEIT,
SENSOR,
DEVICE_CLASS_TEMPERATURE,
),
TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_SOLARRADIATION: (
"Solar Rad",
IRRADIATION_WATTS_PER_SQUARE_METER,
SENSOR,
None,
),
TYPE_SOLARRADIATION_LX: (
"Solar Rad (lx)",
LIGHT_LUX,
SENSOR,
DEVICE_CLASS_ILLUMINANCE,
),
TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, SENSOR, DEVICE_CLASS_TEMPERATURE),
TYPE_TOTALRAININ: ("Lifetime Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_UV: ("uv", "Index", SENSOR, None),
TYPE_WEEKLYRAININ: ("Weekly Rain", PRECIPITATION_INCHES, SENSOR, None),
TYPE_WINDDIR: ("Wind Dir", DEGREE, SENSOR, None),
TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, SENSOR, None),
TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, SENSOR, None),
TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, SENSOR, None),
TYPE_YEARLYRAININ: ("Yearly Rain", PRECIPITATION_INCHES, SENSOR, None),
}
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
@callback
def async_wm2_to_lx(value: float) -> int:
"""Calculate illuminance (in lux)."""
return round(value / 0.0079)
@callback
def async_hydrate_station_data(data: dict[str, Any]) -> dict[str, Any]:
"""Hydrate station data with addition or normalized data."""
if (irradiation := data.get(TYPE_SOLARRADIATION)) is not None:
data[TYPE_SOLARRADIATION_LX] = async_wm2_to_lx(irradiation)
return data
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the Ambient PWS as config entry."""
hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}})
@@ -319,6 +75,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY],
session=session,
logger=LOGGER,
),
)
hass.loop.create_task(ambient.ws_connect())
@@ -405,13 +162,14 @@ class AmbientStation:
def on_data(data: dict) -> None:
"""Define a handler to fire when the data is received."""
mac_address = data["macAddress"]
if data != self.stations[mac_address][ATTR_LAST_DATA]:
LOGGER.debug("New data received: %s", data)
self.stations[mac_address][ATTR_LAST_DATA] = data
async_dispatcher_send(
self._hass, f"ambient_station_data_update_{mac_address}"
)
mac = data["macAddress"]
if data == self.stations[mac][ATTR_LAST_DATA]:
return
LOGGER.debug("New data received: %s", data)
self.stations[mac][ATTR_LAST_DATA] = async_hydrate_station_data(data)
async_dispatcher_send(self._hass, f"ambient_station_data_update_{mac}")
def on_disconnect() -> None:
"""Define a handler to fire when the websocket is disconnected."""
@@ -420,26 +178,19 @@ class AmbientStation:
def on_subscribed(data: dict) -> None:
"""Define a handler to fire when the subscription is set."""
for station in data["devices"]:
if station["macAddress"] in self.stations:
mac = station["macAddress"]
if mac in self.stations:
continue
LOGGER.debug("New station subscription: %s", data)
# Only create entities based on the data coming through the socket.
# If the user is monitoring brightness (in W/m^2), make sure we also
# add a calculated sensor for the same data measured in lx:
monitored_conditions = [
k for k in station["lastData"] if k in SENSOR_TYPES
]
if TYPE_SOLARRADIATION in monitored_conditions:
monitored_conditions.append(TYPE_SOLARRADIATION_LX)
self.stations[station["macAddress"]] = {
ATTR_LAST_DATA: station["lastData"],
self.stations[mac] = {
ATTR_LAST_DATA: async_hydrate_station_data(station["lastData"]),
ATTR_LOCATION: station.get("info", {}).get("location"),
ATTR_MONITORED_CONDITIONS: monitored_conditions,
ATTR_NAME: station.get("info", {}).get(
"name", station["macAddress"]
),
ATTR_NAME: station.get("info", {}).get("name", mac),
}
# If the websocket disconnects and reconnects, the on_subscribed
# handler will get called again; in that case, we don't want to
# attempt forward setup of the config entry (because it will have
@@ -466,28 +217,26 @@ class AmbientStation:
class AmbientWeatherEntity(Entity):
"""Define a base Ambient PWS entity."""
_attr_should_poll = False
def __init__(
self,
ambient: AmbientStation,
mac_address: str,
station_name: str,
sensor_type: str,
sensor_name: str,
device_class: str | None,
description: EntityDescription,
) -> None:
"""Initialize the sensor."""
self._ambient = ambient
self._attr_device_class = device_class
self._attr_device_info = {
"identifiers": {(DOMAIN, mac_address)},
"name": station_name,
"manufacturer": "Ambient Weather",
}
self._attr_name = f"{station_name}_{sensor_name}"
self._attr_should_poll = False
self._attr_unique_id = f"{mac_address}_{sensor_type}"
self._attr_name = f"{station_name}_{description.name}"
self._attr_unique_id = f"{mac_address}_{description.key}"
self._mac_address = mac_address
self._sensor_type = sensor_type
self.entity_description = description
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
@@ -495,18 +244,18 @@ class AmbientWeatherEntity(Entity):
@callback
def update() -> None:
"""Update the state."""
if self._sensor_type == TYPE_SOLARRADIATION_LX:
if self.entity_description.key == TYPE_SOLARRADIATION_LX:
self._attr_available = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
TYPE_SOLARRADIATION
)
]
is not None
)
else:
self._attr_available = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._sensor_type
)
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
self.entity_description.key
]
is not None
)
@@ -1,34 +1,209 @@
"""Support for Ambient Weather Station binary sensors."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CONNECTIVITY,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import (
SENSOR_TYPES,
TYPE_BATT1,
TYPE_BATT2,
TYPE_BATT3,
TYPE_BATT4,
TYPE_BATT5,
TYPE_BATT6,
TYPE_BATT7,
TYPE_BATT8,
TYPE_BATT9,
TYPE_BATT10,
TYPE_BATT_CO2,
TYPE_BATTOUT,
TYPE_PM25_BATT,
TYPE_PM25IN_BATT,
AmbientWeatherEntity,
from . import AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN
TYPE_BATT1 = "batt1"
TYPE_BATT10 = "batt10"
TYPE_BATT2 = "batt2"
TYPE_BATT3 = "batt3"
TYPE_BATT4 = "batt4"
TYPE_BATT5 = "batt5"
TYPE_BATT6 = "batt6"
TYPE_BATT7 = "batt7"
TYPE_BATT8 = "batt8"
TYPE_BATT9 = "batt9"
TYPE_BATT_CO2 = "batt_co2"
TYPE_BATTOUT = "battout"
TYPE_PM25_BATT = "batt_25"
TYPE_PM25IN_BATT = "batt_25in"
TYPE_RELAY1 = "relay1"
TYPE_RELAY10 = "relay10"
TYPE_RELAY2 = "relay2"
TYPE_RELAY3 = "relay3"
TYPE_RELAY4 = "relay4"
TYPE_RELAY5 = "relay5"
TYPE_RELAY6 = "relay6"
TYPE_RELAY7 = "relay7"
TYPE_RELAY8 = "relay8"
TYPE_RELAY9 = "relay9"
@dataclass
class AmbientBinarySensorDescriptionMixin:
"""Define an entity description mixin for binary sensors."""
on_state: Literal[0, 1]
@dataclass
class AmbientBinarySensorDescription(
BinarySensorEntityDescription, AmbientBinarySensorDescriptionMixin
):
"""Describe an Ambient PWS binary sensor."""
BINARY_SENSOR_DESCRIPTIONS = (
AmbientBinarySensorDescription(
key=TYPE_BATTOUT,
name="Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT1,
name="Battery 1",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT2,
name="Battery 2",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT3,
name="Battery 3",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT4,
name="Battery 4",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT5,
name="Battery 5",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT6,
name="Battery 6",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT7,
name="Battery 7",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT8,
name="Battery 8",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT9,
name="Battery 9",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT10,
name="Battery 10",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_BATT_CO2,
name="CO2 Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_PM25IN_BATT,
name="PM25 Indoor Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_PM25_BATT,
name="PM25 Battery",
device_class=DEVICE_CLASS_BATTERY,
on_state=0,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY1,
name="Relay 1",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY2,
name="Relay 2",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY3,
name="Relay 3",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY4,
name="Relay 4",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY5,
name="Relay 5",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY6,
name="Relay 6",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY7,
name="Relay 7",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY8,
name="Relay 8",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY9,
name="Relay 9",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
AmbientBinarySensorDescription(
key=TYPE_RELAY10,
name="Relay 10",
device_class=DEVICE_CLASS_CONNECTIVITY,
on_state=1,
),
)
from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN
async def async_setup_entry(
@@ -37,51 +212,29 @@ async def async_setup_entry(
"""Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
binary_sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in station[ATTR_MONITORED_CONDITIONS]:
name, _, kind, device_class = SENSOR_TYPES[condition]
if kind == BINARY_SENSOR:
binary_sensor_list.append(
AmbientWeatherBinarySensor(
ambient,
mac_address,
station[ATTR_NAME],
condition,
name,
device_class,
)
)
async_add_entities(binary_sensor_list)
async_add_entities(
[
AmbientWeatherBinarySensor(
ambient, mac_address, station[ATTR_NAME], description
)
for mac_address, station in ambient.stations.items()
for description in BINARY_SENSOR_DESCRIPTIONS
if description.key in station[ATTR_LAST_DATA]
]
)
class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorEntity):
"""Define an Ambient binary sensor."""
entity_description: AmbientBinarySensorDescription
@callback
def update_from_latest_data(self) -> None:
"""Fetch new state data for the entity."""
state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._sensor_type
self._attr_is_on = (
self._ambient.stations[self._mac_address][ATTR_LAST_DATA][
self.entity_description.key
]
== self.entity_description.on_state
)
if self._sensor_type in (
TYPE_BATT1,
TYPE_BATT10,
TYPE_BATT2,
TYPE_BATT3,
TYPE_BATT4,
TYPE_BATT5,
TYPE_BATT6,
TYPE_BATT7,
TYPE_BATT8,
TYPE_BATT9,
TYPE_BATT_CO2,
TYPE_BATTOUT,
TYPE_PM25_BATT,
TYPE_PM25IN_BATT,
):
self._attr_is_on = state == 0
else:
self._attr_is_on = state == 1
@@ -5,8 +5,10 @@ DOMAIN = "ambient_station"
LOGGER = logging.getLogger(__package__)
ATTR_LAST_DATA = "last_data"
ATTR_MONITORED_CONDITIONS = "monitored_conditions"
CONF_APP_KEY = "app_key"
DATA_CLIENT = "data_client"
TYPE_SOLARRADIATION = "solarradiation"
TYPE_SOLARRADIATION_LX = "solarradiation_lx"
@@ -3,7 +3,7 @@
"name": "Ambient Weather Station",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambient_station",
"requirements": ["aioambient==1.2.6"],
"requirements": ["aioambient==1.3.0"],
"codeowners": ["@bachya"],
"iot_class": "cloud_push"
}
@@ -1,20 +1,554 @@
"""Support for Ambient Weather Station sensors."""
from __future__ import annotations
from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME
from homeassistant.const import (
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEGREE,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
IRRADIATION_WATTS_PER_SQUARE_METER,
LIGHT_LUX,
PERCENTAGE,
PRECIPITATION_INCHES,
PRECIPITATION_INCHES_PER_HOUR,
PRESSURE_INHG,
SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import (
SENSOR_TYPES,
TYPE_SOLARRADIATION,
TYPE_SOLARRADIATION_LX,
AmbientStation,
AmbientWeatherEntity,
from . import TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX, AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN
TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin"
TYPE_BAROMRELIN = "baromrelin"
TYPE_CO2 = "co2"
TYPE_DAILYRAININ = "dailyrainin"
TYPE_DEWPOINT = "dewPoint"
TYPE_EVENTRAININ = "eventrainin"
TYPE_FEELSLIKE = "feelsLike"
TYPE_HOURLYRAININ = "hourlyrainin"
TYPE_HUMIDITY = "humidity"
TYPE_HUMIDITY1 = "humidity1"
TYPE_HUMIDITY10 = "humidity10"
TYPE_HUMIDITY2 = "humidity2"
TYPE_HUMIDITY3 = "humidity3"
TYPE_HUMIDITY4 = "humidity4"
TYPE_HUMIDITY5 = "humidity5"
TYPE_HUMIDITY6 = "humidity6"
TYPE_HUMIDITY7 = "humidity7"
TYPE_HUMIDITY8 = "humidity8"
TYPE_HUMIDITY9 = "humidity9"
TYPE_HUMIDITYIN = "humidityin"
TYPE_LASTRAIN = "lastRain"
TYPE_MAXDAILYGUST = "maxdailygust"
TYPE_MONTHLYRAININ = "monthlyrainin"
TYPE_PM25 = "pm25"
TYPE_PM25_24H = "pm25_24h"
TYPE_PM25_IN = "pm25_in"
TYPE_PM25_IN_24H = "pm25_in_24h"
TYPE_SOILHUM1 = "soilhum1"
TYPE_SOILHUM10 = "soilhum10"
TYPE_SOILHUM2 = "soilhum2"
TYPE_SOILHUM3 = "soilhum3"
TYPE_SOILHUM4 = "soilhum4"
TYPE_SOILHUM5 = "soilhum5"
TYPE_SOILHUM6 = "soilhum6"
TYPE_SOILHUM7 = "soilhum7"
TYPE_SOILHUM8 = "soilhum8"
TYPE_SOILHUM9 = "soilhum9"
TYPE_SOILTEMP1F = "soiltemp1f"
TYPE_SOILTEMP10F = "soiltemp10f"
TYPE_SOILTEMP2F = "soiltemp2f"
TYPE_SOILTEMP3F = "soiltemp3f"
TYPE_SOILTEMP4F = "soiltemp4f"
TYPE_SOILTEMP5F = "soiltemp5f"
TYPE_SOILTEMP6F = "soiltemp6f"
TYPE_SOILTEMP7F = "soiltemp7f"
TYPE_SOILTEMP8F = "soiltemp8f"
TYPE_SOILTEMP9F = "soiltemp9f"
TYPE_TEMP10F = "temp10f"
TYPE_TEMP1F = "temp1f"
TYPE_TEMP2F = "temp2f"
TYPE_TEMP3F = "temp3f"
TYPE_TEMP4F = "temp4f"
TYPE_TEMP5F = "temp5f"
TYPE_TEMP6F = "temp6f"
TYPE_TEMP7F = "temp7f"
TYPE_TEMP8F = "temp8f"
TYPE_TEMP9F = "temp9f"
TYPE_TEMPF = "tempf"
TYPE_TEMPINF = "tempinf"
TYPE_TOTALRAININ = "totalrainin"
TYPE_UV = "uv"
TYPE_WEEKLYRAININ = "weeklyrainin"
TYPE_WINDDIR = "winddir"
TYPE_WINDDIR_AVG10M = "winddir_avg10m"
TYPE_WINDDIR_AVG2M = "winddir_avg2m"
TYPE_WINDGUSTDIR = "windgustdir"
TYPE_WINDGUSTMPH = "windgustmph"
TYPE_WINDSPDMPH_AVG10M = "windspdmph_avg10m"
TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m"
TYPE_WINDSPEEDMPH = "windspeedmph"
TYPE_YEARLYRAININ = "yearlyrainin"
SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_24HOURRAININ,
name="24 Hr Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_BAROMABSIN,
name="Abs Pressure",
native_unit_of_measurement=PRESSURE_INHG,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=TYPE_BAROMRELIN,
name="Rel Pressure",
native_unit_of_measurement=PRESSURE_INHG,
device_class=DEVICE_CLASS_PRESSURE,
),
SensorEntityDescription(
key=TYPE_CO2,
name="co2",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=DEVICE_CLASS_CO2,
),
SensorEntityDescription(
key=TYPE_DAILYRAININ,
name="Daily Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_DEWPOINT,
name="Dew Point",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_EVENTRAININ,
name="Event Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_FEELSLIKE,
name="Feels Like",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_HOURLYRAININ,
name="Hourly Rain Rate",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_HUMIDITY10,
name="Humidity 10",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY1,
name="Humidity 1",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY2,
name="Humidity 2",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY3,
name="Humidity 3",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY4,
name="Humidity 4",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY5,
name="Humidity 5",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY6,
name="Humidity 6",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY7,
name="Humidity 7",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY8,
name="Humidity 8",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY9,
name="Humidity 9",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_HUMIDITYIN,
name="Humidity In",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_LASTRAIN,
name="Last Rain",
icon="mdi:water",
device_class=DEVICE_CLASS_TIMESTAMP,
),
SensorEntityDescription(
key=TYPE_MAXDAILYGUST,
name="Max Gust",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_MONTHLYRAININ,
name="Monthly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_PM25_24H,
name="PM25 24h Avg",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25_IN,
name="PM25 Indoor",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25_IN_24H,
name="PM25 Indoor 24h Avg",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_PM25,
name="PM25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=DEVICE_CLASS_PM25,
),
SensorEntityDescription(
key=TYPE_SOILHUM10,
name="Soil Humidity 10",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM1,
name="Soil Humidity 1",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM2,
name="Soil Humidity 2",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM3,
name="Soil Humidity 3",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM4,
name="Soil Humidity 4",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM5,
name="Soil Humidity 5",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM6,
name="Soil Humidity 6",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM7,
name="Soil Humidity 7",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM8,
name="Soil Humidity 8",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILHUM9,
name="Soil Humidity 9",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=TYPE_SOILTEMP10F,
name="Soil Temp 10",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP1F,
name="Soil Temp 1",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP2F,
name="Soil Temp 2",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP3F,
name="Soil Temp 3",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP4F,
name="Soil Temp 4",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP5F,
name="Soil Temp 5",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP6F,
name="Soil Temp 6",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP7F,
name="Soil Temp 7",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP8F,
name="Soil Temp 8",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOILTEMP9F,
name="Soil Temp 9",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_SOLARRADIATION,
name="Solar Rad",
native_unit_of_measurement=IRRADIATION_WATTS_PER_SQUARE_METER,
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_SOLARRADIATION_LX,
name="Solar Rad (lx)",
native_unit_of_measurement=LIGHT_LUX,
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_TEMP10F,
name="Temp 10",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP1F,
name="Temp 1",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP2F,
name="Temp 2",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP3F,
name="Temp 3",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP4F,
name="Temp 4",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP5F,
name="Temp 5",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP6F,
name="Temp 6",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP7F,
name="Temp 7",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP8F,
name="Temp 8",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMP9F,
name="Temp 9",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMPF,
name="Temp",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TEMPINF,
name="Inside Temp",
native_unit_of_measurement=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=TYPE_TOTALRAININ,
name="Lifetime Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_UV,
name="UV Index",
native_unit_of_measurement="Index",
device_class=DEVICE_CLASS_ILLUMINANCE,
),
SensorEntityDescription(
key=TYPE_WEEKLYRAININ,
name="Weekly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
SensorEntityDescription(
key=TYPE_WINDDIR,
name="Wind Dir",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG10M,
name="Wind Dir Avg 10m",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDDIR_AVG2M,
name="Wind Dir Avg 2m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDGUSTDIR,
name="Gust Dir",
icon="mdi:weather-windy",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=TYPE_WINDGUSTMPH,
name="Wind Gust",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPDMPH_AVG10M,
name="Wind Avg 10m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPDMPH_AVG2M,
name="Wind Avg 2m",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_WINDSPEEDMPH,
name="Wind Speed",
icon="mdi:weather-windy",
native_unit_of_measurement=SPEED_MILES_PER_HOUR,
),
SensorEntityDescription(
key=TYPE_YEARLYRAININ,
name="Yearly Rain",
icon="mdi:water",
native_unit_of_measurement=PRECIPITATION_INCHES,
),
)
from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN
async def async_setup_entry(
@@ -23,62 +557,22 @@ async def async_setup_entry(
"""Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in station[ATTR_MONITORED_CONDITIONS]:
name, unit, kind, device_class = SENSOR_TYPES[condition]
if kind == SENSOR:
sensor_list.append(
AmbientWeatherSensor(
ambient,
mac_address,
station[ATTR_NAME],
condition,
name,
device_class,
unit,
)
)
async_add_entities(sensor_list)
async_add_entities(
[
AmbientWeatherSensor(ambient, mac_address, station[ATTR_NAME], description)
for mac_address, station in ambient.stations.items()
for description in SENSOR_DESCRIPTIONS
if description.key in station[ATTR_LAST_DATA]
]
)
class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
"""Define an Ambient sensor."""
def __init__(
self,
ambient: AmbientStation,
mac_address: str,
station_name: str,
sensor_type: str,
sensor_name: str,
device_class: str | None,
unit: str | None,
) -> None:
"""Initialize the sensor."""
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name, device_class
)
self._attr_native_unit_of_measurement = unit
@callback
def update_from_latest_data(self) -> None:
"""Fetch new state data for the sensor."""
if self._sensor_type == TYPE_SOLARRADIATION_LX:
# If the user requests the solarradiation_lx sensor, use the
# value of the solarradiation sensor and apply a very accurate
# approximation of converting sunlight W/m^2 to lx:
w_m2_brightness_val = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
].get(TYPE_SOLARRADIATION)
if w_m2_brightness_val is None:
self._attr_native_value = None
else:
self._attr_native_value = round(float(w_m2_brightness_val) / 0.0079)
else:
self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
].get(self._sensor_type)
self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
][self.entity_description.key]
@@ -1,10 +1,10 @@
{
"config": {
"abort": {
"already_configured": "Cette cl\u00e9 d'application est d\u00e9j\u00e0 utilis\u00e9e."
"already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"invalid_key": "Cl\u00e9 d'API et / ou cl\u00e9 d'application non valide",
"invalid_key": "Cl\u00e9 API invalide",
"no_devices": "Aucun appareil trouv\u00e9 dans le compte"
},
"step": {
@@ -232,8 +232,6 @@ class AmcrestBinarySensor(BinarySensorEntity):
async def async_added_to_hass(self) -> None:
"""Subscribe to signals."""
assert self.hass is not None
self._unsub_dispatcher.append(
async_dispatcher_connect(
self.hass,
+4 -19
View File
@@ -180,7 +180,6 @@ class AmcrestCam(Camera):
raise CannotSnapshot
async def _async_get_image(self) -> None:
assert self.hass is not None
try:
# Send the request to snap a picture and return raw jpg data
# Snapshot command needs a much longer read timeout than other commands.
@@ -201,7 +200,6 @@ class AmcrestCam(Camera):
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera."""
assert self.hass is not None
_LOGGER.debug("Take snapshot from %s", self._name)
try:
# Amcrest cameras only support one snapshot command at a time.
@@ -226,7 +224,6 @@ class AmcrestCam(Camera):
self, request: web.Request
) -> web.StreamResponse | None:
"""Return an MJPEG stream."""
assert self.hass is not None
# The snapshot implementation is handled by the parent class
if self._stream_source == "snapshot":
return await super().handle_async_mjpeg_stream(request)
@@ -344,7 +341,6 @@ class AmcrestCam(Camera):
async def async_added_to_hass(self) -> None:
"""Subscribe to signals and add camera to list."""
assert self.hass is not None
self._unsub_dispatcher.extend(
async_dispatcher_connect(
self.hass,
@@ -364,7 +360,6 @@ class AmcrestCam(Camera):
async def async_will_remove_from_hass(self) -> None:
"""Remove camera from list and disconnect from signals."""
assert self.hass is not None
self.hass.data[DATA_AMCREST][CAMERAS].remove(self.entity_id)
for unsub_dispatcher in self._unsub_dispatcher:
unsub_dispatcher()
@@ -379,15 +374,16 @@ class AmcrestCam(Camera):
try:
if self._brand is None:
resp = self._api.vendor_information.strip()
_LOGGER.debug("Assigned brand=%s", resp)
if resp.startswith("vendor="):
self._brand = resp.split("=")[-1]
else:
self._brand = "unknown"
if self._model is None:
resp = self._api.device_type.strip()
_LOGGER.debug("Device_type=%s", resp)
if resp.startswith("type="):
self._model = resp.split("=")[-1]
_LOGGER.debug("Assigned model=%s", resp)
if resp:
self._model = resp
else:
self._model = "unknown"
if self._attr_unique_id is None:
@@ -428,57 +424,46 @@ class AmcrestCam(Camera):
async def async_enable_recording(self) -> None:
"""Call the job and enable recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_recording, True)
async def async_disable_recording(self) -> None:
"""Call the job and disable recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_recording, False)
async def async_enable_audio(self) -> None:
"""Call the job and enable audio."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_audio, True)
async def async_disable_audio(self) -> None:
"""Call the job and disable audio."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_audio, False)
async def async_enable_motion_recording(self) -> None:
"""Call the job and enable motion recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_motion_recording, True)
async def async_disable_motion_recording(self) -> None:
"""Call the job and disable motion recording."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._enable_motion_recording, False)
async def async_goto_preset(self, preset: int) -> None:
"""Call the job and move camera to preset position."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._goto_preset, preset)
async def async_set_color_bw(self, color_bw: str) -> None:
"""Call the job and set camera color mode."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._set_color_bw, color_bw)
async def async_start_tour(self) -> None:
"""Call the job and start camera tour."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._start_tour, True)
async def async_stop_tour(self) -> None:
"""Call the job and stop camera tour."""
assert self.hass is not None
await self.hass.async_add_executor_job(self._start_tour, False)
async def async_ptz_control(self, movement: str, travel_time: float) -> None:
"""Move or zoom camera in specified direction."""
assert self.hass is not None
code = _ACTION[_MOV.index(movement)]
kwargs = {"code": code, "arg1": 0, "arg2": 0, "arg3": 0}
@@ -129,7 +129,6 @@ class AmcrestSensor(SensorEntity):
async def async_added_to_hass(self) -> None:
"""Subscribe to update signal."""
assert self.hass is not None
self._unsub_dispatcher = async_dispatcher_connect(
self.hass,
service_signal(SERVICE_UPDATE, self._signal_name),
+378 -95
View File
@@ -1,10 +1,16 @@
"""Support for APCUPSd sensors."""
from __future__ import annotations
import logging
from apcaccess.status import ALL_UNITS
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import (
CONF_RESOURCES,
DEVICE_CLASS_TEMPERATURE,
@@ -25,74 +31,360 @@ from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
SENSOR_PREFIX = "UPS "
SENSOR_TYPES = {
"alarmdel": ["Alarm Delay", None, "mdi:alarm", None],
"ambtemp": ["Ambient Temperature", None, "mdi:thermometer", None],
"apc": ["Status Data", None, "mdi:information-outline", None],
"apcmodel": ["Model", None, "mdi:information-outline", None],
"badbatts": ["Bad Batteries", None, "mdi:information-outline", None],
"battdate": ["Battery Replaced", None, "mdi:calendar-clock", None],
"battstat": ["Battery Status", None, "mdi:information-outline", None],
"battv": ["Battery Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"bcharge": ["Battery", PERCENTAGE, "mdi:battery", None],
"cable": ["Cable Type", None, "mdi:ethernet-cable", None],
"cumonbatt": ["Total Time on Battery", None, "mdi:timer-outline", None],
"date": ["Status Date", None, "mdi:calendar-clock", None],
"dipsw": ["Dip Switch Settings", None, "mdi:information-outline", None],
"dlowbatt": ["Low Battery Signal", None, "mdi:clock-alert", None],
"driver": ["Driver", None, "mdi:information-outline", None],
"dshutd": ["Shutdown Delay", None, "mdi:timer-outline", None],
"dwake": ["Wake Delay", None, "mdi:timer-outline", None],
"endapc": ["Date and Time", None, "mdi:calendar-clock", None],
"extbatts": ["External Batteries", None, "mdi:information-outline", None],
"firmware": ["Firmware Version", None, "mdi:information-outline", None],
"hitrans": ["Transfer High", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"hostname": ["Hostname", None, "mdi:information-outline", None],
"humidity": ["Ambient Humidity", PERCENTAGE, "mdi:water-percent", None],
"itemp": ["Internal Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE],
"lastxfer": ["Last Transfer", None, "mdi:transfer", None],
"linefail": ["Input Voltage Status", None, "mdi:information-outline", None],
"linefreq": ["Line Frequency", FREQUENCY_HERTZ, "mdi:information-outline", None],
"linev": ["Input Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"loadpct": ["Load", PERCENTAGE, "mdi:gauge", None],
"loadapnt": ["Load Apparent Power", PERCENTAGE, "mdi:gauge", None],
"lotrans": ["Transfer Low", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"mandate": ["Manufacture Date", None, "mdi:calendar", None],
"masterupd": ["Master Update", None, "mdi:information-outline", None],
"maxlinev": ["Input Voltage High", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"maxtime": ["Battery Timeout", None, "mdi:timer-off-outline", None],
"mbattchg": ["Battery Shutdown", PERCENTAGE, "mdi:battery-alert", None],
"minlinev": ["Input Voltage Low", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"mintimel": ["Shutdown Time", None, "mdi:timer-outline", None],
"model": ["Model", None, "mdi:information-outline", None],
"nombattv": ["Battery Nominal Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nominv": ["Nominal Input Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nomoutv": ["Nominal Output Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"nompower": ["Nominal Output Power", POWER_WATT, "mdi:flash", None],
"nomapnt": ["Nominal Apparent Power", POWER_VOLT_AMPERE, "mdi:flash", None],
"numxfers": ["Transfer Count", None, "mdi:counter", None],
"outcurnt": ["Output Current", ELECTRIC_CURRENT_AMPERE, "mdi:flash", None],
"outputv": ["Output Voltage", ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None],
"reg1": ["Register 1 Fault", None, "mdi:information-outline", None],
"reg2": ["Register 2 Fault", None, "mdi:information-outline", None],
"reg3": ["Register 3 Fault", None, "mdi:information-outline", None],
"retpct": ["Restore Requirement", PERCENTAGE, "mdi:battery-alert", None],
"selftest": ["Last Self Test", None, "mdi:calendar-clock", None],
"sense": ["Sensitivity", None, "mdi:information-outline", None],
"serialno": ["Serial Number", None, "mdi:information-outline", None],
"starttime": ["Startup Time", None, "mdi:calendar-clock", None],
"statflag": ["Status Flag", None, "mdi:information-outline", None],
"status": ["Status", None, "mdi:information-outline", None],
"stesti": ["Self Test Interval", None, "mdi:information-outline", None],
"timeleft": ["Time Left", None, "mdi:clock-alert", None],
"tonbatt": ["Time on Battery", None, "mdi:timer-outline", None],
"upsmode": ["Mode", None, "mdi:information-outline", None],
"upsname": ["Name", None, "mdi:information-outline", None],
"version": ["Daemon Info", None, "mdi:information-outline", None],
"xoffbat": ["Transfer from Battery", None, "mdi:transfer", None],
"xoffbatt": ["Transfer from Battery", None, "mdi:transfer", None],
"xonbatt": ["Transfer to Battery", None, "mdi:transfer", None],
}
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="alarmdel",
name="Alarm Delay",
icon="mdi:alarm",
),
SensorEntityDescription(
key="ambtemp",
name="Ambient Temperature",
icon="mdi:thermometer",
),
SensorEntityDescription(
key="apc",
name="Status Data",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="apcmodel",
name="Model",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="badbatts",
name="Bad Batteries",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="battdate",
name="Battery Replaced",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="battstat",
name="Battery Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="battv",
name="Battery Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="bcharge",
name="Battery",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery",
),
SensorEntityDescription(
key="cable",
name="Cable Type",
icon="mdi:ethernet-cable",
),
SensorEntityDescription(
key="cumonbatt",
name="Total Time on Battery",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="date",
name="Status Date",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="dipsw",
name="Dip Switch Settings",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="dlowbatt",
name="Low Battery Signal",
icon="mdi:clock-alert",
),
SensorEntityDescription(
key="driver",
name="Driver",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="dshutd",
name="Shutdown Delay",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="dwake",
name="Wake Delay",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="endapc",
name="Date and Time",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="extbatts",
name="External Batteries",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="firmware",
name="Firmware Version",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="hitrans",
name="Transfer High",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="hostname",
name="Hostname",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="humidity",
name="Ambient Humidity",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:water-percent",
),
SensorEntityDescription(
key="itemp",
name="Internal Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key="lastxfer",
name="Last Transfer",
icon="mdi:transfer",
),
SensorEntityDescription(
key="linefail",
name="Input Voltage Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="linefreq",
name="Line Frequency",
native_unit_of_measurement=FREQUENCY_HERTZ,
icon="mdi:information-outline",
),
SensorEntityDescription(
key="linev",
name="Input Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="loadpct",
name="Load",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:gauge",
),
SensorEntityDescription(
key="loadapnt",
name="Load Apparent Power",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:gauge",
),
SensorEntityDescription(
key="lotrans",
name="Transfer Low",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="mandate",
name="Manufacture Date",
icon="mdi:calendar",
),
SensorEntityDescription(
key="masterupd",
name="Master Update",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="maxlinev",
name="Input Voltage High",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="maxtime",
name="Battery Timeout",
icon="mdi:timer-off-outline",
),
SensorEntityDescription(
key="mbattchg",
name="Battery Shutdown",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery-alert",
),
SensorEntityDescription(
key="minlinev",
name="Input Voltage Low",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="mintimel",
name="Shutdown Time",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="model",
name="Model",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="nombattv",
name="Battery Nominal Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nominv",
name="Nominal Input Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nomoutv",
name="Nominal Output Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nompower",
name="Nominal Output Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:flash",
),
SensorEntityDescription(
key="nomapnt",
name="Nominal Apparent Power",
native_unit_of_measurement=POWER_VOLT_AMPERE,
icon="mdi:flash",
),
SensorEntityDescription(
key="numxfers",
name="Transfer Count",
icon="mdi:counter",
),
SensorEntityDescription(
key="outcurnt",
name="Output Current",
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
icon="mdi:flash",
),
SensorEntityDescription(
key="outputv",
name="Output Voltage",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
icon="mdi:flash",
),
SensorEntityDescription(
key="reg1",
name="Register 1 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="reg2",
name="Register 2 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="reg3",
name="Register 3 Fault",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="retpct",
name="Restore Requirement",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:battery-alert",
),
SensorEntityDescription(
key="selftest",
name="Last Self Test",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="sense",
name="Sensitivity",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="serialno",
name="Serial Number",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="starttime",
name="Startup Time",
icon="mdi:calendar-clock",
),
SensorEntityDescription(
key="statflag",
name="Status Flag",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="status",
name="Status",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="stesti",
name="Self Test Interval",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="timeleft",
name="Time Left",
icon="mdi:clock-alert",
),
SensorEntityDescription(
key="tonbatt",
name="Time on Battery",
icon="mdi:timer-outline",
),
SensorEntityDescription(
key="upsmode",
name="Mode",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="upsname",
name="Name",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="version",
name="Daemon Info",
icon="mdi:information-outline",
),
SensorEntityDescription(
key="xoffbat",
name="Transfer from Battery",
icon="mdi:transfer",
),
SensorEntityDescription(
key="xoffbatt",
name="Transfer from Battery",
icon="mdi:transfer",
),
SensorEntityDescription(
key="xonbatt",
name="Transfer to Battery",
icon="mdi:transfer",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
SPECIFIC_UNITS = {"ITEMP": TEMP_CELSIUS}
INFERRED_UNITS = {
@@ -111,7 +403,7 @@ INFERRED_UNITS = {
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_RESOURCES, default=[]): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
cv.ensure_list, [vol.In(SENSOR_KEYS)]
)
}
)
@@ -120,25 +412,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the APCUPSd sensors."""
apcups_data = hass.data[DOMAIN]
entities = []
resources = config[CONF_RESOURCES]
for resource in config[CONF_RESOURCES]:
sensor_type = resource.lower()
if sensor_type not in SENSOR_TYPES:
SENSOR_TYPES[sensor_type] = [
sensor_type.title(),
"",
"mdi:information-outline",
]
if sensor_type.upper() not in apcups_data.status:
for resource in resources:
if resource.upper() not in apcups_data.status:
_LOGGER.warning(
"Sensor type: %s does not appear in the APCUPSd status output",
sensor_type,
resource,
)
entities.append(APCUPSdSensor(apcups_data, sensor_type))
entities = [
APCUPSdSensor(apcups_data, description)
for description in SENSOR_TYPES
if description.key in resources
]
add_entities(entities, True)
@@ -159,22 +446,18 @@ def infer_unit(value):
class APCUPSdSensor(SensorEntity):
"""Representation of a sensor entity for APCUPSd status values."""
def __init__(self, data, sensor_type):
def __init__(self, data, description: SensorEntityDescription):
"""Initialize the sensor."""
self.entity_description = description
self._data = data
self.type = sensor_type
self._attr_name = SENSOR_PREFIX + SENSOR_TYPES[sensor_type][0]
self._attr_icon = SENSOR_TYPES[self.type][2]
self._attr_native_unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self._attr_device_class = SENSOR_TYPES[sensor_type][3]
self._attr_name = f"{SENSOR_PREFIX}{description.name}"
def update(self):
"""Get the latest status and use it to update our sensor state."""
if self.type.upper() not in self._data.status:
key = self.entity_description.key.upper()
if key not in self._data.status:
self._attr_native_value = None
else:
self._attr_native_value, inferred_unit = infer_unit(
self._data.status[self.type.upper()]
)
if not self._attr_native_unit_of_measurement:
self._attr_native_value, inferred_unit = infer_unit(self._data.status[key])
if not self.native_unit_of_measurement:
self._attr_native_unit_of_measurement = inferred_unit
@@ -5,7 +5,6 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"requirements": ["pyatv==0.8.2"],
"zeroconf": ["_mediaremotetv._tcp.local.", "_touch-able._tcp.local."],
"after_dependencies": ["discovery"],
"codeowners": ["@postlund"],
"iot_class": "local_push"
}
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured_device": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9",
"already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"backoff": "L'appareil n'accepte pas les demandes d'appariement pour le moment (vous avez peut-\u00eatre saisi un code PIN non valide trop de fois), r\u00e9essayez plus tard.",
"device_did_not_pair": "Aucune tentative pour terminer l'appairage n'a \u00e9t\u00e9 effectu\u00e9e \u00e0 partir de l'appareil.",
@@ -11,10 +11,10 @@
},
"error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"invalid_auth": "Autentification invalide",
"no_devices_found": "Aucun appareil d\u00e9tect\u00e9 sur le r\u00e9seau",
"invalid_auth": "Authentification invalide",
"no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau",
"no_usable_service": "Un dispositif a \u00e9t\u00e9 trouv\u00e9, mais aucun moyen d\u2019\u00e9tablir un lien avec lui. Si vous continuez \u00e0 voir ce message, essayez de sp\u00e9cifier son adresse IP ou de red\u00e9marrer votre Apple TV.",
"unknown": "Erreur innatendue"
"unknown": "Erreur inattendue"
},
"flow_title": "Apple TV: {name}",
"step": {
+105 -43
View File
@@ -1,8 +1,15 @@
"""Support for AquaLogic sensors."""
from __future__ import annotations
from dataclasses import dataclass
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import (
CONF_MONITORED_CONDITIONS,
DEVICE_CLASS_TEMPERATURE,
@@ -16,40 +23,88 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN, UPDATE_TOPIC
TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
PERCENT_UNITS = [PERCENTAGE, PERCENTAGE]
SALT_UNITS = ["g/L", "PPM"]
WATT_UNITS = [POWER_WATT, POWER_WATT]
NO_UNITS = [None, None]
# sensor_type [ description, unit, icon, device_class ]
# sensor_type corresponds to property names in aqualogic.core.AquaLogic
SENSOR_TYPES = {
"air_temp": ["Air Temperature", TEMP_UNITS, None, DEVICE_CLASS_TEMPERATURE],
"pool_temp": [
"Pool Temperature",
TEMP_UNITS,
"mdi:oil-temperature",
DEVICE_CLASS_TEMPERATURE,
],
"spa_temp": [
"Spa Temperature",
TEMP_UNITS,
"mdi:oil-temperature",
DEVICE_CLASS_TEMPERATURE,
],
"pool_chlorinator": ["Pool Chlorinator", PERCENT_UNITS, "mdi:gauge", None],
"spa_chlorinator": ["Spa Chlorinator", PERCENT_UNITS, "mdi:gauge", None],
"salt_level": ["Salt Level", SALT_UNITS, "mdi:gauge", None],
"pump_speed": ["Pump Speed", PERCENT_UNITS, "mdi:speedometer", None],
"pump_power": ["Pump Power", WATT_UNITS, "mdi:gauge", None],
"status": ["Status", NO_UNITS, "mdi:alert", None],
}
@dataclass
class AquaLogicSensorEntityDescription(SensorEntityDescription):
"""Describes AquaLogic sensor entity."""
unit_metric: str | None = None
unit_imperial: str | None = None
# keys correspond to property names in aqualogic.core.AquaLogic
SENSOR_TYPES: tuple[AquaLogicSensorEntityDescription, ...] = (
AquaLogicSensorEntityDescription(
key="air_temp",
name="Air Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="pool_temp",
name="Pool Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
icon="mdi:oil-temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="spa_temp",
name="Spa Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
icon="mdi:oil-temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
),
AquaLogicSensorEntityDescription(
key="pool_chlorinator",
name="Pool Chlorinator",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="spa_chlorinator",
name="Spa Chlorinator",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="salt_level",
name="Salt Level",
unit_metric="g/L",
unit_imperial="PPM",
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="pump_speed",
name="Pump Speed",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
icon="mdi:speedometer",
),
AquaLogicSensorEntityDescription(
key="pump_power",
name="Pump Power",
unit_metric=POWER_WATT,
unit_imperial=POWER_WATT,
icon="mdi:gauge",
),
AquaLogicSensorEntityDescription(
key="status",
name="Status",
icon="mdi:alert",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
vol.Required(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
cv.ensure_list, [vol.In(SENSOR_KEYS)]
)
}
)
@@ -57,26 +112,29 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the sensor platform."""
sensors = []
processor = hass.data[DOMAIN]
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
sensors.append(AquaLogicSensor(processor, sensor_type))
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
async_add_entities(sensors)
entities = [
AquaLogicSensor(processor, description)
for description in SENSOR_TYPES
if description.key in monitored_conditions
]
async_add_entities(entities)
class AquaLogicSensor(SensorEntity):
"""Sensor implementation for the AquaLogic component."""
entity_description: AquaLogicSensorEntityDescription
_attr_should_poll = False
def __init__(self, processor, sensor_type):
def __init__(self, processor, description: AquaLogicSensorEntityDescription):
"""Initialize sensor."""
self.entity_description = description
self._processor = processor
self._type = sensor_type
self._attr_name = f"AquaLogic {SENSOR_TYPES[sensor_type][0]}"
self._attr_icon = SENSOR_TYPES[sensor_type][2]
self._attr_name = f"AquaLogic {description.name}"
async def async_added_to_hass(self):
"""Register callbacks."""
@@ -92,11 +150,15 @@ class AquaLogicSensor(SensorEntity):
panel = self._processor.panel
if panel is not None:
if panel.is_metric:
self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][0]
self._attr_native_unit_of_measurement = (
self.entity_description.unit_metric
)
else:
self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][1]
self._attr_native_unit_of_measurement = (
self.entity_description.unit_imperial
)
self._attr_native_value = getattr(panel, self._type)
self._attr_native_value = getattr(panel, self.entity_description.key)
self.async_write_ha_state()
else:
self._attr_native_unit_of_measurement = None
@@ -5,7 +5,10 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.const import (
ATTR_ENTITY_ID,
@@ -57,10 +60,10 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_data = automation_info.get("trigger_data", {}) if automation_info else {}
trigger_data = automation_info["trigger_data"]
job = HassJob(action)
if config[CONF_TYPE] == "turn_on":
@@ -1,8 +1,8 @@
{
"config": {
"abort": {
"already_configured": "L'appareil \u00e9tait d\u00e9j\u00e0 configur\u00e9.",
"already_in_progress": "Le flux de configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"cannot_connect": "\u00c9chec de connexion"
},
"error": {
+1
View File
@@ -88,6 +88,7 @@ class ArestSwitchBase(SwitchEntity):
self._resource = resource
self._attr_name = f"{location.title()} {name.title()}"
self._attr_available = True
self._attr_is_on = False
class ArestSwitchFunction(ArestSwitchBase):
@@ -8,6 +8,7 @@
"invalid_host": "\u05e9\u05dd \u05de\u05d0\u05e8\u05d7 \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea IP \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05d9\u05dd",
"pwd_and_ssh": "\u05e1\u05e4\u05e7 \u05e8\u05e7 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d0\u05d5 \u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH",
"pwd_or_ssh": "\u05d0\u05e0\u05d0 \u05e1\u05e4\u05e7 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d0\u05d5 \u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH",
"ssh_not_file": "\u05e7\u05d5\u05d1\u05e5 \u05de\u05e4\u05ea\u05d7 SSH \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"step": {
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Un seul appareil Atag peut \u00eatre ajout\u00e9 \u00e0 Home Assistant"
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -10,7 +10,7 @@
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP",
"host": "H\u00f4te",
"port": "Port"
},
"title": "Se connecter \u00e0 l'appareil"
@@ -5,8 +5,8 @@
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer",
"invalid_auth": "Authentification non valide",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"unknown": "Erreur inattendue"
},
"step": {
@@ -1,7 +1,7 @@
{
"config": {
"error": {
"cannot_connect": "\u00c9chec \u00e0 la connexion"
"cannot_connect": "\u00c9chec de connexion"
},
"step": {
"user": {
@@ -2,7 +2,7 @@
from __future__ import annotations
import logging
from typing import Any, Awaitable, Callable, Dict, cast
from typing import Any, Awaitable, Callable, Dict, TypedDict, cast
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -106,6 +106,23 @@ _LOGGER = logging.getLogger(__name__)
AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]]
class AutomationTriggerData(TypedDict):
"""Automation trigger data."""
id: str
idx: str
class AutomationTriggerInfo(TypedDict):
"""Information about automation trigger."""
domain: str
name: str
home_assistant_start: bool
variables: TemplateVarsType
trigger_data: AutomationTriggerData
@bind_hass
def is_on(hass, entity_id):
"""
+85 -71
View File
@@ -1,4 +1,5 @@
"""Constants for the Awair component."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
@@ -6,9 +7,8 @@ import logging
from python_awair.devices import AwairDevice
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
@@ -36,10 +36,6 @@ API_VOC = "volatile_organic_compounds"
ATTRIBUTION = "Awair air quality sensor"
ATTR_LABEL = "label"
ATTR_UNIT = "unit"
ATTR_UNIQUE_ID = "unique_id"
DOMAIN = "awair"
DUST_ALIASES = [API_PM25, API_PM10]
@@ -48,71 +44,89 @@ LOGGER = logging.getLogger(__package__)
UPDATE_INTERVAL = timedelta(minutes=5)
SENSOR_TYPES = {
API_SCORE: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: PERCENTAGE,
ATTR_LABEL: "Awair score",
ATTR_UNIQUE_ID: "score", # matches legacy format
},
API_HUMID: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
ATTR_ICON: None,
ATTR_UNIT: PERCENTAGE,
ATTR_LABEL: "Humidity",
ATTR_UNIQUE_ID: "HUMID", # matches legacy format
},
API_LUX: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_ILLUMINANCE,
ATTR_ICON: None,
ATTR_UNIT: LIGHT_LUX,
ATTR_LABEL: "Illuminance",
ATTR_UNIQUE_ID: "illuminance",
},
API_SPL_A: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:ear-hearing",
ATTR_UNIT: SOUND_PRESSURE_WEIGHTED_DBA,
ATTR_LABEL: "Sound level",
ATTR_UNIQUE_ID: "sound_level",
},
API_VOC: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:cloud",
ATTR_UNIT: CONCENTRATION_PARTS_PER_BILLION,
ATTR_LABEL: "Volatile organic compounds",
ATTR_UNIQUE_ID: "VOC", # matches legacy format
},
API_TEMP: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_UNIT: TEMP_CELSIUS,
ATTR_LABEL: "Temperature",
ATTR_UNIQUE_ID: "TEMP", # matches legacy format
},
API_PM25: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_LABEL: "PM2.5",
ATTR_UNIQUE_ID: "PM25", # matches legacy format
},
API_PM10: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_LABEL: "PM10",
ATTR_UNIQUE_ID: "PM10", # matches legacy format
},
API_CO2: {
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2,
ATTR_ICON: "mdi:cloud",
ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION,
ATTR_LABEL: "Carbon dioxide",
ATTR_UNIQUE_ID: "CO2", # matches legacy format
},
}
@dataclass
class AwairRequiredKeysMixin:
"""Mixinf for required keys."""
unique_id_tag: str
@dataclass
class AwairSensorEntityDescription(SensorEntityDescription, AwairRequiredKeysMixin):
"""Describes Awair sensor entity."""
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
key=API_SCORE,
icon="mdi:blur",
native_unit_of_measurement=PERCENTAGE,
name="Awair score",
unique_id_tag="score", # matches legacy format
)
SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_HUMID,
device_class=DEVICE_CLASS_HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
name="Humidity",
unique_id_tag="HUMID", # matches legacy format
),
AwairSensorEntityDescription(
key=API_LUX,
device_class=DEVICE_CLASS_ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
name="Illuminance",
unique_id_tag="illuminance",
),
AwairSensorEntityDescription(
key=API_SPL_A,
icon="mdi:ear-hearing",
native_unit_of_measurement=SOUND_PRESSURE_WEIGHTED_DBA,
name="Sound level",
unique_id_tag="sound_level",
),
AwairSensorEntityDescription(
key=API_VOC,
icon="mdi:cloud",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
name="Volatile organic compounds",
unique_id_tag="VOC", # matches legacy format
),
AwairSensorEntityDescription(
key=API_TEMP,
device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
name="Temperature",
unique_id_tag="TEMP", # matches legacy format
),
AwairSensorEntityDescription(
key=API_CO2,
device_class=DEVICE_CLASS_CO2,
icon="mdi:cloud",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
name="Carbon dioxide",
unique_id_tag="CO2", # matches legacy format
),
)
SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_PM25,
icon="mdi:blur",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
name="PM2.5",
unique_id_tag="PM25", # matches legacy format
),
AwairSensorEntityDescription(
key=API_PM10,
icon="mdi:blur",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
name="PM10",
unique_id_tag="PM10", # matches legacy format
),
)
@dataclass
+46 -47
View File
@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.components.awair import AwairDataUpdateCoordinator, AwairResult
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, CONF_ACCESS_TOKEN
from homeassistant.const import ATTR_ATTRIBUTION, CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
import homeassistant.helpers.config_validation as cv
@@ -22,15 +22,14 @@ from .const import (
API_SCORE,
API_TEMP,
API_VOC,
ATTR_ICON,
ATTR_LABEL,
ATTR_UNIQUE_ID,
ATTR_UNIT,
ATTRIBUTION,
DOMAIN,
DUST_ALIASES,
LOGGER,
SENSOR_TYPE_SCORE,
SENSOR_TYPES,
SENSOR_TYPES_DUST,
AwairSensorEntityDescription,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@@ -60,16 +59,20 @@ async def async_setup_entry(
):
"""Set up Awair sensor entity based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
sensors = []
entities = []
data: list[AwairResult] = coordinator.data.values()
for result in data:
if result.air_data:
sensors.append(AwairSensor(API_SCORE, result.device, coordinator))
entities.append(AwairSensor(result.device, coordinator, SENSOR_TYPE_SCORE))
device_sensors = result.air_data.sensors.keys()
for sensor in device_sensors:
if sensor in SENSOR_TYPES:
sensors.append(AwairSensor(sensor, result.device, coordinator))
entities.extend(
[
AwairSensor(result.device, coordinator, description)
for description in (*SENSOR_TYPES, *SENSOR_TYPES_DUST)
if description.key in device_sensors
]
)
# The "DUST" sensor for Awair is a combo pm2.5/pm10 sensor only
# present on first-gen devices in lieu of separate pm2.5/pm10 sensors.
@@ -78,45 +81,53 @@ async def async_setup_entry(
# that data - because we can't really tell what kind of particles the
# "DUST" sensor actually detected. However, it's still useful data.
if API_DUST in device_sensors:
for alias_kind in DUST_ALIASES:
sensors.append(AwairSensor(alias_kind, result.device, coordinator))
entities.extend(
[
AwairSensor(result.device, coordinator, description)
for description in SENSOR_TYPES_DUST
]
)
async_add_entities(sensors)
async_add_entities(entities)
class AwairSensor(CoordinatorEntity, SensorEntity):
"""Defines an Awair sensor entity."""
entity_description: AwairSensorEntityDescription
def __init__(
self,
kind: str,
device: AwairDevice,
coordinator: AwairDataUpdateCoordinator,
description: AwairSensorEntityDescription,
) -> None:
"""Set up an individual AwairSensor."""
super().__init__(coordinator)
self._kind = kind
self.entity_description = description
self._device = device
@property
def name(self) -> str:
def name(self) -> str | None:
"""Return the name of the sensor."""
name = SENSOR_TYPES[self._kind][ATTR_LABEL]
if self._device.name:
name = f"{self._device.name} {name}"
return f"{self._device.name} {self.entity_description.name}"
return name
return self.entity_description.name
@property
def unique_id(self) -> str:
"""Return the uuid as the unique_id."""
unique_id_tag = SENSOR_TYPES[self._kind][ATTR_UNIQUE_ID]
unique_id_tag = self.entity_description.unique_id_tag
# This integration used to create a sensor that was labelled as a "PM2.5"
# sensor for first-gen Awair devices, but its unique_id reflected the truth:
# under the hood, it was a "DUST" sensor. So we preserve that specific unique_id
# for users with first-gen devices that are upgrading.
if self._kind == API_PM25 and API_DUST in self._air_data.sensors:
if (
self.entity_description.key == API_PM25
and API_DUST in self._air_data.sensors
):
unique_id_tag = "DUST"
return f"{self._device.uuid}_{unique_id_tag}"
@@ -127,16 +138,17 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
# If the last update was successful...
if self.coordinator.last_update_success and self._air_data:
# and the results included our sensor type...
if self._kind in self._air_data.sensors:
sensor_type = self.entity_description.key
if sensor_type in self._air_data.sensors:
# then we are available.
return True
# or, we're a dust alias
if self._kind in DUST_ALIASES and API_DUST in self._air_data.sensors:
if sensor_type in DUST_ALIASES and API_DUST in self._air_data.sensors:
return True
# or we are API_SCORE
if self._kind == API_SCORE:
if sensor_type == API_SCORE:
# then we are available.
return True
@@ -147,38 +159,24 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
def native_value(self) -> float:
"""Return the state, rounding off to reasonable values."""
state: float
sensor_type = self.entity_description.key
# Special-case for "SCORE", which we treat as the AQI
if self._kind == API_SCORE:
if sensor_type == API_SCORE:
state = self._air_data.score
elif self._kind in DUST_ALIASES and API_DUST in self._air_data.sensors:
elif sensor_type in DUST_ALIASES and API_DUST in self._air_data.sensors:
state = self._air_data.sensors.dust
else:
state = self._air_data.sensors[self._kind]
state = self._air_data.sensors[sensor_type]
if self._kind == API_VOC or self._kind == API_SCORE:
if sensor_type in {API_VOC, API_SCORE}:
return round(state)
if self._kind == API_TEMP:
if sensor_type == API_TEMP:
return round(state, 1)
return round(state, 2)
@property
def icon(self) -> str:
"""Return the icon."""
return SENSOR_TYPES[self._kind][ATTR_ICON]
@property
def device_class(self) -> str:
"""Return the device_class."""
return SENSOR_TYPES[self._kind][ATTR_DEVICE_CLASS]
@property
def native_unit_of_measurement(self) -> str:
"""Return the unit the value is expressed in."""
return SENSOR_TYPES[self._kind][ATTR_UNIT]
@property
def extra_state_attributes(self) -> dict:
"""Return the Awair Index alongside state attributes.
@@ -201,10 +199,11 @@ class AwairSensor(CoordinatorEntity, SensorEntity):
https://docs.developer.getawair.com/?version=latest#awair-score-and-index
"""
sensor_type = self.entity_description.key
attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
if self._kind in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices[self._kind])
elif self._kind in DUST_ALIASES and API_DUST in self._air_data.indices:
if sensor_type in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices[sensor_type])
elif sensor_type in DUST_ALIASES and API_DUST in self._air_data.indices:
attrs["awair_index"] = abs(self._air_data.indices.dust)
return attrs
@@ -3,11 +3,11 @@
"abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau",
"reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s"
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"invalid_access_token": "Jeton d'acc\u00e8s non valide",
"unknown": "Erreur d'API Awair inconnue."
"unknown": "Erreur inattendue"
},
"step": {
"reauth": {
@@ -7,7 +7,7 @@
},
"error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "Le flux de configuration de l'appareil est d\u00e9j\u00e0 en cours.",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide"
},
@@ -15,7 +15,7 @@
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP",
"host": "H\u00f4te",
"password": "Mot de passe",
"port": "Port",
"username": "Nom d'utilisateur"
@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s"
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -1,4 +1,14 @@
{
"device_automation": {
"condition_type": {
"is_no_update": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03bc\u03ad\u03bd\u03bf",
"is_update": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
},
"trigger_type": {
"no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5",
"update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
}
},
"state": {
"_": {
"off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2",
@@ -72,6 +82,10 @@
"off": "\u0394\u03b5\u03bd \u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5",
"on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5"
},
"update": {
"off": "\u03a0\u03bb\u03ae\u03c1\u03c9\u03c2 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03bc\u03ad\u03bd\u03bf",
"on": "\u0394\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7"
},
"vibration": {
"off": "\u0394\u03b5\u03bd \u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5",
"on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5"
@@ -178,6 +178,10 @@
"off": "No detectado",
"on": "Detectado"
},
"update": {
"off": "Actualizado",
"on": "Actualizaci\u00f3n disponible"
},
"vibration": {
"off": "No detectado",
"on": "Detectado"
@@ -115,12 +115,12 @@
"on": "Connect\u00e9"
},
"door": {
"off": "Ferm\u00e9e",
"on": "Ouverte"
"off": "Ferm\u00e9",
"on": "Ouvert"
},
"garage_door": {
"off": "Ferm\u00e9e",
"on": "Ouverte"
"off": "Ferm\u00e9",
"on": "Ouvert"
},
"gas": {
"off": "Non d\u00e9tect\u00e9",
@@ -191,8 +191,8 @@
"on": "D\u00e9tect\u00e9e"
},
"window": {
"off": "Ferm\u00e9e",
"on": "Ouverte"
"off": "Ferm\u00e9",
"on": "Ouvert"
}
},
"title": "Capteur binaire"
@@ -17,7 +17,7 @@
"is_no_problem": "sensor {entity_name} nie wykrywa problemu",
"is_no_smoke": "sensor {entity_name} nie wykrywa dymu",
"is_no_sound": "sensor {entity_name} nie wykrywa d\u017awi\u0119ku",
"is_no_update": "{entity_name} jest aktualny(-a)",
"is_no_update": "dla {entity_name} nie ma dost\u0119pnej aktualizacji",
"is_no_vibration": "sensor {entity_name} nie wykrywa wibracji",
"is_not_bat_low": "bateria {entity_name} nie jest roz\u0142adowana",
"is_not_cold": "sensor {entity_name} nie wykrywa zimna",
@@ -43,7 +43,7 @@
"is_smoke": "sensor {entity_name} wykrywa dym",
"is_sound": "sensor {entity_name} wykrywa d\u017awi\u0119k",
"is_unsafe": "sensor {entity_name} wykrywa zagro\u017cenie",
"is_update": "{entity_name} ma dost\u0119pn\u0105 aktualizacj\u0119",
"is_update": "dla {entity_name} jest dost\u0119pna aktualizacja",
"is_vibration": "sensor {entity_name} wykrywa wibracje"
},
"trigger_type": {
@@ -63,7 +63,7 @@
"no_problem": "sensor {entity_name} przestanie wykrywa\u0107 problem",
"no_smoke": "sensor {entity_name} przestanie wykrywa\u0107 dym",
"no_sound": "sensor {entity_name} przestanie wykrywa\u0107 d\u017awi\u0119k",
"no_update": "{entity_name} zosta\u0142 zaktualizowany(-a)",
"no_update": "wykonano aktualizacj\u0119 dla {entity_name}",
"no_vibration": "sensor {entity_name} przestanie wykrywa\u0107 wibracje",
"not_bat_low": "nast\u0105pi na\u0142adowanie baterii {entity_name}",
"not_cold": "sensor {entity_name} przestanie wykrywa\u0107 zimno",
@@ -183,8 +183,8 @@
"on": "wykryto"
},
"update": {
"off": "Aktualny(-a)",
"on": "Dost\u0119pna aktualizacja"
"off": "brak aktualizacji",
"on": "dost\u0119pna aktualizacja"
},
"vibration": {
"off": "brak",
@@ -2,11 +2,11 @@
"config": {
"abort": {
"address_already_configured": "Un p\u00e9riph\u00e9rique BleBox est d\u00e9j\u00e0 configur\u00e9 \u00e0 {address}.",
"already_configured": "Ce p\u00e9riph\u00e9rique BleBox est d\u00e9j\u00e0 configur\u00e9."
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "Impossible de connecter le p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)",
"unknown": "Erreur inconnue lors de la connexion au p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)",
"cannot_connect": "\u00c9chec de connexion",
"unknown": "Erreur inattendue",
"unsupported_version": "L'appareil BleBox a un micrologiciel obsol\u00e8te. Veuillez d'abord le mettre \u00e0 jour."
},
"flow_title": "P\u00e9riph\u00e9rique Blebox: {name} ({host)}",
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "P\u00e9riph\u00e9rique d\u00e9j\u00e0 configur\u00e9"
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
@@ -20,7 +20,7 @@
"user": {
"data": {
"password": "Mot de passe",
"username": "Identifiant"
"username": "Nom d'utilisateur"
},
"title": "Connectez-vous avec un compte Blink"
}
@@ -2,7 +2,7 @@
"domain": "bmw_connected_drive",
"name": "BMW Connected Drive",
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"requirements": ["bimmer_connected==0.7.19"],
"requirements": ["bimmer_connected==0.7.20"],
"codeowners": ["@gerard33", "@rikroe"],
"config_flow": true,
"iot_class": "cloud_polling"
@@ -4,7 +4,7 @@
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec \u00e0 la connexion",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide"
},
"step": {
@@ -4,7 +4,7 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "Echec de connexion",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide",
"old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer",
"unknown": "Erreur inattendue"
@@ -19,7 +19,7 @@
},
"user": {
"data": {
"access_token": "Token d'acc\u00e8s",
"access_token": "Jeton d'acc\u00e8s",
"host": "H\u00f4te"
}
}
@@ -2,7 +2,8 @@
"config": {
"error": {
"pairing_failed": "El emparejamiento ha fallado; compruebe que el Bosch Smart Home Controller est\u00e1 en modo de emparejamiento (el LED parpadea) y que su contrase\u00f1a es correcta.",
"session_error": "Error de sesi\u00f3n: La API devuelve un resultado no correcto."
"session_error": "Error de sesi\u00f3n: La API devuelve un resultado no correcto.",
"unknown": "Error inesperado"
},
"flow_title": "Bosch SHC: {name}",
"step": {
@@ -18,6 +19,9 @@
"description": "La integraci\u00f3n bosch_shc necesita volver a autentificar su cuenta"
},
"user": {
"data": {
"host": "Anfitri\u00f3n"
},
"description": "Configura tu Bosch Smart Home Controller para permitir la supervisi\u00f3n y el control con Home Assistant.",
"title": "Par\u00e1metros de autenticaci\u00f3n SHC"
}
@@ -2,11 +2,11 @@
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "R\u00e9-authentification r\u00e9ussie"
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification incorrecte",
"invalid_auth": "Authentification invalide",
"pairing_failed": "L'appairage a \u00e9chou\u00e9\u00a0; veuillez v\u00e9rifier que le Bosch Smart Home Controller est en mode d'appairage (voyant clignotant) et que votre mot de passe est correct.",
"session_error": "Erreur de session\u00a0: l'API renvoie un r\u00e9sultat non-OK.",
"unknown": "Erreur inattendue"
@@ -23,7 +23,7 @@
},
"reauth_confirm": {
"description": "L'int\u00e9gration bosch_shc doit r\u00e9-authentifier votre compte",
"title": "R\u00e9authentification de l'int\u00e9gration"
"title": "R\u00e9-authentifier l'int\u00e9gration"
},
"user": {
"data": {
@@ -1,12 +1,12 @@
{
"config": {
"abort": {
"already_configured": "Ce t\u00e9l\u00e9viseur est d\u00e9j\u00e0 configur\u00e9.",
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"no_ip_control": "Le contr\u00f4le IP est d\u00e9sactiv\u00e9 sur votre t\u00e9l\u00e9viseur ou le t\u00e9l\u00e9viseur n'est pas pris en charge."
},
"error": {
"cannot_connect": "\u00c9chec de connexion, h\u00f4te ou code PIN non valide.",
"invalid_host": "Nom d'h\u00f4te ou adresse IP invalide.",
"cannot_connect": "\u00c9chec de connexion",
"invalid_host": "Nom d'h\u00f4te ou adresse IP non valide",
"unsupported_model": "Votre mod\u00e8le de t\u00e9l\u00e9viseur n'est pas pris en charge."
},
"step": {
@@ -19,7 +19,7 @@
},
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP"
"host": "H\u00f4te"
},
"description": "Configurez l'int\u00e9gration du t\u00e9l\u00e9viseur Sony Bravia. Si vous rencontrez des probl\u00e8mes de configuration, rendez-vous sur: https://www.home-assistant.io/integrations/braviatv \n\n Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9.",
"title": "Sony Bravia TV"
@@ -142,9 +142,6 @@ class BroadlinkSwitch(BroadlinkEntity, SwitchEntity, RestoreEntity, ABC):
super().__init__(device)
self._command_on = command_on
self._command_off = command_off
self._attr_assumed_state = True
self._attr_device_class = DEVICE_CLASS_SWITCH
self._attr_name = f"{device.name} Switch"
async def async_added_to_hass(self):
@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"already_in_progress": "Il y a d\u00e9j\u00e0 un processus de configuration en cours pour cet appareil",
"already_in_progress": "La configuration est d\u00e9j\u00e0 en cours",
"cannot_connect": "\u00c9chec de connexion",
"invalid_host": "Nom d'h\u00f4te ou adresse IP non valide",
"not_supported": "Dispositif non pris en charge",
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Cette imprimante est d\u00e9j\u00e0 configur\u00e9e.",
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"unsupported_model": "Ce mod\u00e8le d'imprimante n'est pas pris en charge."
},
"error": {
@@ -13,7 +13,7 @@
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP",
"host": "H\u00f4te",
"type": "Type d'imprimante"
},
"description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother"
@@ -10,7 +10,7 @@
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP",
"host": "H\u00f4te",
"passkey": "Cha\u00eene de cl\u00e9 d'acc\u00e8s",
"password": "Mot de passe",
"port": "Port",
@@ -699,7 +699,7 @@ class BrSensor(SensorEntity):
@callback
def data_updated(self, data):
"""Update data."""
if self._load_data(data) and self.hass:
if self.hass and self._load_data(data):
self.async_write_ha_state()
@callback
+12 -5
View File
@@ -165,10 +165,7 @@ async def _async_get_image(
width=width, height=height
)
else:
_LOGGER.warning(
"The camera entity %s does not support requesting width and height, please open an issue with the integration author",
camera.entity_id,
)
camera.async_warn_old_async_camera_image_signature()
image_bytes = await camera.async_camera_image()
if image_bytes:
@@ -381,6 +378,7 @@ class Camera(Entity):
self.stream_options: dict[str, str] = {}
self.content_type: str = DEFAULT_CONTENT_TYPE
self.access_tokens: collections.deque = collections.deque([], 2)
self._warned_old_signature = False
self.async_update_token()
@property
@@ -455,11 +453,20 @@ class Camera(Entity):
return await self.hass.async_add_executor_job(
partial(self.camera_image, width=width, height=height)
)
self.async_warn_old_async_camera_image_signature()
return await self.hass.async_add_executor_job(self.camera_image)
# Remove in 2022.1 after all custom components have had a chance to change their signature
@callback
def async_warn_old_async_camera_image_signature(self) -> None:
"""Warn once when calling async_camera_image with the function old signature."""
if self._warned_old_signature:
return
_LOGGER.warning(
"The camera entity %s does not support requesting width and height, please open an issue with the integration author",
self.entity_id,
)
return await self.hass.async_add_executor_job(self.camera_image)
self._warned_old_signature = True
async def handle_async_still_stream(
self, request: web.Request, interval: float
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire."
"single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible."
},
"error": {
"invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules."
@@ -15,7 +15,7 @@
"title": "Google Cast"
},
"confirm": {
"description": "Voulez-vous configurer Google Cast?"
"description": "Voulez-vous commencer la configuration ?"
}
}
},
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import datetime, timedelta
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT
@@ -44,7 +45,7 @@ async def async_unload_entry(hass, entry):
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[datetime]):
class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[Optional[datetime]]):
"""Class to manage fetching Cert Expiry data from single endpoint."""
def __init__(self, hass, host, port):
@@ -1,4 +1,6 @@
"""Config flow for the Cert Expiry platform."""
from __future__ import annotations
import logging
import voluptuous as vol
@@ -25,7 +27,7 @@ class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the config flow."""
self._errors = {}
self._errors: dict[str, str] = {}
async def _test_connection(self, user_input=None):
"""Test connection to the server and try to get the certificate."""
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Cette combinaison h\u00f4te et port est d\u00e9j\u00e0 configur\u00e9e",
"already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9",
"import_failed": "\u00c9chec de l'importation \u00e0 partir de la configuration"
},
"error": {
@@ -5,7 +5,10 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.automation import (
AutomationActionType,
AutomationTriggerInfo,
)
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
@@ -112,7 +115,7 @@ async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_type = config[CONF_TYPE]
+1 -1
View File
@@ -228,7 +228,7 @@ async def async_setup(hass, config):
cloud.iot.register_on_connect(_on_connect)
await cloud.start()
await cloud.initialize()
await http_api.async_setup(hass)
account_link.async_setup(hass)
+5 -33
View File
@@ -4,9 +4,10 @@ import logging
from typing import Any
import aiohttp
from awesomeversion import AwesomeVersion
from hass_nabucasa import account_link
from homeassistant.const import MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_entry_oauth2_flow, event
@@ -16,6 +17,8 @@ DATA_SERVICES = "cloud_account_link_services"
CACHE_TIMEOUT = 3600
_LOGGER = logging.getLogger(__name__)
CURRENT_VERSION = AwesomeVersion(HA_VERSION)
@callback
def async_setup(hass: HomeAssistant):
@@ -30,43 +33,12 @@ async def async_provide_implementation(hass: HomeAssistant, domain: str):
services = await _get_services(hass)
for service in services:
if service["service"] == domain and _is_older(service["min_version"]):
if service["service"] == domain and CURRENT_VERSION >= service["min_version"]:
return CloudOAuth2Implementation(hass, domain)
return
@callback
def _is_older(version: str) -> bool:
"""Test if a version is older than the current HA version."""
version_parts = version.split(".")
if len(version_parts) != 3:
return False
try:
version_parts = [int(val) for val in version_parts]
except ValueError:
return False
patch_number_str = ""
for char in PATCH_VERSION:
if char.isnumeric():
patch_number_str += char
else:
break
try:
patch_number = int(patch_number_str)
except ValueError:
patch_number = 0
cur_version_parts = [MAJOR_VERSION, MINOR_VERSION, patch_number]
return version_parts <= cur_version_parts
async def _get_services(hass):
"""Get the available services."""
services = hass.data.get(DATA_SERVICES)
+6 -3
View File
@@ -108,8 +108,8 @@ class CloudClient(Interface):
return self._google_config
async def logged_in(self) -> None:
"""When user logs in."""
async def cloud_started(self) -> None:
"""When cloud is started."""
is_new_user = await self.prefs.async_set_username(self.cloud.username)
async def enable_alexa(_):
@@ -150,7 +150,10 @@ class CloudClient(Interface):
if tasks:
await asyncio.gather(*(task(None) for task in tasks))
async def cleanups(self) -> None:
async def cloud_stopped(self) -> None:
"""When the cloud is stopped."""
async def logout_cleanups(self) -> None:
"""Cleanup some stuff after logout."""
await self.prefs.async_set_username(None)
@@ -62,7 +62,7 @@ class CloudGoogleConfig(AbstractConfig):
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._cloud.is_logged_in and self._prefs.google_report_state
return self.enabled and self._prefs.google_report_state
@property
def local_sdk_webhook_id(self):
+27 -62
View File
@@ -6,7 +6,7 @@ import logging
import aiohttp
import async_timeout
import attr
from hass_nabucasa import Cloud, auth, thingtalk
from hass_nabucasa import Cloud, auth, cloud_api, thingtalk
from hass_nabucasa.const import STATE_DISCONNECTED
from hass_nabucasa.voice import MAP_VOICE
import voluptuous as vol
@@ -24,7 +24,6 @@ from homeassistant.const import (
HTTP_BAD_GATEWAY,
HTTP_BAD_REQUEST,
HTTP_INTERNAL_SERVER_ERROR,
HTTP_OK,
HTTP_UNAUTHORIZED,
)
@@ -47,30 +46,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
WS_TYPE_STATUS = "cloud/status"
SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_STATUS}
)
WS_TYPE_SUBSCRIPTION = "cloud/subscription"
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_SUBSCRIPTION}
)
WS_TYPE_HOOK_CREATE = "cloud/cloudhook/create"
SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_HOOK_CREATE, vol.Required("webhook_id"): str}
)
WS_TYPE_HOOK_DELETE = "cloud/cloudhook/delete"
SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_HOOK_DELETE, vol.Required("webhook_id"): str}
)
_CLOUD_ERRORS = {
InvalidTrustedNetworks: (
HTTP_INTERNAL_SERVER_ERROR,
@@ -94,17 +69,11 @@ _CLOUD_ERRORS = {
async def async_setup(hass):
"""Initialize the HTTP API."""
async_register_command = hass.components.websocket_api.async_register_command
async_register_command(WS_TYPE_STATUS, websocket_cloud_status, SCHEMA_WS_STATUS)
async_register_command(
WS_TYPE_SUBSCRIPTION, websocket_subscription, SCHEMA_WS_SUBSCRIPTION
)
async_register_command(websocket_cloud_status)
async_register_command(websocket_subscription)
async_register_command(websocket_update_prefs)
async_register_command(
WS_TYPE_HOOK_CREATE, websocket_hook_create, SCHEMA_WS_HOOK_CREATE
)
async_register_command(
WS_TYPE_HOOK_DELETE, websocket_hook_delete, SCHEMA_WS_HOOK_DELETE
)
async_register_command(websocket_hook_create)
async_register_command(websocket_hook_delete)
async_register_command(websocket_remote_connect)
async_register_command(websocket_remote_disconnect)
@@ -311,6 +280,7 @@ class CloudForgotPasswordView(HomeAssistantView):
@websocket_api.async_response
@websocket_api.websocket_command({vol.Required("type"): "cloud/status"})
async def websocket_cloud_status(hass, connection, msg):
"""Handle request for account info.
@@ -344,36 +314,19 @@ def _require_cloud_login(handler):
@_require_cloud_login
@websocket_api.async_response
@websocket_api.websocket_command({vol.Required("type"): "cloud/subscription"})
async def websocket_subscription(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT):
response = await cloud.fetch_subscription_info()
if response.status != HTTP_OK:
connection.send_message(
websocket_api.error_message(
msg["id"], "request_failed", "Failed to request subscription"
)
try:
with async_timeout.timeout(REQUEST_TIMEOUT):
data = await cloud_api.async_subscription_info(cloud)
except aiohttp.ClientError:
connection.send_error(
msg["id"], "request_failed", "Failed to request subscription"
)
data = await response.json()
# Check if a user is subscribed but local info is outdated
# In that case, let's refresh and reconnect
if data.get("provider") and not cloud.is_connected:
_LOGGER.debug("Found disconnected account with valid subscriotion, connecting")
await cloud.auth.async_renew_access_token()
# Cancel reconnect in progress
if cloud.iot.state != STATE_DISCONNECTED:
await cloud.iot.disconnect()
hass.async_create_task(cloud.iot.connect())
connection.send_message(websocket_api.result_message(msg["id"], data))
else:
connection.send_result(msg["id"], data)
@_require_cloud_login
@@ -429,6 +382,12 @@ async def websocket_update_prefs(hass, connection, msg):
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@websocket_api.websocket_command(
{
vol.Required("type"): "cloud/cloudhook/create",
vol.Required("webhook_id"): str,
}
)
async def websocket_hook_create(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
@@ -439,6 +398,12 @@ async def websocket_hook_create(hass, connection, msg):
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@websocket_api.websocket_command(
{
vol.Required("type"): "cloud/cloudhook/delete",
vol.Required("webhook_id"): str,
}
)
async def websocket_hook_delete(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
+1 -1
View File
@@ -2,7 +2,7 @@
"domain": "cloud",
"name": "Home Assistant Cloud",
"documentation": "https://www.home-assistant.io/integrations/cloud",
"requirements": ["hass-nabucasa==0.46.0"],
"requirements": ["hass-nabucasa==0.49.0"],
"dependencies": ["http", "webhook"],
"after_dependencies": ["google_assistant", "alexa"],
"codeowners": ["@home-assistant/cloud"],
+2 -1
View File
@@ -7,10 +7,11 @@
"relayer_connected": "Relayer Connected",
"remote_connected": "Remote Connected",
"remote_enabled": "Remote Enabled",
"remote_server": "Remote Server",
"alexa_enabled": "Alexa Enabled",
"google_enabled": "Google Enabled",
"logged_in": "Logged In",
"subscription_expiration": "Subscription Expiration"
}
}
}
}
@@ -33,6 +33,7 @@ async def system_health_info(hass):
data["remote_connected"] = cloud.remote.is_connected
data["alexa_enabled"] = client.prefs.alexa_enabled
data["google_enabled"] = client.prefs.google_enabled
data["remote_server"] = cloud.remote.snitun_server
data["can_reach_cert_server"] = system_health.async_check_can_reach_url(
hass, cloud.acme_directory_server
@@ -10,6 +10,7 @@
"relayer_connected": "Encaminador connectat",
"remote_connected": "Connexi\u00f3 remota establerta",
"remote_enabled": "Connexi\u00f3 remota activada",
"remote_server": "Servidor remot",
"subscription_expiration": "Caducitat de la subscripci\u00f3"
}
}
@@ -10,6 +10,7 @@
"relayer_connected": "Relay Verbunden",
"remote_connected": "Remote verbunden",
"remote_enabled": "Remote aktiviert",
"remote_server": "Remote-Server",
"subscription_expiration": "Ablauf des Abonnements"
}
}
@@ -0,0 +1,7 @@
{
"system_health": {
"info": {
"remote_server": "\u0391\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2"
}
}
}
@@ -10,6 +10,7 @@
"relayer_connected": "Relayer Connected",
"remote_connected": "Remote Connected",
"remote_enabled": "Remote Enabled",
"remote_server": "Remote Server",
"subscription_expiration": "Subscription Expiration"
}
}
@@ -10,6 +10,7 @@
"relayer_connected": "Relayer conectado",
"remote_connected": "Remoto conectado",
"remote_enabled": "Remoto habilitado",
"remote_server": "Servidor remoto",
"subscription_expiration": "Caducidad de la suscripci\u00f3n"
}
}
@@ -10,6 +10,7 @@
"relayer_connected": "Edastaja on \u00fchendatud",
"remote_connected": "Kaug\u00fchendus on loodud",
"remote_enabled": "Kaug\u00fchendus on lubatud",
"remote_server": "Kaugserver",
"subscription_expiration": "Tellimuse aegumine"
}
}
@@ -3,7 +3,8 @@
"info": {
"alexa_enabled": "Alexa \u05de\u05d5\u05e4\u05e2\u05dc\u05ea",
"google_enabled": "Google \u05de\u05d5\u05e4\u05e2\u05dc",
"logged_in": "\u05de\u05d7\u05d5\u05d1\u05e8"
"logged_in": "\u05de\u05d7\u05d5\u05d1\u05e8",
"remote_server": "\u05e9\u05e8\u05ea \u05de\u05e8\u05d5\u05d7\u05e7"
}
}
}

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