Compare commits

...

347 Commits

Author SHA1 Message Date
Paulus Schoutsen
33cba4da85 Merge pull request #25280 from home-assistant/rc
0.96.1
2019-07-18 15:22:49 -07:00
Pascal Vizeli
3db106c562 Update azure-pipelines-ci.yml for Azure Pipelines 2019-07-18 23:20:56 +02:00
Paulus Schoutsen
cc595632bd Bumped version to 0.96.1 2019-07-18 14:08:50 -07:00
stboch
f76700567e Added states and modes for zwave climate (#25274)
* Update climate.py

Added support for Fan Only State
Added additional missing modes and states this should correct issue #25216

* Correct line lint error

* Corrected mode spelling

* Lint
2019-07-18 14:08:43 -07:00
Paulus Schoutsen
86cf02739b Add hvac modes back to opentherm (#25268) 2019-07-18 14:08:42 -07:00
Andrew Sayre
46cdbd273a Restore SmartThings A/C on/off services (#25259)
* Restore ST A/C on/off services

* Use correct OFF const

* Support AC HVAC_MODE_OFF
2019-07-18 14:08:42 -07:00
William Sutton
ec3cb11e2f Update CT80 Humidity call (#25258)
Last PR was from a few versions before, not sure how I had it working, but functioning properly now on .96
2019-07-18 14:08:41 -07:00
geekofweek
2016cf872e ecobee Preset Fix (#25256)
* ecobee Preset Fix

* Celsius Fix

* Checks Fix

* Check Fix #2

* Check Fix #3
2019-07-18 14:08:40 -07:00
cgtobi
37810e010a Fix the unit of measurement for ecobee climate (#25246)
* Fix the unit of measurement

* Remove unused const
2019-07-18 14:08:40 -07:00
cgtobi
2b69904b94 Make presets prettier (#25245) 2019-07-18 14:08:39 -07:00
Pascal Vizeli
59cf6a0c79 Fix eq3btsmart (#25238) 2019-07-18 14:08:39 -07:00
Pascal Vizeli
39b249d202 Show off value (#25236) 2019-07-18 14:08:38 -07:00
Paulus Schoutsen
d57cf01cf2 Updated frontend to 20190718.0 2019-07-18 14:08:06 -07:00
Paulus Schoutsen
9a79a0aa90 Merge pull request #25205 from home-assistant/rc
0.96.0
2019-07-17 16:23:24 -07:00
Khole
ccc4f628f1 Hive water heater - Remove Duplication of appending entities (#25210)
* climate_water_heater

* updated names

* Update water_heater

* Update requirements

* Updated reqirements

* Version update

* updated Versiojn

* Update device list

* Removed unused Attributes

* removed duplicate appending entities

* re-added missing hotwater

* Move call to async_added_to_hass

* Move session append to async_added_to_hass

* White space
2019-07-17 15:19:03 -07:00
cgtobi
90231c5e07 Fix schema validation for service calls (#25204)
* Fix schema validation for service calls

* No need for get

* No need for get
2019-07-17 15:19:02 -07:00
Paulus Schoutsen
ff5dd0cf42 Updated frontend to 20190717.1 2019-07-17 15:10:57 -07:00
Paulus Schoutsen
5d7f420821 Fix ecobee missing preset mode support flag (#25211) 2019-07-17 15:08:14 -07:00
Paulus Schoutsen
e4bb955498 Pin Docker to Debain Stretch (#25206)
* Pin Docker to Debain Stretch

* Update dev docker too"
2019-07-17 14:05:00 -07:00
Paulus Schoutsen
74d0e65958 Bumped version to 0.96.0 2019-07-17 13:42:32 -07:00
Paulus Schoutsen
3cfbbdc720 Only include target temp if has right support flag (#25193)
* Only include target temp if has right support flag

* Remove comma
2019-07-17 13:09:07 -07:00
David Bonnes
3d5c773670 [climate] Tweak evohome migration (#25187)
* de-lint

* use _evo_tcs instead of _evo_device for TCS

* add hvac_action to zones, remove target_temp from controller

* fix incorrect hvac_action

* de-lint
2019-07-17 13:09:06 -07:00
David F. Mulcahey
b5b0f56ae7 Fix device name customization on ZHA add devices page (#25180)
* ensure new device exists

* clean up dev reg handling

* update test

* fix tests
2019-07-17 13:09:05 -07:00
Paulus Schoutsen
c03d5f1a73 Correctly set property decorator on preset modes (#25151) 2019-07-17 13:09:05 -07:00
Paulus Schoutsen
5abe4dd1f7 Updated frontend to 20190717.0 2019-07-17 13:08:13 -07:00
Paulus Schoutsen
026dbffa77 Bumped version to 0.96.0b4 2019-07-16 14:59:46 -07:00
Fabian Affolter
e74fc9836d Upgrade luftdaten to 0.6.2 (#25177) 2019-07-16 14:59:38 -07:00
Alexei Chetroi
c7dfec702d Fix climate is_aux_heat type hint. (#25170) 2019-07-16 14:59:37 -07:00
Anders Melchiorsen
0f8f9db319 Update pysonos to 0.0.21 (#25168) 2019-07-16 14:59:37 -07:00
Daniel Perna
366ad8202a Fix device types for some HomeMatic IP sensors (#25167)
* Update pyhomematic to 0.1.60

* Devicetype for pyhomematic classes, fixes #24080
2019-07-16 14:59:36 -07:00
Paulus Schoutsen
28bd7b6a4e Bumped version to 0.96.0b3 2019-07-15 13:57:41 -07:00
Paulus Schoutsen
c04049d6f6 Make dev tools titlte translatable (#25166) 2019-07-15 13:57:35 -07:00
Joakim Sørensen
ff79e437d2 Version sensor update (#25162)
* component -> integration

* Bump pyhaversion to 3.0.2

* Update requirements

* Formating
2019-07-15 13:57:34 -07:00
Daniel Perna
c8b495f224 Update pyhomematic to 0.1.60 (#25152) 2019-07-15 13:57:33 -07:00
Josh Anderson
842c1a2274 Remove check and restore temp/mode changes (#25149) 2019-07-15 13:57:33 -07:00
Khole
50b145cf05 [Climate] Hive Add water heater Component post the refresh of the climate component. (#25148)
* climate_water_heater

* updated names

* Update water_heater

* Update requirements

* Updated reqirements

* Version update

* updated Versiojn

* Update device list

* Removed unused Attributes
2019-07-15 13:57:32 -07:00
David Bonnes
78a0d72a5c [climate-1.0] Add RoundThermostat to evohome (#25141)
* initial commit

* improve enumeration of zone(s)

* remove unused self._config

* remove unused self._config 2

* remove unused self._id

* clean up device_state_attributes

* remove some pylint: disable=protected-access

* remove LOGGER.warn(

* refactor for RoundThermostat

* ready for review

* small tweak

* small tweak 2

* fix regression, tweak

* tidy up docstring

* simplify code
2019-07-15 13:57:32 -07:00
Markus Jankowski
97ca0d81e7 remove comfort mode (#25140) 2019-07-15 13:57:31 -07:00
David Bonnes
02e8ee137f [climate-1.0] Bugfix evohome showstopper (#25139)
* initial commit

* small tweak
2019-07-15 13:57:31 -07:00
Anders Melchiorsen
2643bbc228 Handle Sonos connection errors during setup (#25135) 2019-07-15 13:57:30 -07:00
Joakim Plate
c8d7e1346c Load requirements for platforms (#25133)
Fixes #25124 and fixes #25126
2019-07-15 13:57:30 -07:00
Paulus Schoutsen
7dedf173ad Allow area ID in service call schemas (#25121)
* Allow area ID in service call schemas

* Remove ATTR_ENTITY_ID from service light turn off schcema
2019-07-15 13:57:29 -07:00
Paulus Schoutsen
65593e36b1 Verify cloud user exists during boot (#25119) 2019-07-15 13:57:28 -07:00
Paulus Schoutsen
8996e330b8 Simplify Alexa/Google for new climate turn_on/off (#25115) 2019-07-15 13:57:28 -07:00
Markus Jankowski
e85d434f4e Add climate related services to Homematic IP Cloud (#25079)
* add hmip climate services

* Rename accesspoint_id to hapid

to comply with config

* Revert "Rename accesspoint_id to hapid"

This reverts commit 4a3cd14e1482fb508273c728ad8020945b02e426.
2019-07-15 13:57:27 -07:00
Paulus Schoutsen
82d9488ec8 Updated frontend to 20190715.0 2019-07-15 13:55:59 -07:00
Pascal Vizeli
4fc302b67a Update azure-pipelines-release.yml for Azure Pipelines 2019-07-15 22:38:48 +02:00
Pascal Vizeli
9548345ed0 Update azure-pipelines-wheels.yml for Azure Pipelines 2019-07-15 15:23:08 +02:00
Pascal Vizeli
d444ba397b Update azure-pipelines-wheels.yml for Azure Pipelines 2019-07-15 15:22:41 +02:00
Paulus Schoutsen
c884f9edbc Bumped version to 0.96.0b2 2019-07-12 15:09:02 -07:00
Aaron Bach
d0af73efe1 Fix missing sensor unit in RainMachine (#25101) 2019-07-12 15:08:56 -07:00
Aaron Bach
5eb7268ae7 Fix window exception in WWLLN (#25100)
* Beta fix: handle window exception in WWLLN

* Fixed test

* Fix bug

* Member comments

* Removed unused import
2019-07-12 15:08:55 -07:00
On Freund
60c2e5e2e2 Add turn on/off to coolmaster (#25097) 2019-07-12 15:08:54 -07:00
cgtobi
4e69b5b45f Fix Netatmo climate issue when device out of reach (#25096)
* Fix valve/thermostat out of reach

* Fix boost for valves

* Set netatmo default max temp to 30

* Remove unnecessary get

* Remove unnecessary default value

* Readd get
2019-07-12 15:08:54 -07:00
David Bonnes
1d784bdc05 [climate] Add water_heater to evohome (#25035)
* initial commit

* refactor for sync

* minor tweak

* refactor convert code

* fix regression

* remove bad await

* de-lint

* de-lint 2

* address edge case - invalid tokens

* address edge case - delint

* handle no schedule

* improve support for RoundThermostat

* tweak logging

* delint

* refactor for greatness

* use time_zone: for state attributes

* small tweak

* small tweak 2

* have datetime state attributes as UTC

* have datetime state attributes as UTC - delint

* have datetime state attributes as UTC - tweak

* missed this - remove

* de-lint type hint

* use parse_datetime instead of datetime.strptime)

* remove debug code

* state atrribute datetimes are UTC now

* revert

* de-lint (again)

* tweak type hints

* de-lint (again, again)

* tweak type hints

* Convert datetime closer to sending it out
2019-07-12 15:08:53 -07:00
Paulus Schoutsen
9181660497 Updated frontend to 20190712.0 2019-07-12 15:05:02 -07:00
Pascal Vizeli
7fc8ff982b Version bump to 0.96.0b1 2019-07-12 07:24:30 +00:00
Paulus Schoutsen
155c75c54a Guard module being None (#25077) 2019-07-12 07:23:30 +00:00
Anders Melchiorsen
afade4e997 Support podcast episodes as Sonos favorites (#25087) 2019-07-12 07:22:02 +00:00
Pascal Vizeli
53111f6426 Fix powercontrol media player alexa (#25080) 2019-07-12 07:21:59 +00:00
Aaron Bach
53a701b12c Change unique_id formula for Notion entities (#25076)
* Change unique_id formula for Notion entities

* Don't use name
2019-07-12 07:21:57 +00:00
Pascal Vizeli
a0e45cce79 Add support for on/off climate (#25026)
* Add support for on/off climate

* address comments

* Add test for sync overwrite

* Add more tests
2019-07-12 07:21:54 +00:00
Paulus Schoutsen
2b62ea1f0e Do not reverse open/close calls (#24879) 2019-07-12 07:19:26 +00:00
Pascal Vizeli
cc7b65a6c8 Support hass-release inside devcontainer (#25090) 2019-07-12 07:18:31 +00:00
Pascal Vizeli
3b6b421152 Update azure-pipelines-release.yml for Azure Pipelines 2019-07-11 09:24:45 +02:00
Pascal Vizeli
fcb1783f56 Update azure-pipelines-release.yml for Azure Pipelines 2019-07-11 09:22:37 +02:00
Pascal Vizeli
8041339052 Update azure-pipelines-release.yml for Azure Pipelines 2019-07-11 09:17:44 +02:00
Paulus Schoutsen
87d3680630 Merge commit 'df920b4eda1d64368ed3bf166bcb0a90aeec6c44' into rc 2019-07-10 20:54:06 -07:00
Paulus Schoutsen
bd7c0e87d5 Version bump to 0.96.0b0 2019-07-10 20:49:56 -07:00
Paulus Schoutsen
df920b4eda Merge remote-tracking branch 'origin/master' into dev 2019-07-10 20:48:54 -07:00
Paulus Schoutsen
cde3f670c2 pylint 2019-07-10 20:47:27 -07:00
Phil Bruckner
c80683bb15 Restore automation last_triggered as datetime & fix test (#24951)
* Restore automation last_triggered as datetime & fix test

* last_triggered is always a string
2019-07-10 20:42:38 -07:00
Paulus Schoutsen
073327831f Correctly store removed entities for restore state (#25073)
* Correctly store removed entities for restore state

* Lint

* Do not assume about set encoding
2019-07-10 20:41:03 -07:00
Charles Garwood
312fceeaf6 Add websocket API command for Z-Wave network status (#25066)
* Add websocket API command for Z-Wave network status

* lint

* Add callback decorator

* Remove state_str, fix lint
2019-07-10 19:50:42 -07:00
monte-monte
42d2f30ab8 Complete OPERATION_MODES (#25069)
XKNX library has complete list of KNX controller modes, but current version of HA KNX climate plugin uses only two of them and one is named incorrectly ("Dehumidification" instead of "Dry"). https://github.com/XKNX/xknx/blob/master/xknx/knx/dpt_hvac_mode.py
I've added missing control modes, which has corresponding operation mode in HA. Tested this patch on my KNX IntesisBox which is used with Mitsubishi split AC, all modes were detected correctly and working as expected.
I've also corrected datapoint number in a comment, because it was pointing to a wrong one: http://www.sti.uniurb.it/romanell/Domotica_e_Edifici_Intelligenti/110504-Lez10a-KNX-Datapoint%20Types%20v1.5.00%20AS.pdf see page 94.
2019-07-10 15:59:43 -07:00
Jeff Irion
4844477d3a Make sure volume level is valid when incrementing/decrementing (#25061)
* Make sure volume level is not None before incrementing/decrementing

* Pass linting checks
2019-07-10 15:58:29 -07:00
Martijn van Zal
ca8118138c Change phrases in the logbook component for persons and binary_sensors (#25053)
Persons are now threated the same as device trackers, so the logbook states
"<name> is at <location>" or "<name> is away" instead of "<name> changed to <location|not_home>"

Binary sensors now show phrases that relate to their device_class attribute.
So "Front door is closed" instead of "Front door turned off" or "Hallway PIR detected movement"
instead of "Hallway PIR turned on"
2019-07-10 15:56:41 -07:00
Johann Kellerman
e51b5e801e SMA catch error (#25045)
* SMA small fix

* lib update

* req
2019-07-10 15:55:40 -07:00
Aaron Bach
9ccb85d959 Add support for World Wide Lightning Location Network (#25001)
* Add support for World Wide Lightning Location Network

* Updated .coveragerc

* Added test

* Updated requirements

* Fixed tests

* Use local time for nearest strike

* Base geo location in place

* Finished geolocation work

* Fixed tests

* Cleanup

* Removed no-longer-needed method

* Updated requirements

* Add support for window and attrs

* Add strike ID to entity name

* Member comments
2019-07-10 16:40:11 -06:00
Alexei Chetroi
cea857e18a Bump up ZHA dependencies. (#25062)
Bump zigpy-homeassistant to 0.7.0
Bump zigpy-deconz to 0.2.1
Bump zigpy-xbee-homeassistant to 0.4.0
2019-07-10 12:20:37 -07:00
Anders Melchiorsen
1afa136fc0 Fix for Sonos debug logging (#25064)
* Fix for Sonos debug logging

* Start logging messages with capital letters
2019-07-10 12:19:28 -07:00
Paulus Schoutsen
7d33b0a259 Fix broken test in Python 3.7 (#25067) 2019-07-10 12:17:10 -07:00
David F. Mulcahey
777e1ca832 bump zha-quirks version (#25059) 2019-07-10 11:59:06 -07:00
Johann Kellerman
2e26f0bd2b Add check_config helper (#24557)
* check_config

* no ignore

* tests

* try tests again
2019-07-10 11:56:50 -07:00
Penny Wood
236debb455 Avoid flooding steam API (#23941) 2019-07-10 11:15:42 -07:00
Paulus Schoutsen
5f5c541f2f Update translations 2019-07-10 10:50:50 -07:00
Paulus Schoutsen
f0f7dc4884 Updated frontend to 20190710.0 2019-07-10 10:49:07 -07:00
Anders Melchiorsen
18d27c997d Add Sonos debug logging (#25063) 2019-07-10 09:30:45 -07:00
David Bonnes
a44686389c [climate] Bugfix honeywell misleading error message (#25048)
* initial commit

* refactor for sync

* minor tweak

* refactor convert code

* fix regression

* remove bad await

* de-lint

* de-lint 2

* improve error message

* rebase

* tweak

* de-lint
2019-07-10 08:38:31 -07:00
cdce8p
98ba015f06 Remove myself as codeowner (#25043) 2019-07-10 08:36:17 -07:00
Matte23
c1c2159dee Added marker sensor to CUPS integration (#25037) 2019-07-10 08:35:30 -07:00
Paul Annekov
a30c37017b Update tuyaha to 0.0.2 to catch API exceptions (#25050)
* Update tuyaha to 0.0.2 to catch API exceptions

* Updated tuyaha version in requirements
2019-07-10 01:54:19 +02:00
Aaron Bach
195b034abc Add config flow support to Geolocation (#25046) 2019-07-10 00:50:16 +02:00
William Sutton
c5239c6176 Add radiotherm CT80 current humidity support (#25024)
* Added CT80 Current Humidity Support

Added a check for if device is a CT80, and if so, queries the humidity object to get the current measured humidity reading.

* Update climate.py

Removed whitespace on line 229

* Update climate.py

Added humidity property. Version on local machine had that from previous tinkering.

* Update climate.py

Removed whitespace

* Update climate.py

Fixed tstat error handling for humidity data.
2019-07-09 21:18:05 +02:00
jlrgraham
5be695c49c Bump pyvera to 0.3.2, null/missing value protection (#25041)
* Bump pyvera to 0.3.2, null/missing value protection.

* Add another place where the pyvera version is set.
2019-07-09 20:06:45 +02:00
cgtobi
8652c84745 Fix Netatmo rain gauge precision (#25036) 2019-07-09 19:57:29 +02:00
Franck Nijhof
36ed725ab4 Improve toon climate (#25040)
* Renames internal climate state variable to preset

* Shorten function comments

* Updates local variables on preset and temp changes

* Adds support for hvac_action
2019-07-09 19:52:38 +02:00
Malte Franken
cf5a35a421 updated geojson_client library to version 0.4 (#25039) 2019-07-09 13:06:10 -04:00
Fabian Affolter
8256d72f6d Upgrade youtube_dl to 2019.07.02 (#24990)
* Upgrade youtube_dl to 2019.07.01

* Update homeassistant/components/media_extractor/manifest.json

Co-Authored-By: Josef Schlehofer <pepe.schlehofer@gmail.com>

* Update requirements_all.txt

Co-Authored-By: Josef Schlehofer <pepe.schlehofer@gmail.com>
2019-07-09 13:03:52 -04:00
Pascal Vizeli
25745e9e27 Update build pipeline 2019-07-09 15:32:09 +02:00
Franck Nijhof
3ce1049d21 Centralizes Toon data, reducing API calls (#23988)
* Centralizes Toon data, reducing API calls

Fixes #21825

Signed-off-by: Franck Nijhof <frenck@addons.community>

* Fixes bad copy past action in services.yaml

Signed-off-by: Franck Nijhof <frenck@addons.community>

* Addresses review comments

Signed-off-by: Franck Nijhof <frenck@addons.community>

* 👕 Fixes too many blank lines

* Unsub dispatcher
2019-07-09 14:18:51 +02:00
arigilder
f3e542542a Add missing support for jewish_calendar.omer_count sensor (#24958)
* Add missing support for omer_count to jewish_calendar

* Add tests for omer sensor

* Add tests for omer after tzeit hakochavim

* Lint fixes
2019-07-09 11:58:57 +02:00
cgtobi
07b635e7aa Fix Netatmo climate presets (#25029)
* Fix netatmo presets

* Remove off mode for valves

* Revert usage of global const

* Flip values

* Remove try...except block
2019-07-09 10:40:02 +02:00
Aaron Bach
c2e843cbc3 Add support for Notion Home Monitoring (#24634)
* Add support for Notion Home Monitoring

* Updated coverage

* Removed auto-generated translations

* Stale docstrings

* Corrected hardware version

* Fixed binary sensor representation

* Cleanup and update protection

* Updated log message

* Cleaned up is_on

* Updated docstring

* Modified which data is updated during async_update

* Added more checks during update

* More cleanup

* Fixed unhandled exception

* Owner-requested changes (round 1)

* Fixed incorrect scan interval retrieval

* Ugh

* Removed unnecessary import

* Simplified everything via dict lookups

* Ensure bridges are properly registered

* Fixed tests

* Added catch for invalid credentials

* Ensure bridge ID is updated as necessary

* Updated method name

* Simplified bridge update

* Add support for updating bridge via_device_id

* Device update guard clause

* Removed excess whitespace

* Whitespace

* Owner comments

* Member comments
2019-07-09 10:29:06 +02:00
Andrew Sayre
7a5fca69af Add hvac fan state (#25030) 2019-07-09 09:59:48 +02:00
Franck Nijhof
3016d3a186 Toon fixes for Climate 1.0 (#25027) 2019-07-09 08:44:30 +02:00
Andrew Sayre
a31e49c857 Improve SmartThings test mocking (#25028)
* Migrate to asynctest

* Simplify mock access

* Use mocks
2019-07-08 22:39:55 -04:00
Joakim Plate
2fbbcafaed Support config flow on custom components (#24946)
* Support populating list of flows from custom components

* Re-allow custom component config flows

* Add tests for custom component retrieval

* Don't crash view if no handler exist

* Use get_custom_components instead fo resolve_from_root

* Switch to using an event instead of lock

* Leave list of integrations as set

* The returned list is not guaranteed to be ordered

Backend uses a set to represent them.
2019-07-09 01:19:37 +02:00
Pascal Vizeli
a2237ce5d4 homematic add off support for climate (#25017)
* homematic add off support for climate

* fix lint
2019-07-09 00:00:25 +02:00
Daniel Høyer Iversen
af7f61fec2 ambiclimate hvac_modes (#25015)
* ambiclimate hvac_modes

* style
2019-07-08 14:12:23 -07:00
Paulus Schoutsen
26a66276cd Fix Nest sensor (#25023) 2019-07-08 14:12:02 -07:00
Phil Bruckner
9944e675a5 Add template support to state trigger's for option (#24912) 2019-07-08 13:59:58 -07:00
Phil Bruckner
f9b9883aba Add template support to numeric_state trigger's for option (#24955) 2019-07-08 13:58:50 -07:00
Phil Bruckner
1431fd6fbd Add datetime option to input_datetime.set_datetime service (#24975) 2019-07-08 13:18:42 -07:00
Paulus Schoutsen
b11171aaeb Fix mimetypes on borked Windows machines (#25018) 2019-07-08 13:16:22 -07:00
Paulus Schoutsen
0b7a901c81 Fix ecobee flaky test (#25019) 2019-07-08 13:10:01 -07:00
Daniel Høyer Iversen
662e0dde80 Sensibo, add HVAC_MODE_OFF (#25016) 2019-07-08 13:17:59 -04:00
Joakim Plate
ab832cda71 Add support for arcam fmj receivers (#24621)
* Add arcam_fmj support

* Just use use state in player avoid direct client access

* Avoid leaking exceptions on invalid data

* Fix return value for volume in case of 0

* Mark component as having no coverage

* Add new requirement

* Add myself as maintainer

* Correct linting errors

* Use async_create_task instead of async_add_job

* Use new style string format instead of concat

* Don't call init of base class without init

* Annotate callbacks with @callback

Otherwise they won't be called in loop

* Reduce log level to debug

* Use async_timeout instead of wait_for

* Bump to version of arcam_fmj supporting 3.5

* Fix extra spaces

* Drop somewhat flaky unique_id

* Un-blackify ident to satisy pylint

* Un-blackify ident to satisy pylint

* Move default name calculation to config validation

* Add test folder

* Drop unused code

* Add tests for config flow import
2019-07-08 17:14:19 +02:00
Jesse Rizzo
f90fe7e628 Enphase envoy individual inverter production (#24445)
* bump envoy_reader version to 0.4

* bump dependency envoy_reader to 0.4

* Enphase envoy get individual inverter production

* Add period in function description

* Fix dumb typo

* Define _attributes in __init__

* Better error messages, make update async

* Fix format error

* Fix pylint errors

* set unknown state to None

* Bump envoy_reader version to 0.8

* Change attributes to separate sensors

* Fix dumb thing

* Improve platform_setup for inverters

* Remove unneeded self._attributes, refactor platform setup

* Refactor platform setup
2019-07-08 10:21:08 -04:00
Chris Johnston
32685f16bf Implement Twilio SMS notify MediaUrl support (#24971)
* Implement Twilio SMS notify MediaUrl support

Adds support for setting the `media_url` parameter of the twilio API
client with an optional attribute under the notify `data`
attribute.

Per the twilio docs (https://www.twilio.com/docs/sms/send-messages#include-medi$
this feature is only available in the US and Canada, for
GIF, PNG, or JPEG content.

* lint: fix 80 char ruler

* use kwargs to set the media_url

after testing locally, seems like the previous way of using
object() was not working. this seems to be working

* re-use the ATTR_MEDIAURL attribute
2019-07-08 14:05:15 +02:00
Pascal Vizeli
84cf76ba36 Climate 1.0 (#23899)
* Climate 1.0 / part 1/2/3

* fix flake

* Lint

* Update Google Assistant

* ambiclimate to climate 1.0 (#24911)

* Fix Alexa

* Lint

* Migrate zhong_hong

* Migrate tuya

* Migrate honeywell to new climate schema (#24257)

* Update one

* Fix model climate v2

* Cleanup p4

* Add comfort hold mode

* Fix old code

* Update homeassistant/components/climate/__init__.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/climate/const.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* First renaming

* Rename operation to hvac for paulus

* Rename hold mode to preset mode

* Cleanup & update comments

* Remove on/off

* Fix supported feature count

* Update services

* Update demo

* Fix tests & use current_hvac

* Update comment

* Fix tests & add typing

* Add more typing

* Update modes

* Fix tests

* Cleanup low/high with range

* Update homematic part 1

* Finish homematic

* Fix lint

* fix hm mapping

* Support simple devices

* convert lcn

* migrate oem

* Fix xs1

* update hive

* update mil

* Update toon

* migrate deconz

* cleanup

* update tesla

* Fix lint

* Fix vera

* Migrate zwave

* Migrate velbus

* Cleanup humity feature

* Cleanup

* Migrate wink

* migrate dyson

* Fix current hvac

* Renaming

* Fix lint

* Migrate tfiac

* migrate tado

* Fix PRESET can be None

* apply PR#23913 from dev

* remove EU component, etc.

* remove EU component, etc.

* ready to test now

* de-linted

* some tweaks

* de-lint

* better handling of edge cases

* delint

* fix set_mode typos

* apply PR#23913 from dev

* remove EU component, etc.

* ready to test now

* de-linted

* some tweaks

* de-lint

* better handling of edge cases

* delint

* fix set_mode typos

* delint, move debug code

* away preset now working

* code tidy-up

* code tidy-up 2

* code tidy-up 3

* address issues #18932, #15063

* address issues #18932, #15063 - 2/2

* refactor MODE_AUTO to MODE_HEAT_COOL and use F not C

* add low/high to set_temp

* add low/high to set_temp 2

* add low/high to set_temp - delint

* run HA scripts

* port changes from PR #24402

* manual rebase

* manual rebase 2

* delint

* minor change

* remove SUPPORT_HVAC_ACTION

* Migrate radiotherm

* Convert touchline

* Migrate flexit

* Migrate nuheat

* Migrate maxcube

* Fix names maxcube const

* Migrate proliphix

* Migrate heatmiser

* Migrate fritzbox

* Migrate opentherm_gw

* Migrate venstar

* Migrate daikin

* Migrate modbus

* Fix elif

* Migrate Homematic IP Cloud to climate-1.0 (#24913)

* hmip climate fix

* Update hvac_mode and preset_mode

* fix lint

* Fix lint

* Migrate generic_thermostat

* Migrate incomfort to new climate schema (#24915)

* initial commit

* Update climate.py

* Migrate eq3btsmart

* Lint

* cleanup PRESET_MANUAL

* Migrate ecobee

* No conditional features

* KNX: Migrate climate component to new climate platform (#24931)

* Migrate climate component

* Remove unused code

* Corrected line length

* Lint

* Lint

* fix tests

* Fix value

* Migrate geniushub to new climate schema (#24191)

* Update one

* Fix model climate v2

* Cleanup p4

* Add comfort hold mode

* Fix old code

* Update homeassistant/components/climate/__init__.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/climate/const.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* First renaming

* Rename operation to hvac for paulus

* Rename hold mode to preset mode

* Cleanup & update comments

* Remove on/off

* Fix supported feature count

* Update services

* Update demo

* Fix tests & use current_hvac

* Update comment

* Fix tests & add typing

* Add more typing

* Update modes

* Fix tests

* Cleanup low/high with range

* Update homematic part 1

* Finish homematic

* Fix lint

* fix hm mapping

* Support simple devices

* convert lcn

* migrate oem

* Fix xs1

* update hive

* update mil

* Update toon

* migrate deconz

* cleanup

* update tesla

* Fix lint

* Fix vera

* Migrate zwave

* Migrate velbus

* Cleanup humity feature

* Cleanup

* Migrate wink

* migrate dyson

* Fix current hvac

* Renaming

* Fix lint

* Migrate tfiac

* migrate tado

* delinted

* delinted

* use latest client

* clean up mappings

* clean up mappings

* add duration to set_temperature

* add duration to set_temperature

* manual rebase

* tweak

* fix regression

* small fix

* fix rebase mixup

* address comments

* finish refactor

* fix regression

* tweak type hints

* delint

* manual rebase

* WIP: Fixes for honeywell migration to climate-1.0 (#24938)

* add type hints

* code tidy-up

* Fixes for incomfort migration to climate-1.0 (#24936)

* delint type hints

* no async unless await

* revert: no async unless await

* revert: no async unless await 2

* delint

* fix typo

* Fix homekit_controller on climate-1.0 (#24948)

* Fix tests on climate-1.0 branch

* As part of climate-1.0, make state return the heating-cooling.current characteristic

* Fixes from review

* lint

* Fix imports

* Migrate stibel_eltron

* Fix lint

* Migrate coolmaster to climate 1.0 (#24967)

* Migrate coolmaster to climate 1.0

* fix lint errors

* More lint fixes

* Fix demo to work with UI

* Migrate spider

* Demo update

* Updated frontend to 20190705.0

* Fix boost mode (#24980)

* Prepare Netatmo for climate 1.0 (#24973)

* Migration Netatmo

* Address comments

* Update climate.py

* Migrate ephember

* Migrate Sensibo

* Implemented review comments (#24942)

* Migrate ESPHome

* Migrate MQTT

* Migrate Nest

* Migrate melissa

* Initial/partial migration of ST

* Migrate ST

* Remove Away mode (#24995)

* Migrate evohome, cache access tokens (#24491)

* add water_heater, add storage - initial commit

* add water_heater, add storage - initial commit

delint

add missing code

desiderata

update honeywell client library & CODEOWNER

add auth_tokens code, refactor & delint

refactor for broker

delint

* Add Broker, Water Heater & Refactor

add missing code

desiderata

* update honeywell client library & CODEOWNER

add auth_tokens code, refactor & delint

refactor for broker

* bugfix - loc_idx may not be 0

more refactor - ensure pure async

more refactoring

appears all r/o attributes are working

tweak precsion, DHW & delint

remove unused code

remove unused code 2

remove unused code, refactor _save_auth_tokens()

* support RoundThermostat

bugfix opmode, switch to util.dt, add until=1h

revert breaking change

* store at_expires as naive UTC

remove debug code

delint

tidy up exception handling

delint

add water_heater, add storage - initial commit

delint

add missing code

desiderata

update honeywell client library & CODEOWNER

add auth_tokens code, refactor & delint

refactor for broker

add water_heater, add storage - initial commit

delint

add missing code

desiderata

update honeywell client library & CODEOWNER

add auth_tokens code, refactor & delint

refactor for broker

delint

bugfix - loc_idx may not be 0

more refactor - ensure pure async

more refactoring

appears all r/o attributes are working

tweak precsion, DHW & delint

remove unused code

remove unused code 2

remove unused code, refactor _save_auth_tokens()

support RoundThermostat

bugfix opmode, switch to util.dt, add until=1h

revert breaking change

store at_expires as naive UTC

remove debug code

delint

tidy up exception handling

delint

* update CODEOWNERS

* fix regression

* fix requirements

* migrate to climate-1.0

* tweaking

* de-lint

* TCS working? & delint

* tweaking

* TCS code finalised

* remove available() logic

* refactor _switchpoints()

* tidy up switchpoint code

* tweak

* teaking device_state_attributes

* some refactoring

* move PRESET_CUSTOM back to evohome

* move CONF_ACCESS_TOKEN_EXPIRES CONF_REFRESH_TOKEN back to evohome

* refactor SP code and dt conversion

* delinted

* delinted

* remove water_heater

* fix regression

* Migrate homekit

* Cleanup away mode

* Fix tests

* add helpers

* fix tests melissa

* Fix nehueat

* fix zwave

* add more tests

* fix deconz

* Fix climate test emulate_hue

* fix tests

* fix dyson tests

* fix demo with new layout

* fix honeywell

* Switch homekit_controller to use HVAC_MODE_HEAT_COOL instead of HVAC_MODE_AUTO (#25009)

* Lint

* PyLint

* Pylint

* fix fritzbox tests

* Fix google

* Fix all tests

* Fix lint

* Fix auto for homekit like controler

* Fix lint

* fix lint
2019-07-08 14:00:24 +02:00
Joakim Plate
c2f1c4b981 Correct socket use in cert_expiry platform (#25011)
* Make sure we use same family for ssl socket and connection

getaddrinfo result could be different from what connection
was made with. It also blocks potential use of
happy eye balls algorithm

This also fixes lingering sockets until python garbage
collection.

* Add availability value if unable to get expiry

* Fix lint issue
2019-07-08 11:33:23 +02:00
Seweryn Zeman
31d7b702a6 Added missing yeelight models mapping (#24963) 2019-07-07 23:50:48 -04:00
Joakim Sørensen
df4caf41d0 Install requirements for integrations in packages before importing them. (#25005)
* Process requirements for integrations in packages before loading

* trigger buld
2019-07-07 12:04:30 -07:00
Tom Harris
0595fc3097 Upgrade insteonplm to 0.16.0 and add INSTEON scene triggering (#24765)
* Upgrade insteonplm to 0.16.0 and add INSTEON scene triggering

* Fix spacing issue

* Dummy commit to trigger CLA

* Remove dummy change

* Code review changes

* Use ENTITY_MATCH_ALL keyword from const and lint cleanup

* Make entity method print_aldb private
2019-07-07 20:31:04 +02:00
Tsvi Mostovicz
b0dc782c98 Upgrade hdate==0.8.8 (#25008)
This should fix incosistencies between issur_melacha_in_effect sensor and candle_lighting time.

Probably fixes #24479 and #23852
2019-07-07 17:32:54 +02:00
Daniel Høyer Iversen
ecd7f86df0 upgrade switchmate to latest lib (#25006) 2019-07-07 13:02:13 +02:00
Ville Skyttä
b834671555 Test dependency updates (#25004)
* Upgrade pytest to 5.0.1

https://docs.pytest.org/en/latest/changelog.html#pytest-5-0-1-2019-07-04

* Upgrade asynctest to 0.13.0

* Upgrade requests_mock to 1.6.0
2019-07-07 12:30:31 +02:00
Dave T
6e24b52a7e Add support for aurora ABB Powerone solar photovoltaic inverter (#24809)
* Add support for aurora ABB Powerone solar photovoltaic inverter

* Add support for aurora ABB Powerone solar photovoltaic inverter

* Update stale docstring

* Fixed whitespace lint errors

* Remove test code

* Delete README.md

Website documentation contains setup instructions.  README not needed here.

* Only close the serial line once.

* Correct newlines between imports

* Change add_devices to add_entites and remove unnecessary logging.

* Use new style string formatting instead of concatenation

* Directly access variables rather than via config.get

* Update sensor.py
2019-07-07 11:22:21 +02:00
David Winn
628e12c944 Sleepiq single sleeper crash (#24941)
* Update sleepyq to 0.7

Fixes crash when working with a single sleeper.

* sleepiq: Handle null side definitions

These happen if no sleeper is defined for a side of the bed. Don't
create sensors for null sides; they'll crash every time we try to use
them.

* sleepiq: Fix urls mocked to match sleepyq 0.7

* sleepi: Fix test_sensor.TestSleepIQSensorSetup

Sleepyq 0.7 throws on empty strings, so we have to specify them.

* sleepiq: Test for ValueError thrown by sleepyq 0.7

* sleepiq: Drop no longer used HTTPError import

* sleepiq: Add tests for single sleeper case

* sleepiq: Shorten comments to not overflow line length

* sleepiq: Use formatted string literals for adding suffixes to test files

* sleepiq: Use str.format() for test suffixing
2019-07-07 08:40:02 +02:00
Penny Wood
adbec5bffc Changes as per code review of #24646 (#24917) 2019-07-07 07:36:57 +02:00
Ville Skyttä
e8a5306c23 Upgrade mypy to 0.711, drop no longer needed workarounds (#24998)
https://mypy-lang.blogspot.com/2019/06/mypy-0711-released.html
2019-07-07 03:58:33 +02:00
Franck Nijhof
b274b10f38 Adds Stale Probot for issues (#24985)
* Adds Stale Probot for issues

* Do not ignore assigned issues

* Small language tweak in mark comment
2019-07-06 20:18:20 +02:00
Franck Nijhof
ac4f2c9f73 Adds Lock Threads Probot (#24984) 2019-07-06 19:48:08 +02:00
Paul Annekov
97ed7fbb3f Switched from tuyapy to tuyaha as 1st one is not maintained (#24821) 2019-07-06 10:39:49 -07:00
Robert Dunmire III
003ca655ee Fix errors if rest source becomes unavailable (#24986)
* Fix errors if rest source becomes unavailable

* Remove exclamation mark
2019-07-06 19:33:37 +02:00
Adriaan Peeters
412910ca65 Add sonos.play_queue service (#24974)
* Add sonos.play_queue service

* Add SERVICE_PLAY_QUEUE import in alphabetical order

* Add queue_position parameter for sonos.play_queue service

* Move queue_position default to schema definition
2019-07-06 17:19:03 +02:00
Franck Nijhof
31f569ada9 Batch of Component(s) -> Integration(s) (#24972) 2019-07-05 15:24:26 -07:00
Niels Mündler
e75c9efb3f Fix monitoring of trays in syncthru component (#24961) 2019-07-05 11:23:17 +02:00
cgtobi
e93919673e Implement ADR0003 for Netatmo sensor (#24944)
* Remove configurable monitored conditions

* Only process existing modules

* Remove unused import

* Fix linter error
2019-07-05 09:41:18 +02:00
John Mihalic
c814b39fdb Update pyHik library to 0.2.3 (#24957) 2019-07-05 09:29:35 +02:00
Aaron Bach
a491f97eb9 Allow updating of via_device in device registry (#24921)
* Allow updating of via_device in device registry

* Added test
2019-07-04 19:10:23 -04:00
David F. Mulcahey
3c487928d4 New scanner device tracker and ZHA device tracker support (#24584)
* initial implementation for zha device trackers

* constant

* review comments

* Revert "review comments"

This reverts commit 2130823566820dfc114dbeda08fcdf76ed47a4e7.

* rename device tracker entity

* update trackers

* raise when not implemented

* Update homeassistant/components/device_tracker/config_entry.py

Review comment

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* move source type to base state attrs

* review comments

* review comments

* review comments

* fix super call

* fix battery and use last seen from device

* add test

* cleanup and add more to test

* cleanup post zha entity removal PR

* add tests for base entities

* rework entity tests
2019-07-04 12:44:39 +02:00
Steven Rollason
e824c553ca Fix exclusion of routes with excl_filter (#24928)
Fix exclusion of routes with excl_filter (was including instead of excluding)
2019-07-03 19:48:01 -04:00
Chris Soyars
2634f35b4e Add support for Yale YRL256 lock (#24932) 2019-07-03 19:29:21 -04:00
Anders Melchiorsen
a1aaeab33a Update pysonos to 0.0.19 (#24930) 2019-07-03 19:26:16 -04:00
Jeff Irion
e9816f7e30 Bump androidtv to 0.0.18 (#24927)
* Bump androidtv to 0.0.18

* Bump androidtv to 0.0.18
2019-07-03 20:18:37 +02:00
David F. Mulcahey
a9459c6d92 Remove ZHA device entity (#24909)
* move availability handling to device

* update last_seen format

* add battery sensor

* fix interval

* fix battery reporting now that it is a sensor

* remove zha entities and add battery sensor
2019-07-03 13:36:36 -04:00
Дубовик Максим
eec67d8b1a New languages that looks like supported by Google but not documented: (#24881)
* cs-CZ – Czech, Czech Republic
* el-GR – Modern Greek (1453-), Greece
* en-IN – English, India
* fi-FI – Finnish, Finland
* fil-PH – Filipino, Philippines
* hi-IN – Hindi, India
* id-ID – Indonesian, Indonesia
* vi-VN – Vietnamese, Viet Nam
Fixed regex expression to match language codes like fil-PH
2019-07-03 16:40:14 +02:00
cgtobi
e8d9fe0aa8 Fix home coach discovery (#24902)
* Fix home coach discovery

* Update requirements file
2019-07-02 21:55:01 -04:00
Paulus Schoutsen
aa03550f6b Updated frontend to 20190702.0 2019-07-02 10:34:22 -07:00
kreegahbundolo
61c88db8a1 Add ability to send attachments in pushover notifications (#24806)
* Added ability to send attachments in pushover notifications

* Added full name for exception to satisfy static check

* Fixed hanging indent lint problem

* Added path checking, removed import re, changed url check method to use
startswith.

* Removed argument from logging statement.

* Changed IOError to OSError, fixed logging, added logging statement.
2019-07-02 17:56:12 +02:00
Phil Bruckner
8dca73d08e Add missing trigger.for variable to template trigger (#24893) 2019-07-02 17:46:26 +02:00
Phil Bruckner
3f4ce70414 Fix 'same state' monitoring in state trigger (#24904) 2019-07-02 17:29:38 +02:00
Phil Bruckner
945afbc6d4 Fix 'same state' monitoring in numeric_state trigger (#24910) 2019-07-02 17:28:02 +02:00
Anders Melchiorsen
c0a342d790 Stability improvements for Sonos availability (#24880)
* Stability improvements for Sonos availability

* Handle seen reentrancy
2019-07-02 09:25:02 -04:00
Pascal Vizeli
6de6c10bc3 Update devcontainer.json 2019-07-02 14:31:06 +02:00
Pascal Vizeli
6c25c9760a Update devcontainer.json 2019-07-02 13:34:50 +02:00
Pascal Vizeli
7bf140f921 Update devcontainer.json 2019-07-02 13:32:35 +02:00
Phil Bruckner
e3d281b3c4 Bump life360 package to 4.0.1 (#24905) 2019-07-02 12:14:46 +02:00
Pascal Vizeli
0c43c4b5e1 Add git editor / app port 2019-07-02 10:39:02 +02:00
Penny Wood
23dd644f4a Update IDs for rename node/value (#24646)
* Update IDs for rename node/value

* Rename devices and entities

* Improved coverage
2019-07-01 15:54:19 -07:00
David F. Mulcahey
7f90a1cab2 go back to signals and no hard entity references (#24894) 2019-07-01 16:32:57 -04:00
kevank
b6e0f538c5 Update tts.py (#24892) 2019-07-01 10:49:27 -07:00
Dennis Keitzel
8cd138608c Support mqtt discovery topic prefix with slashes (#24840) 2019-07-01 10:23:01 -07:00
David Bonnes
846575b7fb Tweak geniushub battery icons according to device state (#24798)
* tweak battery icons according to device state/availability

* tweak battery icons according to device state/availability 2

* make dt objects aware

* make dt objects aware 2

* woops - use util.dt in favour of datetime

* woops - use util.dt in favour of datetime 2

* refactor battery icon code, remove parallel_updates
2019-07-01 10:19:14 -07:00
Daniel Høyer Iversen
3d2f843c1d Upgrade pytest to 5.0.0 (#24885)
* Upgrade pytest to 5.0.0

* exception message for pytest 5
2019-07-01 10:47:42 -04:00
Jeff Irion
5ba83d4dfb Bump androidtv to 0.0.17 (#24886)
* Bump androidtv to 0.0.17

* Bump androidtv to 0.0.17
2019-07-01 10:47:21 -04:00
Paulus Schoutsen
0dd19ed49c Updated frontend to 20190630.0 2019-06-30 22:53:35 -07:00
Paulus Schoutsen
77b83b9e4d Update translations 2019-06-30 22:53:27 -07:00
Andrew Sayre
7db4eeaf7f Move SmartThings imports to top (#24878)
* Move imports to top

* use lib constants

* Add missing three_axis mapping
2019-06-30 22:29:21 -04:00
David F. Mulcahey
7d651e2b7a Fix traceback during ZHA device removal (#24882)
* fix device remove lifecycle
* clean up remove signal
* add guard
2019-06-30 21:12:27 -04:00
Fabian Affolter
40c424e793 Upgrade bcrypt to 3.1.7 (#24850) 2019-06-30 20:23:47 -04:00
Fabian Affolter
a6ea5d43b4 Upgrade importlib-metadata to 0.18 (#24848) 2019-06-30 20:23:27 -04:00
Maikel Punie
bf70e91a0d Velbus: autodiscover covers (#24877)
* Added covers to the velbus component with autodicovery, bumped python velbus version

* Fixed some pylint stuff
2019-06-30 13:02:07 -07:00
Fabian Affolter
5cf923ead6 Upgrade youtube_dl to 2019.06.27 (#24875) 2019-06-30 13:52:08 -04:00
realthk
fec2461e0e Hungarian is also supported in Google Cloud TTS (#24861)
* Hungarian is also a supported language

* Hungarian is also a supported language

* Hungarian is also a supported language
2019-06-30 13:50:06 -04:00
Fabian Affolter
c71a5643ff Update praw to 6.3.1 (#23737)
* Upgrade praw to 6.3.1

* Update praw to 6.3.1
2019-06-30 16:49:16 +02:00
zewelor
b0387c4428 Fix mysensors icon name (#24871) 2019-06-30 12:15:29 +02:00
Fabian Affolter
1e149a704b Upgrade cryptography to 2.7 (#24852) 2019-06-30 07:21:35 +02:00
Fabian Affolter
cb71b4a657 Upgrade psutil to 5.6.3 (#24854) 2019-06-29 11:40:57 -04:00
Fabian Affolter
26cc41094d Upgrade jinja2 to >=2.10.1 (#24851) 2019-06-29 15:47:22 +02:00
Fabian Affolter
9946b19735 Upgrade pyyaml to 5.1.1 (#24847) 2019-06-29 14:34:55 +02:00
Fabian Affolter
6ad9a97f0d Upgrade certifi to >= 2019.6.16 (#24846) 2019-06-29 14:34:27 +02:00
Fabian Affolter
a91ad0189e Upgrade numpy to 1.16.4 (#24845) 2019-06-29 07:15:32 -04:00
Fabian Affolter
67b6657bcd Upgrade sqlalchemy to 1.3.5 (#24844) 2019-06-29 07:14:47 -04:00
Fabian Affolter
e1a34c8030 Upgrade luftdaten to 0.6.1 (#24842)
* Upgrade luftdaten to 0.6.0

* Upgrade luftdaten to 0.6.1
2019-06-29 11:03:38 +02:00
zewelor
b70f907d25 Fix yeelight color temp getter (#24830)
* Fix yeelight color temp getter

* Remove wrong types
2019-06-28 22:56:11 -07:00
Jonathan Keljo
cde855f67d Upgrade sisyphus-control to 2.2 (#24837)
PR #22457 added some code that used new methods in `sisyphus-control` 2.2.
Unfortunately, because of the move to manifests it was merged still depending
on 2.1.

Fixes #24834
2019-06-28 22:45:57 -07:00
Paulus Schoutsen
9cf43dd8ff Merge pull request #24839 from home-assistant/rc
0.95.4
2019-06-28 22:42:23 -07:00
Phil Bruckner
03e6a92cf3 Add template support to template trigger's for option (#24810) 2019-06-28 22:30:47 -07:00
Paulus Schoutsen
21c2e8da6e Bumped version to 0.95.4 2019-06-28 22:23:22 -07:00
Paulus Schoutsen
b3963e56ec Guard for None entity config (#24838) 2019-06-28 22:23:17 -07:00
zewelor
c6e8e2398c Improve autodiscovered yeelights model detection (#24671)
* Improve autodiscovered yeelights model detection

* Lint fixes

* Logger warn fix
2019-06-28 22:23:17 -07:00
Paulus Schoutsen
4b5718431d Guard for None entity config (#24838) 2019-06-28 22:23:00 -07:00
Niels Mündler
333e1d6789 Fronius (solar energy and inverter) component (#22316)
* Introduced fronius component that adds ability to track Fronius devices from Home Assistant

* Use device parameter for fetching inverter data

* Fixed handling of default scope

* Handle exceptions from yield

* Fulfill PR requirements

* Fixed houndci violations

* Found the last hound violation

* Fixed docstring (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165776934)

* Fixed import order with isort (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165776957)

* CONF_DEVICE is now CONF_DEVICEID (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165777161)

* Added docstring to class FroniusSensor (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165777792)

* Fixed docstring for state (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165777885)

* Added/fixed docstrings (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165778108 & https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165778125)

* Remove redundant log entry (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165779213)

* Fixed error message if sensor update fails (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165779435)

* Fixed error log messages (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165779751 & https://github.com/home-assistant/home-assistant/pull/11446#discussion_r165779761)

* Satisfy hound

* Handle exceptions explicit (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r168940902)

* Removed unnecessary call of update (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r168940894)

* The point makes the difference.

* Removed unrelated requirements

* Remove config logging (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r168968748)

* Reorder and fix imports (https://github.com/home-assistant/home-assistant/pull/11446#discussion_r168968725, https://github.com/home-assistant/home-assistant/pull/11446#discussion_r168968691)

* Update fronius requirement

* Various small fixes

* Small fixes

* Formatting

* Add fronius to coverage

* New structure and formatting

* Add manifest.json

* Fix data loading

* Make pylint happy

* Fix issues

* Fix parse_attributes

* Fix docstring and platform schema

* Make use of default HA-Const config values

* Change configuration setup, introducing list of monitored conditions

* Change the structure slightly, allowing for a list of sensors

* Remove periods from logging

* Formatting

* Change name generation, use variable instead of string

* small fixes

* Update sensor.py

* Incorporate correction proposals

* Setting default device inside validation

* Move import on top and small format

* Formatting fix

* Rename validation method to _device_id_validator
2019-06-28 20:48:52 -07:00
Paulus Schoutsen
2e9c71f2c0 Merge pull request #24836 from home-assistant/rc
0.95.3
2019-06-28 20:45:21 -07:00
Paulus Schoutsen
072879cc6e Bumped version to 0.95.3 2019-06-28 20:44:23 -07:00
Paulus Schoutsen
cc75adfed6 Alexa sync state report (#24835)
* Do a sync after changing state reporting

* Fix entity config being None
2019-06-28 20:44:19 -07:00
Paulus Schoutsen
3cafc1f2c6 Alexa sync state report (#24835)
* Do a sync after changing state reporting

* Fix entity config being None
2019-06-28 20:43:57 -07:00
Aaron Bach
19a65f8db6 Remove temperature attribute from SimpliSafe alarm control panel (#24833) 2019-06-28 20:38:07 -07:00
Paulus Schoutsen
e8d1d28fdd Make sure alert is set up after notify (#24829) 2019-06-28 16:28:33 -06:00
Pascal Vizeli
9ad063ce03 Update azure-pipelines-ci.yml for Azure Pipelines 2019-06-28 22:55:04 +02:00
Paulus Schoutsen
f67693c56c Fix vacuum tests 2019-06-28 13:41:25 -07:00
Pascal Vizeli
9616fbdc36 Update azure-pipelines-ci.yml for Azure Pipelines 2019-06-28 22:31:27 +02:00
Pascal Vizeli
48dd5af9e3 Update azure-pipelines-ci.yml for Azure Pipelines 2019-06-28 22:18:52 +02:00
Pascal Vizeli
bc0fb5e3d9 Update azure-pipelines-ci.yml for Azure Pipelines 2019-06-28 22:08:29 +02:00
Pascal Vizeli
8e2bbf8c82 Update azure-pipelines-ci.yml for Azure Pipelines 2019-06-28 19:38:13 +02:00
Pascal Vizeli
538caafac2 Full speed azure 2019-06-28 17:35:17 +00:00
Paulus Schoutsen
0871d6c9c6 Merge pull request #24828 from home-assistant/rc
0.95.2
2019-06-28 10:30:01 -07:00
Luuk
468b0e8934 Add template vacuum support (#22904)
* Add template vacuum component

* Fix linting issues

* Make vacuum state optional

* Fix pylint issues

* Add context to template vacuum service calls

* Added tests to template vacuum

* Fix indent

* Fix docstrings

* Move files for new component folder structure

* Revert additions for template_vacuum tests to common.py

* Use existing constants for template vacuum config

* Handle invalid templates

* Add tests for unused services

* Add test for invalid templates

* Fix line too long

* Do not start template change tracking in case of MATCH_ALL

* Resolve review comments
2019-06-28 12:19:00 -04:00
Paulus Schoutsen
6cbfc63311 Bumped version to 0.95.2 2019-06-28 08:50:23 -07:00
Paulus Schoutsen
2886b217ab Fix calling empty script turn off (#24827) 2019-06-28 08:50:16 -07:00
Phil Bruckner
fafc68673a Fix another Life360 bug (#24805) 2019-06-28 08:50:16 -07:00
David F. Mulcahey
1990df63aa Bump ZHA quirks module (#24802)
* bump quirks version

* bump version - mija magnet
2019-06-28 08:50:15 -07:00
Paulus Schoutsen
e39f0f3e25 Make sure entity config is never none (#24801) 2019-06-28 08:50:14 -07:00
Pascal Vizeli
1f5e2fa3ce Update azure-pipelines-release.yml for Azure Pipelines (#24800) 2019-06-28 08:50:14 -07:00
cgtobi
204dd77404 Fix netatmo weatherstation setup error (#24788)
* Check if station data exists and reduce calls

* Fix module names list

* Add warning

* Remove dead code
2019-06-28 08:50:13 -07:00
Paulus Schoutsen
4e5b1ccde6 Fix calling empty script turn off (#24827) 2019-06-28 08:49:33 -07:00
Paulus Schoutsen
80844ae2ee Add developer tools panel (#24812) 2019-06-28 08:34:53 -07:00
cgtobi
a69a00785f Fix netatmo weatherstation setup error (#24788)
* Check if station data exists and reduce calls

* Fix module names list

* Add warning

* Remove dead code
2019-06-27 20:16:46 -07:00
Tejpal Sahota
41dd70f644 Changed default encoding to mp3 (#24808) 2019-06-27 20:16:22 -07:00
Paulus Schoutsen
e5b8d5f7ea Updated frontend to 20190627.0 2019-06-27 17:57:02 -07:00
Josh Anderson
c49869160b Use step from tado rather than assuming 0.1 (#24807) 2019-06-27 16:17:15 -07:00
Josh Anderson
69089da88e Use climate device's target temp step value (#24804) 2019-06-27 15:14:23 -07:00
Phil Bruckner
e43a733017 Fix another Life360 bug (#24805) 2019-06-27 15:11:32 -07:00
dreed47
3eb6b9d297 Zestimate fix for issue #23837 (#23838)
* Zestimate fix for issue #23837

removed references to MIN_TIME_BETWEEN_UPDATES
and replaced with SCAN_INTERVAL

* Zestimate fix for issue #23837

removed references to MIN_TIME_BETWEEN_UPDATES
and replaced with SCAN_INTERVAL
2019-06-27 15:09:33 -07:00
David F. Mulcahey
ac5ab52d01 Bump ZHA quirks module (#24802)
* bump quirks version

* bump version - mija magnet
2019-06-27 15:28:56 -04:00
Paulus Schoutsen
0d89b82bff Make sure entity config is never none (#24801) 2019-06-27 15:17:42 -04:00
Pascal Vizeli
0cde24e103 Update azure-pipelines-release.yml for Azure Pipelines (#24800) 2019-06-27 18:26:13 +02:00
Paulus Schoutsen
5598f05dee Merge pull request #24787 from home-assistant/rc
0.95.1
2019-06-27 08:55:10 -07:00
h3ndrik
e932fc832c Add time delta option when searching for deutsche_bahn connections (#24600)
* Add time delta option when searching for connections

Add another option 'in' to search for upcoming connections in the future.
Handy if you need a few minutes to get to the train station and need to add that to the queried departure time.

* correct style errors

* rename new option

* rename new option (2/2)

* add offset correctly
2019-06-27 15:53:05 +02:00
Paulus Schoutsen
01b6830fd2 Bumped version to 0.95.1 2019-06-26 21:25:33 -07:00
Paulus Schoutsen
c1d0ac7b9d Catch uncaught Alexa error (#24785) 2019-06-26 20:24:52 -07:00
William Scanlon
dce667fa07 Pubnub to 1.0.8 (#24781) 2019-06-26 20:24:52 -07:00
Phil Bruckner
a78361341e Fix life360 exception when no location provided (#24777) 2019-06-26 20:24:51 -07:00
Paulus Schoutsen
c87d6e4720 Catch uncaught Alexa error (#24785) 2019-06-26 20:24:20 -07:00
Ville Skyttä
71346760d0 Upgrade pytest to 4.6.3 (#24782)
* Upgrade pytest to 4.6.3

https://docs.pytest.org/en/latest/changelog.html#pytest-4-6-2-2019-06-03
https://docs.pytest.org/en/latest/changelog.html#pytest-4-6-3-2019-06-11

* Make litejet switch test work with pytest 4.6.3

Essentially reverts the corresponing change that was made for pytest
4.2 compatibility.
2019-06-26 20:01:03 -07:00
William Scanlon
f6c1f336d4 Pubnub to 1.0.8 (#24781) 2019-06-26 16:14:00 -07:00
Phil Bruckner
638c958acd Fix life360 exception when no location provided (#24777) 2019-06-26 16:03:11 -07:00
Paulus Schoutsen
b2231945dc Merge branch 'master' into dev 2019-06-26 10:42:25 -07:00
Paulus Schoutsen
4dbfafa8ca Merge pull request #24776 from home-assistant/rc
0.95.0
2019-06-26 10:41:11 -07:00
Andre Richter
56b8da133c Upgrade vallox to async client API (#24774) 2019-06-26 18:40:34 +02:00
Paulus Schoutsen
06af6f19a3 Entity to handle updates via events (#24733)
* Entity to handle updates via events

* Fix a bug

* Update entity.py
2019-06-26 09:22:51 -07:00
Paulus Schoutsen
5f37852695 Bumped version to 0.95.0 2019-06-26 09:17:45 -07:00
Daniel Høyer Iversen
5fe8a43e36 Return correct name for met.no (#24763) 2019-06-26 09:17:35 -07:00
Paulus Schoutsen
760b62e068 Ignore duplicate tradfri discovery (#24759)
* Ignore duplicate tradfri discovery

* Update name
2019-06-26 09:17:35 -07:00
John Dyer
9205334235 Update Waze route dependency to 0.10 (#24754)
* Update manifest.json

Update waze calculator to 0.10, this was supposed to have been done in #22428 but was missed. See discussion [here](https://community.home-assistant.io/t/waze-travel-time-update/50955/201)

* Update requirements_all.txt
2019-06-26 09:17:34 -07:00
cgtobi
ca4c6ffe8d Handle timeouts gracefully (#24752) 2019-06-26 09:17:33 -07:00
cgtobi
b47b555c4f Bump pyatmo to v2.1.0 (#24724) 2019-06-26 09:17:33 -07:00
Paulus Schoutsen
5d2f97de74 Updated frontend to 20190626.0 2019-06-26 09:16:04 -07:00
Paulus Schoutsen
9e0636eefa Updated frontend to 20190626.0 2019-06-26 09:15:54 -07:00
Alexei Chetroi
6ae1228e61 Enhancement/zha model manuf (#24771)
* Cleanup ZHA entities model and manufacturer usage.
Zigpy includes manufacturer and model as attributes of a zigpy
Device class, which simplifies handling of manufacturer and/or model
derived properties for the ZHA platform.

* Sort ZHA imports.
* Lint.
2019-06-26 09:31:19 -04:00
Matte23
29311e6391 Add support for IPP Printers to the CUPS integration (#24756)
* Add support for IPP Printers to the CUPS integration

* Fixed lint error

* Addressed comments, removed redundant check

* Simplified check, improved code readability
2019-06-25 16:13:08 -07:00
John Dyer
bd4f66fda3 Update Waze route dependency to 0.10 (#24754)
* Update manifest.json

Update waze calculator to 0.10, this was supposed to have been done in #22428 but was missed. See discussion [here](https://community.home-assistant.io/t/waze-travel-time-update/50955/201)

* Update requirements_all.txt
2019-06-25 15:25:53 -07:00
Daniel Høyer Iversen
dc89499116 Return correct name for met.no (#24763) 2019-06-25 13:09:04 -07:00
Alain Tavan
41b58b8bc1 fix an error in the description (#24735) 2019-06-25 10:37:25 -07:00
Emilv2
58df05a7e7 Remove obsolete comments in Dockerfile (#24748)
relevant lines were removed in e49b970665
2019-06-25 10:16:05 -07:00
Andre Richter
fb940e4269 Vallox: Fix missing hass member (#24753) 2019-06-25 10:15:41 -07:00
Paulus Schoutsen
26fc57d1b3 Ignore duplicate tradfri discovery (#24759)
* Ignore duplicate tradfri discovery

* Update name
2019-06-25 09:54:40 -07:00
cgtobi
da57f92796 Handle timeouts gracefully (#24752) 2019-06-25 08:57:43 -07:00
Andre Richter
236820d093 Add integration for Vallox Ventilation Units (#24660)
* Add integration for Vallox Ventilation Units.

* Address review comments #1

* Address review comments #2

* Replace IOError with OSError.

* Bump to fixed version of vallox_websocket_api.
2019-06-25 11:38:24 +02:00
Paulus Schoutsen
87712b9fa5 Bumped version to 0.95.0b4 2019-06-24 22:23:41 -07:00
Paulus Schoutsen
510d6d7874 Improve Alexa error handling (#24745) 2019-06-24 22:08:15 -07:00
Martin Hjelmare
8830054fad Fix locative device update (#24744)
* Add a test for two devices

* Fix locative updating all devices

* Add a guard clause that checks if correct device is passed.
2019-06-24 22:08:14 -07:00
Paulus Schoutsen
327fe63047 Clean up Google Config (#24663)
* Clean up Google Config

* Lint

* pylint

* pylint2
2019-06-24 22:08:13 -07:00
Paulus Schoutsen
0f5c9b4af3 Updated frontend to 20190624.1 2019-06-24 22:07:50 -07:00
Paulus Schoutsen
9813396880 Updated frontend to 20190624.1 2019-06-24 22:07:39 -07:00
Paulus Schoutsen
f5f86993f1 Improve Alexa error handling (#24745) 2019-06-24 22:04:31 -07:00
Martin Hjelmare
d4fc22add4 Fix locative device update (#24744)
* Add a test for two devices

* Fix locative updating all devices

* Add a guard clause that checks if correct device is passed.
2019-06-24 20:00:28 -07:00
Paulus Schoutsen
d699a550c8 Bumped version to 0.95.0b3 2019-06-24 15:01:17 -07:00
Anders Melchiorsen
f71d4312e2 Update pysonos to 0.0.17 (#24740) 2019-06-24 15:00:11 -07:00
Paulus Schoutsen
ec777a802c AdGuard to update entry (#24737) 2019-06-24 15:00:10 -07:00
Alexei Chetroi
82cad58b8d Update ZHA dependencies. (#24736) 2019-06-24 15:00:09 -07:00
Evan Bruhn
34231383ec Save cached logi_circle tokens in config folder (#24726)
Instead of the working directory, which it's doing currently. Matches pattern observed on Abode, Ring, Skybell integrations.
2019-06-24 15:00:08 -07:00
Anders Melchiorsen
6e14e8ed91 Update pysonos to 0.0.17 (#24740) 2019-06-24 14:59:15 -07:00
Paulus Schoutsen
4aedd3a09a AdGuard to update entry (#24737) 2019-06-24 14:46:32 -07:00
Alexei Chetroi
26dea0f247 Update ZHA dependencies. (#24736) 2019-06-24 16:57:07 -04:00
Conrad Juhl Andersen
0792e72f71 Add support for sensor state STATE_UNAVAILABLE (#24641)
* Fixed integration with ESPhome, which caused an error if ESPhome did not update fast enough on startup

* Set state to problem if sensor is unavailable

* Fix line length.
2019-06-24 11:30:44 -07:00
David F. Mulcahey
d9420c1f73 Remove device and entity registry entries when removing a ZHA device (#24369)
* cleanup when device is removed

fixes

* cleanup
2019-06-24 11:26:44 -07:00
Evan Bruhn
ee1884423a Save cached logi_circle tokens in config folder (#24726)
Instead of the working directory, which it's doing currently. Matches pattern observed on Abode, Ring, Skybell integrations.
2019-06-24 09:36:39 -07:00
Robin Wohlers-Reichel
17480a0398 Add 'unique_id' Property to Inverter Sensors (#24707)
* Option to change sensor names

* Python 3.5 compatibility

* Oops

* Get serial number at start

* Remove config opportunity

* Oops comma

* Changes from review

* Check yourself before you commit.
2019-06-24 08:34:20 -07:00
Paulus Schoutsen
75ec855822 Bumped version to 0.95.0b2 2019-06-24 08:33:21 -07:00
Phil Bruckner
2c5080e382 Add show_as_state options to Life360 (#24725) 2019-06-24 08:33:14 -07:00
David F. Mulcahey
48e9742658 Update ZHA dependencies (#24718)
* update deps and remove legacy constants bridge

* run deps script and fix test import
2019-06-24 08:33:13 -07:00
Oleg Kurapov
14b62120fd Extend websocket method usage to port 8002 in Samsung TV media player (#24716) 2019-06-24 08:33:12 -07:00
cgtobi
4a8149627e Bump version pyatmo to 2.0.1 (#24703) 2019-06-24 08:33:11 -07:00
David F. Mulcahey
9c85ba5b66 ZHA fix device type mappings (#24699) 2019-06-24 08:33:11 -07:00
Anders Melchiorsen
fb0cb43261 Fix time expression parsing (#24696) 2019-06-24 08:33:10 -07:00
Thomas Lovén
23722dc291 Allow extra js modules to be included in frontend (#24675)
* Add extra_module_url and extra_module_url_es5 to frontend options

* Address review comments
2019-06-24 08:33:09 -07:00
Paulus Schoutsen
e841f568c1 Update translations 2019-06-24 08:27:46 -07:00
Paulus Schoutsen
9b096322e1 Updated frontend to 20190624.0 2019-06-24 08:27:04 -07:00
Paulus Schoutsen
df32a81165 Updated frontend to 20190624.0 2019-06-24 08:26:50 -07:00
Phil Bruckner
8924d657a4 Add show_as_state options to Life360 (#24725) 2019-06-24 08:05:34 -07:00
endor
98ba529ead Add Trafikverket train component (#23470)
* Added Trafikverket train component

* Updated manifest with proper name and codeowner

* Updated requirements and manifest

* Updated CODEOWNERS

* Corrected requirements

* Added trafikverket_train/sensor.py to .coveragerc

* Added error handling and log if API call fails

* Corrected styles, removed dev log, improved validation

* Method calls to async_update(), improved error handling

* Minor cleanup/reorg for effeciency

* Added station cache and corrected to fit standards

* Simplified trainstop id  and cleaned up dict.get

* Corrected mistake after change from dict to array

* Change device class to timestamp
2019-06-24 10:38:50 +02:00
cgtobi
9a01cd84c2 Bump pyatmo to v2.1.0 (#24724) 2019-06-24 07:43:49 +02:00
John Luetke
09c6f57364 Expose ports 8123, 8300 and 51827 in Dockerfile (#24389) 2019-06-23 21:44:26 +02:00
Pascal Vizeli
a807572382 Add initial support for remote dev container (#24681)
* Add initial support for remote container

* Use constrain
2019-06-23 12:18:33 -07:00
Oleg Kurapov
dc6a44d0eb Extend websocket method usage to port 8002 in Samsung TV media player (#24716) 2019-06-23 12:11:25 -07:00
Paulus Schoutsen
c296e9b9bb Update owner stream integration 2019-06-23 12:00:06 -07:00
David F. Mulcahey
d22bb8fc7d Update ZHA dependencies (#24718)
* update deps and remove legacy constants bridge

* run deps script and fix test import
2019-06-23 13:43:19 -04:00
ktnrg45
b99275f6a5 Fix PS4 entities with shared host not updating and latency with multiple connections (#24642)
* correct assume info call

* 0.8.4

* 0.8.4

* 0.8.4

* 0.8.5

* 0.8.5

* 0.8.5

* revert condition
2019-06-23 09:52:53 -06:00
Robin Wohlers-Reichel
57502bc911 Solax update 0.1.0 (#24708)
* Update to solax 0.0.6

* Library version 0.1.0
2019-06-23 11:16:39 +02:00
cgtobi
128e66fa24 Bump version pyatmo to 2.0.1 (#24703) 2019-06-23 07:50:04 +02:00
Fabian Affolter
0132ac3c27 Upgrade Sphinx to 2.1.2 (#24693) 2019-06-23 07:49:40 +02:00
David F. Mulcahey
cfd8d70890 ZHA fix device type mappings (#24699) 2019-06-22 15:05:35 -04:00
Fabian Affolter
44d2871dc9 Upgrade youtube_dl to 2019.06.08 (#24692) 2019-06-22 14:45:39 +02:00
Fabian Affolter
821e3beab0 Upgrade discord.py to 1.2.2 (#24695) 2019-06-22 14:44:24 +02:00
Anders Melchiorsen
a439e087e1 Fix time expression parsing (#24696) 2019-06-22 13:39:33 +02:00
Andre Lengwenus
b8acbf3c3a Corrected number of default LCN segment coupler scan tryouts (#24678)
* Bump to pypck==0.6.2

* Set default segment coupler scan tryouts to 0
2019-06-22 13:27:41 +02:00
Jonathan
d25214beb1 Add aml_thermal label (#24665)
Added label for the CPU Temperature for AmLogic ARM chips.
2019-06-22 12:58:37 +02:00
Penny Wood
22d9bee41a Template: Expand method to expand groups, and closest as filter (#23691)
* Implement expand method

* Allow expand and closest to be used as filters

* Correct patch

* Addresses review comments
2019-06-22 00:32:32 -07:00
Ville Skyttä
a6eef22fbc Upgrade mypy to 0.710 (#24666)
* Upgrade mypy to 0.710

* Address mypy 0.710 errors
2019-06-22 10:19:36 +03:00
Steven Looman
f189367c02 Upgrade to async_upnp_client==0.14.10 and increase search timeout (#24685) 2019-06-22 09:12:27 +02:00
Aaron Bach
40fa4463de Change Ambient solar radiation units to lx (#24690) 2019-06-21 23:12:16 -06:00
Aaron Bach
729df112a7 Add RainMachine device classes where appropriate (#24682) 2019-06-21 17:12:28 -06:00
Thomas Lovén
9b52b9bf66 Allow extra js modules to be included in frontend (#24675)
* Add extra_module_url and extra_module_url_es5 to frontend options

* Address review comments
2019-06-21 13:16:28 -07:00
zewelor
c6d5a5a6cc Improve autodiscovered yeelights model detection (#24671)
* Improve autodiscovered yeelights model detection

* Lint fixes

* Logger warn fix
2019-06-21 15:50:25 -04:00
Pascal Vizeli
7f169e97ca Update azure-pipelines-release.yml for Azure Pipelines 2019-06-21 20:08:19 +02:00
Pascal Vizeli
560161bdbb Update azure-pipelines-release.yml for Azure Pipelines 2019-06-21 20:08:06 +02:00
Paulus Schoutsen
1761a71338 Bumped version to 0.95.0b1 2019-06-21 09:27:58 -07:00
Aaron Bach
3da3612c7b Add device class support for Ambient PWS sensors (#24677) 2019-06-21 10:27:53 -06:00
Pascal Vizeli
198432f222 Prefere binary with wheels (#24669) 2019-06-21 09:27:41 -07:00
Andrew Sayre
a868685ac9 Bump pysmartthings (#24659) 2019-06-21 09:27:41 -07:00
Paulus Schoutsen
d4cab60343 Warn when user tries run custom config flow (#24657) 2019-06-21 09:27:40 -07:00
Kevin Fronczak
da12ceae5b Upgrade blinkpy==0.14.1 for startup bugfix (#24656) 2019-06-21 09:27:39 -07:00
Anders Melchiorsen
79b10612aa Update LIFX brightness during long transitions (#24653) 2019-06-21 09:27:39 -07:00
Alexei Chetroi
d5edbb424a Bump ZHA dependencies. (#24637) 2019-06-21 09:27:38 -07:00
Martin Hjelmare
d527e2c926 Fix device tracker see for entity registry entities (#24633)
* Add a test for see service gaurd

* Guard from seeing devices part of entity registry

* Await registry task early

* Lint

* Correct comment

* Clean up wait for registry

* Fix spelling

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Fix spelling

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>
2019-06-21 09:27:37 -07:00
Rodrigo Pérez
b899dd59c5 Vlc telnet (#24290)
* Vlc telnet first commit

First functional version, remains to add more functionality.

* New functions added and bugfixes

* Compliance with dev checklist

* Compliance with dev checklist

* Compliance with pydocstyle

* Removed unused import

* Fixed wrong reference for exception

* Module renamed

* Fixed module rename in other

* Fixed wrong reference for exception


Module renamed


Fixed module rename in other

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Suggestions by @OttoWinter

+Manage error when the VLC dissapears to show status unavailable.

* Removed error log, instead set unavailable state

* Changes suggested by @pvizeli

-Import location
-Use of constants

* Implemented available method

* Improved available method
2019-06-21 09:27:36 -07:00
Paulus Schoutsen
8f928982e0 Updated frontend to 20190620.0 2019-06-21 09:27:23 -07:00
Paulus Schoutsen
8f243ad59d Updated frontend to 20190620.0 2019-06-21 09:27:02 -07:00
Pascal Vizeli
c9453bab19 Prefere binary with wheels (#24669) 2019-06-21 08:47:56 -07:00
Paulus Schoutsen
78b7ed0ebe Clean up Google Config (#24663)
* Clean up Google Config

* Lint

* pylint

* pylint2
2019-06-21 11:17:21 +02:00
Rodrigo Pérez
d468d0f71b Vlc telnet (#24290)
* Vlc telnet first commit

First functional version, remains to add more functionality.

* New functions added and bugfixes

* Compliance with dev checklist

* Compliance with dev checklist

* Compliance with pydocstyle

* Removed unused import

* Fixed wrong reference for exception

* Module renamed

* Fixed module rename in other

* Fixed wrong reference for exception


Module renamed


Fixed module rename in other

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update homeassistant/components/vlc_telnet/media_player.py

Accepted suggestion by @OttoWinter

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Suggestions by @OttoWinter

+Manage error when the VLC dissapears to show status unavailable.

* Removed error log, instead set unavailable state

* Changes suggested by @pvizeli

-Import location
-Use of constants

* Implemented available method

* Improved available method
2019-06-21 11:13:47 +02:00
Pascal Vizeli
6bc636c2f2 Update azure-pipelines-wheels.yml for Azure Pipelines 2019-06-21 11:07:42 +02:00
mvn23
43a6be6471 Multiple devices support for opentherm_gw (#22932)
* Breaking change: Rewrite opentherm_gw to add support for more than one OpenTherm Gateway.
Breaks config layout and child entity ids and adds a required parameter to all service calls (gateway_id).

* Add schema and parameter description for service opentherm_gw.reset_gateway.

* Add optional name attribute in config to be used for friendly names.
Fix bugs in binary_sensor and climate platforms.

* pylint fixes

* Remove unused variables.

* Update manifest.json, remove REQUIREMENTS from .py file

* Update CODEOWNERS

* Address issues that were brought up (requested changes):
- Move imports to module level
- Change certain functions from async to sync
- Move constants to const.py (new file)
- Call gateway setup from outside of __init__()
- Move validation of monitored_variables to config schema

* Address requested changes:
- Make module imports relative
- Move more functions from async to sync, decorate with @callback where necessary
- Remove monitored_variables option, add all sensors by default
2019-06-21 10:52:25 +02:00
Pascal Vizeli
d9f2a406f6 Update azure-pipelines-wheels.yml for Azure Pipelines 2019-06-21 10:46:35 +02:00
sfjes
0bdbf007b2 Fix downloader_download_failed event not firing for HTTP response errors (#24640) 2019-06-20 13:59:17 -07:00
Ville Skyttä
f1cbb2a0b3 braviatv, nmap_tracker: use getmac for getting MAC addresses (#24628)
* braviatv, nmap_tracker: use getmac for getting MAC addresses

Refs https://github.com/home-assistant/home-assistant/pull/24601

* Move getmac imports to top level
2019-06-20 23:35:02 +03:00
foreign-sub
ecfbfb4527 Fix AttributeError: 'NoneType' object has no attribute 'group' with sytadin component (#24652)
* Fix AttributeError: 'NoneType' object has no attribute 'group'

* Update sensor.py
2019-06-20 13:28:39 -07:00
Andrew Sayre
d8690f426c Bump pysmartthings (#24659) 2019-06-20 13:25:32 -07:00
Anders Melchiorsen
39f2e49451 Update LIFX brightness during long transitions (#24653) 2019-06-20 13:24:45 -07:00
Kevin Fronczak
58f14c5fe2 Upgrade blinkpy==0.14.1 for startup bugfix (#24656) 2019-06-20 13:24:02 -07:00
Paulus Schoutsen
319ac23736 Warn when user tries run custom config flow (#24657) 2019-06-20 13:22:12 -07:00
Alexei Chetroi
86e50530b0 Bump ZHA dependencies. (#24637) 2019-06-19 22:32:31 -04:00
Martin Hjelmare
7881081207 Fix device tracker see for entity registry entities (#24633)
* Add a test for see service gaurd

* Guard from seeing devices part of entity registry

* Await registry task early

* Lint

* Correct comment

* Clean up wait for registry

* Fix spelling

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Fix spelling

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>
2019-06-20 03:22:33 +02:00
526 changed files with 18774 additions and 9415 deletions

View File

@@ -38,6 +38,8 @@ omit =
homeassistant/components/apple_tv/*
homeassistant/components/aqualogic/*
homeassistant/components/aquostv/media_player.py
homeassistant/components/arcam_fmj/media_player.py
homeassistant/components/arcam_fmj/__init__.py
homeassistant/components/arduino/*
homeassistant/components/arest/binary_sensor.py
homeassistant/components/arest/sensor.py
@@ -49,6 +51,7 @@ omit =
homeassistant/components/asterisk_mbox/*
homeassistant/components/asuswrt/device_tracker.py
homeassistant/components/august/*
homeassistant/components/aurora_abb_powerone/sensor.py
homeassistant/components/automatic/device_tracker.py
homeassistant/components/avion/light.py
homeassistant/components/azure_event_hub/*
@@ -214,6 +217,7 @@ omit =
homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/fritzbox_netmonitor/sensor.py
homeassistant/components/fritzdect/switch.py
homeassistant/components/fronius/sensor.py
homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
@@ -405,6 +409,8 @@ omit =
homeassistant/components/nissan_leaf/*
homeassistant/components/nmap_tracker/device_tracker.py
homeassistant/components/nmbs/sensor.py
homeassistant/components/notion/binary_sensor.py
homeassistant/components/notion/sensor.py
homeassistant/components/noaa_tides/sensor.py
homeassistant/components/norway_air/air_quality.py
homeassistant/components/nsw_fuel_station/sensor.py
@@ -637,6 +643,7 @@ omit =
homeassistant/components/trackr/device_tracker.py
homeassistant/components/tradfri/*
homeassistant/components/tradfri/light.py
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/*
homeassistant/components/travisci/sensor.py
@@ -655,6 +662,7 @@ omit =
homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uscis/sensor.py
homeassistant/components/usps/*
homeassistant/components/vallox/*
homeassistant/components/vasttrafik/sensor.py
homeassistant/components/velbus/*
homeassistant/components/velux/*
@@ -665,6 +673,7 @@ omit =
homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vizio/media_player.py
homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/media_player.py
homeassistant/components/volkszaehler/sensor.py
homeassistant/components/volumio/media_player.py
homeassistant/components/volvooncall/*
@@ -683,6 +692,8 @@ omit =
homeassistant/components/worldtidesinfo/sensor.py
homeassistant/components/worxlandroid/sensor.py
homeassistant/components/wunderlist/*
homeassistant/components/wwlln/__init__.py
homeassistant/components/wwlln/geo_location.py
homeassistant/components/x10/light.py
homeassistant/components/xbox_live/sensor.py
homeassistant/components/xeoma/camera.py

30
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
FROM python:3.7
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libudev-dev \
libavformat-dev \
libavcodec-dev \
libavdevice-dev \
libavutil-dev \
libswscale-dev \
libswresample-dev \
libavfilter-dev \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
&& cd hass-release \
&& pip3 install -e .
WORKDIR /workspace
# Install Python dependencies from requirements.txt if it exists
COPY requirements_test_all.txt homeassistant/package_constraints.txt /workspace/
RUN pip3 install -r requirements_test_all.txt -c package_constraints.txt
# Set the default shell to bash instead of sh
ENV SHELL /bin/bash

View File

@@ -0,0 +1,24 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"name": "Home Assistant Dev",
"context": "..",
"dockerFile": "Dockerfile",
"postCreateCommand": "pip3 install -e .",
"appPort": 8123,
"runArgs": [
"-e", "GIT_EDTIOR='code --wait'"
],
"extensions": [
"ms-python.python",
"ms-azure-devops.azure-pipelines",
"redhat.vscode-yaml"
],
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"files.trimTrailingWhitespace": true,
"editor.rulers": [80],
"terminal.integrated.shell.linux": "/bin/bash"
}
}

View File

@@ -3,7 +3,7 @@
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- Do not report issues for integrations if you are using custom integration: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
-->

View File

@@ -9,7 +9,7 @@ about: Create a report to help us improve
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- Do not report issues for integrations if you are using a custom integration: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
-->

27
.github/lock.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 1
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: 2019-07-01
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
only: pulls
# Optionally, specify configuration settings just for `issues` or `pulls`
issues:
daysUntilLock: 30

54
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- under investigation
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
There hasn't been any activity on this issue recently. Due to the high number
of incoming GitHub notifications, we have to clean some of the old issues,
as many of them have already been resolved with the latest updates.
Please make sure to update to the latest Home Assistant version and check
if that solves the issue. Let us know if that works for you by adding a
comment 👍
This issue now has been marked as stale and will be closed if no further
activity occurs. Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

10
.gitignore vendored
View File

@@ -4,6 +4,10 @@ config2/*
tests/testing_config/deps
tests/testing_config/home-assistant.log
# hass-release
data/
.token
# Hide sublime text stuff
*.sublime-project
*.sublime-workspace
@@ -94,8 +98,10 @@ virtualization/vagrant/.vagrant
virtualization/vagrant/config
# Visual Studio Code
.vscode
.devcontainer
.vscode/*
!.vscode/cSpell.json
!.vscode/extensions.json
!.vscode/tasks.json
# Built docs
docs/build

92
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,92 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Preview",
"type": "shell",
"command": "hass -c ./config",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pytest",
"type": "shell",
"command": "pytest --timeout=10 tests",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Flake8",
"type": "shell",
"command": "flake8 homeassistant tests",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pylint",
"type": "shell",
"command": "pylint homeassistant",
"dependsOn": [
"Install all Requirements"
],
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Generate Requirements",
"type": "shell",
"command": "./script/gen_requirements_all.py",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Install all Requirements",
"type": "shell",
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}

View File

@@ -26,9 +26,11 @@ homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya
homeassistant/components/api/* @home-assistant/core
homeassistant/components/aprs/* @PhilRW
homeassistant/components/arcam_fmj/* @elupus
homeassistant/components/arduino/* @fabaff
homeassistant/components/arest/* @fabaff
homeassistant/components/asuswrt/* @kennedyshead
homeassistant/components/aurora_abb_powerone/* @davet2001
homeassistant/components/auth/* @home-assistant/core
homeassistant/components/automatic/* @armills
homeassistant/components/automation/* @home-assistant/core
@@ -91,6 +93,7 @@ homeassistant/components/flock/* @fabaff
homeassistant/components/flunearyou/* @bachya
homeassistant/components/foursquare/* @robbiet480
homeassistant/components/freebox/* @snoof85
homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb
@@ -113,7 +116,6 @@ homeassistant/components/history/* @home-assistant/core
homeassistant/components/history_graph/* @andrey-git
homeassistant/components/hive/* @Rendili @KJonline
homeassistant/components/homeassistant/* @home-assistant/core
homeassistant/components/homekit/* @cdce8p
homeassistant/components/homekit_controller/* @Jc2k
homeassistant/components/homematic/* @pvizeli @danielperna84
homeassistant/components/honeywell/* @zxdavb
@@ -180,10 +182,12 @@ homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff
homeassistant/components/notify/* @home-assistant/core
homeassistant/components/notion/* @bachya
homeassistant/components/nsw_fuel_station/* @nickw444
homeassistant/components/nuki/* @pschmitt
homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/opentherm_gw/* @mvn23
homeassistant/components/openuv/* @bachya
homeassistant/components/openweathermap/* @fabaff
homeassistant/components/orangepi_gpio/* @pascallj
@@ -237,6 +241,7 @@ homeassistant/components/spider/* @peternijssen
homeassistant/components/sql/* @dgomes
homeassistant/components/statistics/* @fabaff
homeassistant/components/stiebel_eltron/* @fucm
homeassistant/components/stream/* @hunterjm
homeassistant/components/sun/* @Swamp-Ig
homeassistant/components/supla/* @mwegrzynek
homeassistant/components/swiss_hydrological_data/* @fabaff
@@ -263,6 +268,7 @@ homeassistant/components/toon/* @frenck
homeassistant/components/tplink/* @rytilahti
homeassistant/components/traccar/* @ludeeus
homeassistant/components/tradfri/* @ggravlingen
homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/tts/* @robbiet480
homeassistant/components/twilio_call/* @robbiet480
homeassistant/components/twilio_sms/* @robbiet480
@@ -275,6 +281,7 @@ homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velux/* @Julius2342
homeassistant/components/version/* @fabaff
homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf
homeassistant/components/waqi/* @andrey-git
homeassistant/components/watson_tts/* @rutkai
homeassistant/components/weather/* @fabaff
@@ -282,6 +289,7 @@ homeassistant/components/weblink/* @home-assistant/core
homeassistant/components/websocket_api/* @home-assistant/core
homeassistant/components/wemo/* @sqldiablo
homeassistant/components/worldclock/* @fabaff
homeassistant/components/wwlln/* @bachya
homeassistant/components/xfinity/* @cisasteelersfan
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
homeassistant/components/xiaomi_miio/* @rytilahti @syssi
@@ -300,5 +308,4 @@ homeassistant/components/zoneminder/* @rohankapoorcom
homeassistant/components/zwave/* @home-assistant/z-wave
# Individual files
homeassistant/components/group/cover @cdce8p
homeassistant/components/demo/weather @fabaff

View File

@@ -2,7 +2,7 @@
# When updating this file, please also update virtualization/Docker/Dockerfile.dev
# This way, the development image and the production image are kept in sync.
FROM python:3.7
FROM python:3.7-stretch
LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
# Uncomment any of the following lines to disable the installation.
@@ -24,12 +24,14 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some dependencies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.12.2 cchardet cython tensorflow
# Copy source
COPY . .
EXPOSE 8123
EXPOSE 8300
EXPOSE 51827
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@@ -4,8 +4,13 @@ trigger:
batch: true
branches:
include:
- rc
- dev
pr: none
- master
pr:
- rc
- dev
- master
resources:
containers:
@@ -15,136 +20,181 @@ resources:
image: homeassistant/ci-azure:3.6
- container: 37
image: homeassistant/ci-azure:3.7
variables:
- name: ArtifactFeed
value: '2df3ae11-3bf6-49bc-a809-ba0d340d6a6d'
- name: PythonMain
value: '35'
- group: codecov
stages:
jobs:
- stage: 'Overview'
jobs:
- job: 'Lint'
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python -m venv venv
- job: 'Lint'
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python -m venv lint
. lint/bin/activate
pip install flake8
flake8 homeassistant tests script
displayName: 'Run flake8'
. venv/bin/activate
pip install flake8
displayName: 'Setup Env'
- script: |
. venv/bin/activate
flake8 homeassistant tests script
displayName: 'Run flake8'
- job: 'Validate'
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python -m venv venv
. venv/bin/activate
pip install -e .
displayName: 'Setup Env'
- script: |
. venv/bin/activate
python -m script.hassfest validate
displayName: 'Validate manifests'
- script: |
. venv/bin/activate
./script/gen_requirements_all.py validate
displayName: 'requirements_all validate'
- job: 'Check'
- stage: 'Tests'
dependsOn:
- Lint
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 1
matrix:
Python35:
python.version: '3.5'
python.container: '35'
Python36:
python.version: '3.6'
python.container: '36'
Python37:
python.version: '3.7'
python.container: '37'
container: $[ variables['python.container'] ]
steps:
- script: |
echo "$(python.version)" > .cache
displayName: 'Set python $(python.version) for requirement cache'
- 'Overview'
jobs:
- job: 'PyTest'
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 3
matrix:
Python35:
python.container: '35'
Python36:
python.container: '36'
Python37:
python.container: '37'
container: $[ variables['python.container'] ]
steps:
- script: |
python --version > .cache
displayName: 'Set python $(python.container) for requirement cache'
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
keyfile: 'requirements_test_all.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
pip install pytest-azurepipelines -c homeassistant/package_constraints.txt
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
# Explicit Cache Save (instead of using RestoreAndSaveCache)
# Dont wait with cache save for all the other task in this job to complete (±30 minutes), other parallel jobs might utilize this
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_test_all.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(python.container)'
- script: |
. venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml -qq -o console_output_style=count -p no:sugar tests
displayName: 'Run pytest for python $(python.container)'
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
- script: |
. venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml --cov --cov-report=xml -qq -o console_output_style=count -p no:sugar tests
codecov
displayName: 'Run pytest for python $(python.container) / coverage'
env:
CODECOV_TOKEN: '$(codecovToken)'
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: 'test-results.xml'
testRunTitle: 'Publish test results for Python $(python.container)'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: cobertura
summaryFileLocation: coverage.xml
displayName: 'publish coverage artifact'
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
keyfile: 'requirements_test_all.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_test_all.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(python.version)'
- script: |
. venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=junit/test-results.xml -qq -o console_output_style=count -p no:sugar tests
displayName: 'Run pytest for python $(python.version)'
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: '**/test-*.xml'
testRunTitle: 'Publish test results for Python $(python.version)'
- job: 'FullCheck'
- stage: 'FullCheck'
dependsOn:
- Check
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
echo "$(PythonMain)" > .cache
displayName: 'Set python $(python.version) for requirement cache'
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(python.version)'
- script: |
. venv/bin/activate
pylint homeassistant
displayName: 'Run pylint'
- 'Overview'
jobs:
- job: 'Pylint'
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python --version > .cache
displayName: 'Set python $(PythonMain) for requirement cache'
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(PythonMain)'
- script: |
. venv/bin/activate
pylint homeassistant
displayName: 'Run pylint'
- job: 'Mypy'
pool:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python -m venv venv
. venv/bin/activate
pip install -r requirements_test.txt
displayName: 'Setup Env'
- script: |
. venv/bin/activate
TYPING_FILES=$(cat mypyrc)
mypy $TYPING_FILES
displayName: 'Run mypy'

View File

@@ -8,161 +8,152 @@ trigger:
pr: none
variables:
- name: versionBuilder
value: '3.2'
value: '5.2'
- group: docker
- group: github
- group: twine
jobs:
stages:
- stage: 'Validate'
jobs:
- job: 'VersionValidate'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: |
setup_version="$(python setup.py -V)"
branch_version="$(Build.SourceBranchName)"
- job: 'VersionValidate'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: |
setup_version="$(python setup.py -V)"
branch_version="$(Build.SourceBranchName)"
if [ "${setup_version}" != "${branch_version}" ]; then
echo "Version of tag ${branch_version} don't match with ${setup_version}!"
exit 1
fi
displayName: 'Check version of branch/tag'
- script: |
sudo apt-get install -y --no-install-recommends \
jq curl
if [ "${setup_version}" != "${branch_version}" ]; then
echo "Version of tag ${branch_version} don't match with ${setup_version}!"
release="$(Build.SourceBranchName)"
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480)$ ]]; then
exit 0
fi
echo "${created_by} is not allowed to create an release!"
exit 1
fi
displayName: 'Check version of branch/tag'
- script: |
sudo apt-get install -y --no-install-recommends \
jq curl
displayName: 'Check rights'
release="$(Build.SourceBranchName)"
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
- stage: 'Build'
jobs:
- job: 'ReleasePython'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: pip install twine wheel
displayName: 'Install tools'
- script: python setup.py sdist bdist_wheel
displayName: 'Build package'
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
twine upload dist/* --skip-existing
displayName: 'Upload pypi'
- job: 'ReleaseDocker'
timeoutInMinutes: 240
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 5
matrix:
amd64:
buildArch: 'amd64'
buildMachine: 'qemux86-64,intel-nuc'
i386:
buildArch: 'i386'
buildMachine: 'qemux86'
armhf:
buildArch: 'armhf'
buildMachine: 'qemuarm,raspberrypi'
armv7:
buildArch: 'armv7'
buildMachine: 'raspberrypi2,raspberrypi3,raspberrypi4,odroid-xu,tinker'
aarch64:
buildArch: 'aarch64'
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime'
steps:
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
displayName: 'Docker hub login'
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
displayName: 'Install Builder'
- script: |
set -e
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480)$ ]]; then
exit 0
fi
sudo docker run --rm --privileged \
-v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant $(Build.SourceBranchName) "--$(buildArch)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t generic --docker-hub homeassistant
echo "${created_by} is not allowed to create an release!"
exit 1
displayName: 'Check rights'
sudo docker run --rm --privileged \
-v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant-machine "$(Build.SourceBranchName)=$(buildMachine)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t machine --docker-hub homeassistant
displayName: 'Build Release'
- stage: 'Publish'
jobs:
- job: 'ReleaseHassio'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
sudo apt-get install -y --no-install-recommends \
git jq curl
- job: 'ReleasePython'
condition: and(startsWith(variables['Build.SourceBranch'], 'refs/tags'), succeeded('VersionValidate'))
dependsOn:
- 'VersionValidate'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: pip install twine wheel
displayName: 'Install tools'
- script: python setup.py sdist bdist_wheel
displayName: 'Build package'
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
twine upload dist/* --skip-existing
displayName: 'Upload pypi'
git config --global user.name "Pascal Vizeli"
git config --global user.email "pvizeli@syshack.ch"
git config --global credential.helper store
echo "https://$(githubToken):x-oauth-basic@github.com" > $HOME/.git-credentials
displayName: 'Install requirements'
- script: |
set -e
- job: 'ReleaseDocker'
condition: and(startsWith(variables['Build.SourceBranch'], 'refs/tags'), succeeded('VersionValidate'))
dependsOn:
- 'VersionValidate'
timeoutInMinutes: 240
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 5
matrix:
amd64:
buildArch: 'amd64'
buildMachine: 'qemux86-64,intel-nuc'
i386:
buildArch: 'i386'
buildMachine: 'qemux86'
armhf:
buildArch: 'armhf'
buildMachine: 'qemuarm,raspberrypi'
armv7:
buildArch: 'armv7'
buildMachine: 'raspberrypi2,raspberrypi3,odroid-xu,tinker'
aarch64:
buildArch: 'aarch64'
buildMachine: 'qemuarm-64,raspberrypi3-64,odroid-c2,orangepi-prime'
steps:
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
displayName: 'Docker hub login'
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
displayName: 'Install Builder'
- script: |
set -e
version="$(Build.SourceBranchName)"
sudo docker run --rm --privileged \
-v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant $(Build.SourceBranchName) "--$(buildArch)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t generic --docker-hub homeassistant
git clone https://github.com/home-assistant/hassio-version
cd hassio-version
sudo docker run --rm --privileged \
-v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant-machine "$(Build.SourceBranchName)=$(buildMachine)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t machine --docker-hub homeassistant
displayName: 'Build Release'
dev_version="$(jq --raw-output '.homeassistant.default' dev.json)"
beta_version="$(jq --raw-output '.homeassistant.default' beta.json)"
stable_version="$(jq --raw-output '.homeassistant.default' stable.json)"
if [[ "$version" =~ b ]]; then
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json
else
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json
sed -i "s|$stable_version|$version|g" stable.json
fi
- job: 'ReleaseHassio'
condition: and(startsWith(variables['Build.SourceBranch'], 'refs/tags'), succeeded('ReleaseDocker'))
dependsOn:
- 'ReleaseDocker'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
sudo apt-get install -y --no-install-recommends \
git jq curl
git config --global user.name "Pascal Vizeli"
git config --global user.email "pvizeli@syshack.ch"
git config --global credential.helper store
echo "https://$(githubToken):x-oauth-basic@github.com" > $HOME/.git-credentials
displayName: 'Install requirements'
- script: |
set -e
version="$(Build.SourceBranchName)"
git clone https://github.com/home-assistant/hassio-version
cd hassio-version
dev_version="$(jq --raw-output '.homeassistant.default' dev.json)"
beta_version="$(jq --raw-output '.homeassistant.default' beta.json)"
stable_version="$(jq --raw-output '.homeassistant.default' stable.json)"
if [[ "$version" =~ b ]]; then
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json
else
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json
sed -i "s|$stable_version|$version|g" stable.json
fi
git commit -am "Bump Home Assistant $version"
git push
displayName: 'Update version files'
git commit -am "Bump Home Assistant $version"
git push
displayName: 'Update version files'

View File

@@ -11,19 +11,18 @@ trigger:
pr: none
variables:
- name: versionWheels
value: '0.7'
value: '1.0-3.7-alpine3.10'
- group: wheels
jobs:
- job: 'Wheels'
condition: or(eq(variables['Build.SourceBranchName'], 'dev'), eq(variables['Build.SourceBranchName'], 'master'))
timeoutInMinutes: 360
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 3
maxParallel: 5
matrix:
amd64:
buildArch: 'amd64'

View File

@@ -547,7 +547,7 @@ class AuthStore:
def _set_defaults(self) -> None:
"""Set default values for auth store."""
self._users = OrderedDict() # type: Dict[str, models.User]
self._users = OrderedDict()
groups = OrderedDict() # type: Dict[str, models.Group]
admin_group = _system_admin_group()

View File

@@ -36,7 +36,7 @@ def is_on(hass, entity_id=None):
continue
if not hasattr(component, 'is_on'):
_LOGGER.warning("Component %s has no is_on method.", domain)
_LOGGER.warning("Integration %s has no is_on method.", domain)
continue
if component.is_on(ent_id):

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "S'ha actualitzat la configuraci\u00f3 existent.",
"single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 d'AdGuard Home."
},
"error": {

View File

@@ -23,6 +23,7 @@
"description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern.",
"title": "Verkn\u00fcpfe AdGuard Home."
}
}
},
"title": "AdGuard Home"
}
}

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Updated existing configuration.",
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "\ud558\ub098\uc758 AdGuard Home \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"error": {
@@ -16,7 +17,7 @@
"host": "\ud638\uc2a4\ud2b8",
"password": "\ube44\ubc00\ubc88\ud638",
"port": "\ud3ec\ud2b8",
"ssl": "AdGuard Home \uc740 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4",
"ssl": "AdGuard Home \uc740 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4",
"username": "\uc0ac\uc6a9\uc790 \uc774\ub984",
"verify_ssl": "AdGuard Home \uc740 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4"
},

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "D\u00e9i bestehend Konfiguratioun ass ge\u00e4nnert.",
"single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun AdGuard Home ass erlaabt."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Bestaande configuratie bijgewerkt.",
"single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van AdGuard Home is toegestaan."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Oppdatert eksisterende konfigurasjon.",
"single_instance_allowed": "Kun \u00e9n enkelt konfigurasjon av AdGuard Hjemer tillatt."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Zaktualizowano istniej\u0105c\u0105 konfiguracj\u0119.",
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Configura\u00e7\u00e3o existente atualizada.",
"single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do AdGuard Home \u00e9 permitida."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430.",
"single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Posodobljena obstoje\u010da konfiguracija.",
"single_instance_allowed": "Dovoljena je samo ena konfiguracija AdGuard Home."
},
"error": {

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "\u5df2\u66f4\u65b0\u73fe\u6709\u8a2d\u5b9a\u3002",
"single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 AdGuard Home\u3002"
},
"error": {

View File

@@ -104,12 +104,33 @@ class AdGuardHomeFlowHandler(ConfigFlow):
This flow is triggered by the discovery component.
"""
if self._async_current_entries():
entries = self._async_current_entries()
if not entries:
self._hassio_discovery = user_input
return await self.async_step_hassio_confirm()
cur_entry = entries[0]
if (cur_entry.data[CONF_HOST] == user_input[CONF_HOST] and
cur_entry.data[CONF_PORT] == user_input[CONF_PORT]):
return self.async_abort(reason='single_instance_allowed')
self._hassio_discovery = user_input
is_loaded = cur_entry.state == config_entries.ENTRY_STATE_LOADED
return await self.async_step_hassio_confirm()
if is_loaded:
await self.hass.config_entries.async_unload(cur_entry.entry_id)
self.hass.config_entries.async_update_entry(cur_entry, data={
**cur_entry.data,
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
})
if is_loaded:
await self.hass.config_entries.async_setup(cur_entry.entry_id)
return self.async_abort(reason='existing_instance_updated')
async def async_step_hassio_confirm(self, user_input=None):
"""Confirm Hass.io discovery."""

View File

@@ -23,7 +23,8 @@
"connection_error": "Failed to connect."
},
"abort": {
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed."
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed.",
"existing_instance_updated": "Updated existing configuration."
}
}
}
}

View File

@@ -4,5 +4,8 @@
"documentation": "https://www.home-assistant.io/components/alert",
"requirements": [],
"dependencies": [],
"after_dependencies": [
"notify"
],
"codeowners": []
}

View File

@@ -23,6 +23,7 @@ import homeassistant.util.color as color_util
from .const import (
API_TEMP_UNITS,
API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
DATE_FORMAT,
PERCENTAGE_FAN_MAP,
)
@@ -180,9 +181,13 @@ class AlexaPowerController(AlexaCapibility):
if name != 'powerState':
raise UnsupportedProperty(name)
if self.entity.state == STATE_OFF:
return 'OFF'
return 'ON'
if self.entity.domain == climate.DOMAIN:
is_on = self.entity.state != climate.HVAC_MODE_OFF
else:
is_on = self.entity.state != STATE_OFF
return 'ON' if is_on else 'OFF'
class AlexaLockController(AlexaCapibility):
@@ -546,16 +551,13 @@ class AlexaThermostatController(AlexaCapibility):
def properties_supported(self):
"""Return what properties this entity supports."""
properties = []
properties = [{'name': 'thermostatMode'}]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_TARGET_TEMPERATURE:
properties.append({'name': 'targetSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW:
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
properties.append({'name': 'lowerSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH:
properties.append({'name': 'upperSetpoint'})
if supported & climate.SUPPORT_OPERATION_MODE:
properties.append({'name': 'thermostatMode'})
return properties
def properties_proactively_reported(self):
@@ -569,13 +571,18 @@ class AlexaThermostatController(AlexaCapibility):
def get_property(self, name):
"""Read and return a property."""
if name == 'thermostatMode':
ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE)
mode = API_THERMOSTAT_MODES.get(ha_mode)
if mode is None:
_LOGGER.error("%s (%s) has unsupported %s value '%s'",
self.entity.entity_id, type(self.entity),
climate.ATTR_OPERATION_MODE, ha_mode)
raise UnsupportedProperty(name)
preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)
if preset in API_THERMOSTAT_PRESETS:
mode = API_THERMOSTAT_PRESETS[preset]
else:
mode = API_THERMOSTAT_MODES.get(self.entity.state)
if mode is None:
_LOGGER.error(
"%s (%s) has unsupported state value '%s'",
self.entity.entity_id, type(self.entity),
self.entity.state)
raise UnsupportedProperty(name)
return mode
unit = self.hass.config.units.temperature_unit

View File

@@ -42,11 +42,11 @@ class AbstractConfig:
self._unsub_proactive_report = self.hass.async_create_task(
async_enable_proactive_mode(self.hass, self)
)
resp = await self._unsub_proactive_report
# Failed to start reporting.
if resp is None:
try:
await self._unsub_proactive_report
except Exception: # pylint: disable=broad-except
self._unsub_proactive_report = None
raise
async def async_disable_proactive_mode(self):
"""Disable proactive mode."""

View File

@@ -2,7 +2,6 @@
from collections import OrderedDict
from homeassistant.const import (
STATE_OFF,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
@@ -57,16 +56,17 @@ API_TEMP_UNITS = {
# reverse mapping of this dict and we want to map the first occurrance of OFF
# back to HA state.
API_THERMOSTAT_MODES = OrderedDict([
(climate.STATE_HEAT, 'HEAT'),
(climate.STATE_COOL, 'COOL'),
(climate.STATE_AUTO, 'AUTO'),
(climate.STATE_ECO, 'ECO'),
(climate.STATE_MANUAL, 'AUTO'),
(STATE_OFF, 'OFF'),
(climate.STATE_IDLE, 'OFF'),
(climate.STATE_FAN_ONLY, 'OFF'),
(climate.STATE_DRY, 'OFF'),
(climate.HVAC_MODE_HEAT, 'HEAT'),
(climate.HVAC_MODE_COOL, 'COOL'),
(climate.HVAC_MODE_HEAT_COOL, 'AUTO'),
(climate.HVAC_MODE_AUTO, 'AUTO'),
(climate.HVAC_MODE_OFF, 'OFF'),
(climate.HVAC_MODE_FAN_ONLY, 'OFF'),
(climate.HVAC_MODE_DRY, 'OFF'),
])
API_THERMOSTAT_PRESETS = {
climate.PRESET_ECO: 'ECO'
}
PERCENTAGE_FAN_MAP = {
fan.SPEED_LOW: 33,

View File

@@ -248,9 +248,11 @@ class ClimateCapabilities(AlexaEntity):
def interfaces(self):
"""Yield the supported interfaces."""
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_ON_OFF:
# If we support two modes, one being off, we allow turning on too.
if (climate.HVAC_MODE_OFF in
self.entity.attributes[climate.ATTR_HVAC_MODES]):
yield AlexaPowerController(self.entity)
yield AlexaThermostatController(self.hass, self.entity)
yield AlexaTemperatureSensor(self.hass, self.entity)
yield AlexaEndpointHealth(self.hass, self.entity)
@@ -337,16 +339,12 @@ class MediaPlayerCapabilities(AlexaEntity):
def interfaces(self):
"""Yield the supported interfaces."""
yield AlexaEndpointHealth(self.hass, self.entity)
yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.const.SUPPORT_VOLUME_SET:
yield AlexaSpeaker(self.entity)
power_features = (media_player.SUPPORT_TURN_ON |
media_player.SUPPORT_TURN_OFF)
if supported & power_features:
yield AlexaPowerController(self.entity)
step_volume_features = (media_player.const.SUPPORT_VOLUME_MUTE |
media_player.const.SUPPORT_VOLUME_STEP)
if supported & step_volume_features:

View File

@@ -4,44 +4,26 @@ import logging
import math
from homeassistant import core as ha
from homeassistant.util.decorator import Registry
import homeassistant.util.color as color_util
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_UNLOCK,
SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.components.climate import const as climate
from homeassistant.components import cover, fan, group, light, media_player
from homeassistant.components.climate import const as climate
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
from homeassistant.util.temperature import convert as convert_temperature
from .const import (
API_TEMP_UNITS,
API_THERMOSTAT_MODES,
Cause,
)
API_TEMP_UNITS, API_THERMOSTAT_MODES, API_THERMOSTAT_PRESETS, Cause)
from .entities import async_get_entities
from .state_report import async_enable_proactive_mode
from .errors import (
AlexaInvalidValueError,
AlexaTempRangeError,
AlexaUnsupportedThermostatModeError,
)
AlexaInvalidValueError, AlexaTempRangeError,
AlexaUnsupportedThermostatModeError)
from .state_report import async_enable_proactive_mode
_LOGGER = logging.getLogger(__name__)
HANDLERS = Registry()
@@ -98,6 +80,12 @@ async def async_api_turn_on(hass, config, directive, context):
service = SERVICE_TURN_ON
if domain == cover.DOMAIN:
service = cover.SERVICE_OPEN_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = (media_player.SUPPORT_TURN_ON |
media_player.SUPPORT_TURN_OFF)
if not supported & power_features:
service = media_player.SERVICE_MEDIA_PLAY
await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id
@@ -117,6 +105,12 @@ async def async_api_turn_off(hass, config, directive, context):
service = SERVICE_TURN_OFF
if entity.domain == cover.DOMAIN:
service = cover.SERVICE_CLOSE_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = (media_player.SUPPORT_TURN_ON |
media_player.SUPPORT_TURN_OFF)
if not supported & power_features:
service = media_player.SERVICE_MEDIA_STOP
await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id
@@ -686,23 +680,45 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
mode = directive.payload['thermostatMode']
mode = mode if isinstance(mode, str) else mode['value']
operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST)
ha_mode = next(
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
None
)
if ha_mode not in operation_list:
msg = 'The requested thermostat mode {} is not supported'.format(mode)
raise AlexaUnsupportedThermostatModeError(msg)
data = {
ATTR_ENTITY_ID: entity.entity_id,
climate.ATTR_OPERATION_MODE: ha_mode,
}
ha_preset = next(
(k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode),
None
)
if ha_preset:
presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
if ha_preset not in presets:
msg = 'The requested thermostat mode {} is not supported'.format(
ha_preset
)
raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_PRESET_MODE
data[climate.ATTR_PRESET_MODE] = climate.PRESET_ECO
else:
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
ha_mode = next(
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
None
)
if ha_mode not in operation_list:
msg = 'The requested thermostat mode {} is not supported'.format(
mode
)
raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_HVAC_MODE
data[climate.ATTR_HVAC_MODE] = ha_mode
response = directive.response()
await hass.services.async_call(
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
climate.DOMAIN, service, data,
blocking=False, context=context)
response.add_context_property({
'name': 'thermostatMode',

View File

@@ -52,7 +52,7 @@ class AlexaConfig(AbstractConfig):
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG, {})
return self._config.get(CONF_ENTITY_CONFIG) or {}
def should_expose(self, entity_id):
"""If an entity should be exposed."""

View File

@@ -21,6 +21,9 @@ async def async_enable_proactive_mode(hass, smart_home_config):
Proactive mode makes this component report state changes to Alexa.
"""
# Validate we can get access token.
await smart_home_config.async_get_access_token()
async def async_entity_state_listener(changed_entity, old_state,
new_state):
if not new_state:

View File

@@ -7,11 +7,8 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_ON_OFF, STATE_HEAT)
from homeassistant.const import ATTR_NAME
from homeassistant.const import (ATTR_TEMPERATURE,
STATE_OFF, TEMP_CELSIUS)
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT)
from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
@@ -20,8 +17,7 @@ from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
@@ -177,11 +173,6 @@ class AmbiclimateEntity(ClimateDevice):
"""Return the current humidity."""
return self._data.get('humidity')
@property
def is_on(self):
"""Return true if heater is on."""
return self._data.get('power', '').lower() == 'on'
@property
def min_temp(self):
"""Return the minimum temperature."""
@@ -198,9 +189,17 @@ class AmbiclimateEntity(ClimateDevice):
return SUPPORT_FLAGS
@property
def current_operation(self):
def hvac_modes(self):
"""Return the list of available hvac operation modes."""
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
@property
def hvac_mode(self):
"""Return current operation."""
return STATE_HEAT if self.is_on else STATE_OFF
if self._data.get('power', '').lower() == 'on':
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -209,13 +208,13 @@ class AmbiclimateEntity(ClimateDevice):
return
await self._heater.set_target_temperature(temperature)
async def async_turn_on(self):
"""Turn device on."""
await self._heater.turn_on()
async def async_turn_off(self):
"""Turn device off."""
await self._heater.turn_off()
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
await self._heater.turn_on()
return
if hvac_mode == HVAC_MODE_OFF:
await self._heater.turn_off()
async def async_update(self):
"""Retrieve latest state."""

View File

@@ -119,8 +119,8 @@ TYPE_WINDSPEEDMPH = 'windspeedmph'
TYPE_YEARLYRAININ = 'yearlyrainin'
SENSOR_TYPES = {
TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None),
TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, None),
TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, None),
TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, 'pressure'),
TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, 'pressure'),
TYPE_BATT10: ('Battery 10', None, TYPE_BINARY_SENSOR, 'battery'),
TYPE_BATT1: ('Battery 1', None, TYPE_BINARY_SENSOR, 'battery'),
TYPE_BATT2: ('Battery 2', None, TYPE_BINARY_SENSOR, 'battery'),
@@ -134,23 +134,23 @@ SENSOR_TYPES = {
TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'),
TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None),
TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None),
TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, None),
TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, 'temperature'),
TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None),
TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, None),
TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, 'temperature'),
TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None),
TYPE_HUMIDITY10: ('Humidity 10', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY1: ('Humidity 1', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY2: ('Humidity 2', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY3: ('Humidity 3', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY4: ('Humidity 4', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY5: ('Humidity 5', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY6: ('Humidity 6', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY7: ('Humidity 7', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY8: ('Humidity 8', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY9: ('Humidity 9', '%', TYPE_SENSOR, None),
TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, None),
TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, None),
TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, None),
TYPE_HUMIDITY10: ('Humidity 10', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY1: ('Humidity 1', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY2: ('Humidity 2', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY3: ('Humidity 3', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY4: ('Humidity 4', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY5: ('Humidity 5', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY6: ('Humidity 6', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY7: ('Humidity 7', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY8: ('Humidity 8', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY9: ('Humidity 9', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, 'humidity'),
TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, 'humidity'),
TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, 'timestamp'),
TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None),
TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None),
TYPE_RELAY10: ('Relay 10', None, TYPE_BINARY_SENSOR, 'connectivity'),
@@ -163,39 +163,39 @@ SENSOR_TYPES = {
TYPE_RELAY7: ('Relay 7', None, TYPE_BINARY_SENSOR, 'connectivity'),
TYPE_RELAY8: ('Relay 8', None, TYPE_BINARY_SENSOR, 'connectivity'),
TYPE_RELAY9: ('Relay 9', None, TYPE_BINARY_SENSOR, 'connectivity'),
TYPE_SOILHUM10: ('Soil Humidity 10', '%', TYPE_SENSOR, None),
TYPE_SOILHUM1: ('Soil Humidity 1', '%', TYPE_SENSOR, None),
TYPE_SOILHUM2: ('Soil Humidity 2', '%', TYPE_SENSOR, None),
TYPE_SOILHUM3: ('Soil Humidity 3', '%', TYPE_SENSOR, None),
TYPE_SOILHUM4: ('Soil Humidity 4', '%', TYPE_SENSOR, None),
TYPE_SOILHUM5: ('Soil Humidity 5', '%', TYPE_SENSOR, None),
TYPE_SOILHUM6: ('Soil Humidity 6', '%', TYPE_SENSOR, None),
TYPE_SOILHUM7: ('Soil Humidity 7', '%', TYPE_SENSOR, None),
TYPE_SOILHUM8: ('Soil Humidity 8', '%', TYPE_SENSOR, None),
TYPE_SOILHUM9: ('Soil Humidity 9', '%', TYPE_SENSOR, None),
TYPE_SOILTEMP10F: ('Soil Temp 10', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP1F: ('Soil Temp 1', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP2F: ('Soil Temp 2', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP3F: ('Soil Temp 3', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP4F: ('Soil Temp 4', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP5F: ('Soil Temp 5', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP6F: ('Soil Temp 6', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP7F: ('Soil Temp 7', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP8F: ('Soil Temp 8', '°F', TYPE_SENSOR, None),
TYPE_SOILTEMP9F: ('Soil Temp 9', '°F', TYPE_SENSOR, None),
TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None),
TYPE_TEMP10F: ('Temp 10', '°F', TYPE_SENSOR, None),
TYPE_TEMP1F: ('Temp 1', '°F', TYPE_SENSOR, None),
TYPE_TEMP2F: ('Temp 2', '°F', TYPE_SENSOR, None),
TYPE_TEMP3F: ('Temp 3', '°F', TYPE_SENSOR, None),
TYPE_TEMP4F: ('Temp 4', '°F', TYPE_SENSOR, None),
TYPE_TEMP5F: ('Temp 5', '°F', TYPE_SENSOR, None),
TYPE_TEMP6F: ('Temp 6', '°F', TYPE_SENSOR, None),
TYPE_TEMP7F: ('Temp 7', '°F', TYPE_SENSOR, None),
TYPE_TEMP8F: ('Temp 8', '°F', TYPE_SENSOR, None),
TYPE_TEMP9F: ('Temp 9', '°F', TYPE_SENSOR, None),
TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, None),
TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, None),
TYPE_SOILHUM10: ('Soil Humidity 10', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM1: ('Soil Humidity 1', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM2: ('Soil Humidity 2', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM3: ('Soil Humidity 3', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM4: ('Soil Humidity 4', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM5: ('Soil Humidity 5', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM6: ('Soil Humidity 6', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM7: ('Soil Humidity 7', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM8: ('Soil Humidity 8', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILHUM9: ('Soil Humidity 9', '%', TYPE_SENSOR, 'humidity'),
TYPE_SOILTEMP10F: ('Soil Temp 10', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP1F: ('Soil Temp 1', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP2F: ('Soil Temp 2', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP3F: ('Soil Temp 3', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP4F: ('Soil Temp 4', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP5F: ('Soil Temp 5', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP6F: ('Soil Temp 6', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP7F: ('Soil Temp 7', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP8F: ('Soil Temp 8', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOILTEMP9F: ('Soil Temp 9', '°F', TYPE_SENSOR, 'temperature'),
TYPE_SOLARRADIATION: ('Solar Rad', 'lx', TYPE_SENSOR, 'illuminance'),
TYPE_TEMP10F: ('Temp 10', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP1F: ('Temp 1', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP2F: ('Temp 2', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP3F: ('Temp 3', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP4F: ('Temp 4', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP5F: ('Temp 5', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP6F: ('Temp 6', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP7F: ('Temp 7', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP8F: ('Temp 8', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMP9F: ('Temp 9', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, 'temperature'),
TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None),
TYPE_UV: ('uv', 'Index', TYPE_SENSOR, None),
TYPE_WEEKLYRAININ: ('Weekly Rain', 'in', TYPE_SENSOR, None),
@@ -404,9 +404,10 @@ class AmbientWeatherEntity(Entity):
def __init__(
self, ambient, mac_address, station_name, sensor_type,
sensor_name):
sensor_name, device_class):
"""Initialize the sensor."""
self._ambient = ambient
self._device_class = device_class
self._async_unsub_dispatcher_connect = None
self._mac_address = mac_address
self._sensor_name = sensor_name
@@ -420,6 +421,11 @@ class AmbientWeatherEntity(Entity):
return self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
self._sensor_type) is not None
@property
def device_class(self):
"""Return the device class."""
return self._device_class
@property
def device_info(self):
"""Return device registry information for this entity."""

View File

@@ -39,20 +39,6 @@ async def async_setup_entry(hass, entry, async_add_entities):
class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice):
"""Define an Ambient binary sensor."""
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
device_class):
"""Initialize the sensor."""
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name)
self._device_class = device_class
@property
def device_class(self):
"""Return the device class."""
return self._device_class
@property
def is_on(self):
"""Return the status of the sensor."""

View File

@@ -3,7 +3,7 @@ import logging
from homeassistant.const import ATTR_NAME
from . import SENSOR_TYPES, AmbientWeatherEntity
from . import SENSOR_TYPES, TYPE_SOLARRADIATION, AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR
_LOGGER = logging.getLogger(__name__)
@@ -22,12 +22,12 @@ async def async_setup_entry(hass, entry, async_add_entities):
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions:
name, unit, kind, _ = SENSOR_TYPES[condition]
name, unit, kind, device_class = SENSOR_TYPES[condition]
if kind == TYPE_SENSOR:
sensor_list.append(
AmbientWeatherSensor(
ambient, mac_address, station[ATTR_NAME], condition,
name, unit))
name, device_class, unit))
async_add_entities(sensor_list, True)
@@ -37,10 +37,15 @@ class AmbientWeatherSensor(AmbientWeatherEntity):
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
unit):
device_class, unit):
"""Initialize the sensor."""
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name)
ambient,
mac_address,
station_name,
sensor_type,
sensor_name,
device_class)
self._unit = unit
@@ -56,5 +61,13 @@ class AmbientWeatherSensor(AmbientWeatherEntity):
async def async_update(self):
"""Fetch new state data for the sensor."""
self._state = self._ambient.stations[
new_state = self._ambient.stations[
self._mac_address][ATTR_LAST_DATA].get(self._sensor_type)
if self._sensor_type == TYPE_SOLARRADIATION:
# Ambient's units for solar radiation (illuminance) are
# W/m^2; since those aren't commonly used in the HASS
# world, transform them to lx:
self._state = round(float(new_state)/0.0079)
else:
self._state = new_state

View File

@@ -3,7 +3,7 @@
"name": "Androidtv",
"documentation": "https://www.home-assistant.io/components/androidtv",
"requirements": [
"androidtv==0.0.16"
"androidtv==0.0.18"
],
"dependencies": [],
"codeowners": []

View File

@@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"one": "Een",
"other": "Ander"
},
"error": {
"one": "Een",
"other": "Ander"
},
"step": {
"one": "Een",
"other": "Ander"
},
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,23 @@
{
"config": {
"abort": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"error": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"step": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@@ -0,0 +1,176 @@
"""Arcam component."""
import logging
import asyncio
import voluptuous as vol
import async_timeout
from arcam.fmj.client import Client
from arcam.fmj import ConnectionFailed
from homeassistant import config_entries
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_ZONE,
SERVICE_TURN_ON,
)
from .const import (
DOMAIN,
DOMAIN_DATA_ENTRIES,
DOMAIN_DATA_CONFIG,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
SIGNAL_CLIENT_DATA,
SIGNAL_CLIENT_STARTED,
SIGNAL_CLIENT_STOPPED,
)
_LOGGER = logging.getLogger(__name__)
def _optional_zone(value):
if value:
return ZONE_SCHEMA(value)
return ZONE_SCHEMA({})
def _zone_name_validator(config):
for zone, zone_config in config[CONF_ZONE].items():
if CONF_NAME not in zone_config:
zone_config[CONF_NAME] = "{} ({}:{}) - {}".format(
DEFAULT_NAME,
config[CONF_HOST],
config[CONF_PORT],
zone)
return config
ZONE_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(SERVICE_TURN_ON): cv.SERVICE_SCHEMA,
}
)
DEVICE_SCHEMA = vol.Schema(
vol.All({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.positive_int,
vol.Optional(
CONF_ZONE, default={1: _optional_zone(None)}
): {vol.In([1, 2]): _optional_zone},
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.positive_int,
}, _zone_name_validator)
)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA])}, extra=vol.ALLOW_EXTRA
)
async def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the component."""
hass.data[DOMAIN_DATA_ENTRIES] = {}
hass.data[DOMAIN_DATA_CONFIG] = {}
for device in config[DOMAIN]:
hass.data[DOMAIN_DATA_CONFIG][
(device[CONF_HOST], device[CONF_PORT])
] = device
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_HOST: device[CONF_HOST],
CONF_PORT: device[CONF_PORT],
},
)
)
return True
async def async_setup_entry(
hass: HomeAssistantType, entry: config_entries.ConfigEntry
):
"""Set up an access point from a config entry."""
client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
config = hass.data[DOMAIN_DATA_CONFIG].get(
(entry.data[CONF_HOST], entry.data[CONF_PORT]),
DEVICE_SCHEMA(
{
CONF_HOST: entry.data[CONF_HOST],
CONF_PORT: entry.data[CONF_PORT],
}
),
)
hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id] = {
"client": client,
"config": config,
}
asyncio.ensure_future(
_run_client(hass, client, config[CONF_SCAN_INTERVAL])
)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "media_player")
)
return True
async def _run_client(hass, client, interval):
task = asyncio.Task.current_task()
run = True
async def _stop(_):
nonlocal run
run = False
task.cancel()
await task
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop)
def _listen(_):
hass.helpers.dispatcher.async_dispatcher_send(
SIGNAL_CLIENT_DATA, client.host
)
while run:
try:
with async_timeout.timeout(interval):
await client.start()
_LOGGER.debug("Client connected %s", client.host)
hass.helpers.dispatcher.async_dispatcher_send(
SIGNAL_CLIENT_STARTED, client.host
)
try:
with client.listen(_listen):
await client.process()
finally:
await client.stop()
_LOGGER.debug("Client disconnected %s", client.host)
hass.helpers.dispatcher.async_dispatcher_send(
SIGNAL_CLIENT_STOPPED, client.host
)
except ConnectionFailed:
await asyncio.sleep(interval)
except asyncio.TimeoutError:
continue

View File

@@ -0,0 +1,27 @@
"""Config flow to configure the Arcam FMJ component."""
from operator import itemgetter
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PORT
from .const import DOMAIN
_GETKEY = itemgetter(CONF_HOST, CONF_PORT)
@config_entries.HANDLERS.register(DOMAIN)
class ArcamFmjFlowHandler(config_entries.ConfigFlow):
"""Handle a SimpliSafe config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
entries = self.hass.config_entries.async_entries(DOMAIN)
import_key = _GETKEY(import_config)
for entry in entries:
if _GETKEY(entry.data) == import_key:
return self.async_abort(reason="already_setup")
return self.async_create_entry(title="Arcam FMJ", data=import_config)

View File

@@ -0,0 +1,13 @@
"""Constants used for arcam."""
DOMAIN = "arcam_fmj"
SIGNAL_CLIENT_STARTED = "arcam.client_started"
SIGNAL_CLIENT_STOPPED = "arcam.client_stopped"
SIGNAL_CLIENT_DATA = "arcam.client_data"
DEFAULT_PORT = 50000
DEFAULT_NAME = "Arcam FMJ"
DEFAULT_SCAN_INTERVAL = 5
DOMAIN_DATA_ENTRIES = "{}.entries".format(DOMAIN)
DOMAIN_DATA_CONFIG = "{}.config".format(DOMAIN)

View File

@@ -0,0 +1,13 @@
{
"domain": "arcam_fmj",
"name": "Arcam FMJ Receiver control",
"config_flow": false,
"documentation": "https://www.home-assistant.io/components/arcam_fmj",
"requirements": [
"arcam-fmj==0.4.3"
],
"dependencies": [],
"codeowners": [
"@elupus"
]
}

View File

@@ -0,0 +1,342 @@
"""Arcam media player."""
import logging
from typing import Optional
from arcam.fmj import (
DecodeMode2CH,
DecodeModeMCH,
IncomingAudioFormat,
SourceCodes,
)
from arcam.fmj.state import State
from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC,
SUPPORT_SELECT_SOUND_MODE,
SUPPORT_SELECT_SOURCE,
SUPPORT_TURN_ON,
SUPPORT_TURN_OFF,
SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP,
)
from homeassistant.const import (
CONF_NAME,
CONF_ZONE,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
)
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.helpers.service import async_call_from_config
from .const import (
SIGNAL_CLIENT_DATA,
SIGNAL_CLIENT_STARTED,
SIGNAL_CLIENT_STOPPED,
DOMAIN_DATA_ENTRIES,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistantType,
config_entry: config_entries.ConfigEntry,
async_add_entities,
):
"""Set up the configuration entry."""
data = hass.data[DOMAIN_DATA_ENTRIES][config_entry.entry_id]
client = data["client"]
config = data["config"]
async_add_entities(
[
ArcamFmj(
State(client, zone),
zone_config[CONF_NAME],
zone_config.get(SERVICE_TURN_ON),
)
for zone, zone_config in config[CONF_ZONE].items()
]
)
return True
class ArcamFmj(MediaPlayerDevice):
"""Representation of a media device."""
def __init__(self, state: State, name: str, turn_on: Optional[ConfigType]):
"""Initialize device."""
self._state = state
self._name = name
self._turn_on = turn_on
self._support = (
SUPPORT_SELECT_SOURCE
| SUPPORT_VOLUME_SET
| SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_STEP
| SUPPORT_TURN_OFF
)
if state.zn == 1:
self._support |= SUPPORT_SELECT_SOUND_MODE
def _get_2ch(self):
"""Return if source is 2 channel or not."""
audio_format, _ = self._state.get_incoming_audio_format()
return bool(
audio_format
in (
IncomingAudioFormat.PCM,
IncomingAudioFormat.ANALOGUE_DIRECT,
None,
)
)
@property
def device_info(self):
"""Return a device description for device registry."""
return {
"identifiers": {
(DOMAIN, self._state.client.host, self._state.client.port)
},
"model": "FMJ",
"manufacturer": "Arcam",
}
@property
def should_poll(self) -> bool:
"""No need to poll."""
return False
@property
def name(self):
"""Return the name of the controlled device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
if self._state.get_power():
return STATE_ON
return STATE_OFF
@property
def supported_features(self):
"""Flag media player features that are supported."""
support = self._support
if self._state.get_power() is not None or self._turn_on:
support |= SUPPORT_TURN_ON
return support
async def async_added_to_hass(self):
"""Once registed add listener for events."""
await self._state.start()
@callback
def _data(host):
if host == self._state.client.host:
self.async_schedule_update_ha_state()
@callback
def _started(host):
if host == self._state.client.host:
self.async_schedule_update_ha_state(force_refresh=True)
@callback
def _stopped(host):
if host == self._state.client.host:
self.async_schedule_update_ha_state(force_refresh=True)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_CLIENT_DATA, _data
)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_CLIENT_STARTED, _started
)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_CLIENT_STOPPED, _stopped
)
async def async_update(self):
"""Force update of state."""
_LOGGER.debug("Update state %s", self.name)
await self._state.update()
async def async_mute_volume(self, mute):
"""Send mute command."""
await self._state.set_mute(mute)
self.async_schedule_update_ha_state()
async def async_select_source(self, source):
"""Select a specific source."""
try:
value = SourceCodes[source]
except KeyError:
_LOGGER.error("Unsupported source %s", source)
return
await self._state.set_source(value)
self.async_schedule_update_ha_state()
async def async_select_sound_mode(self, sound_mode):
"""Select a specific source."""
try:
if self._get_2ch():
await self._state.set_decode_mode_2ch(
DecodeMode2CH[sound_mode]
)
else:
await self._state.set_decode_mode_mch(
DecodeModeMCH[sound_mode]
)
except KeyError:
_LOGGER.error("Unsupported sound_mode %s", sound_mode)
return
self.async_schedule_update_ha_state()
async def async_set_volume_level(self, volume):
"""Set volume level, range 0..1."""
await self._state.set_volume(round(volume * 99.0))
self.async_schedule_update_ha_state()
async def async_volume_up(self):
"""Turn volume up for media player."""
await self._state.inc_volume()
self.async_schedule_update_ha_state()
async def async_volume_down(self):
"""Turn volume up for media player."""
await self._state.dec_volume()
self.async_schedule_update_ha_state()
async def async_turn_on(self):
"""Turn the media player on."""
if self._state.get_power() is not None:
_LOGGER.debug("Turning on device using connection")
await self._state.set_power(True)
elif self._turn_on:
_LOGGER.debug("Turning on device using service call")
await async_call_from_config(
self.hass,
self._turn_on,
variables=None,
blocking=True,
validate_config=False,
)
else:
_LOGGER.error("Unable to turn on")
async def async_turn_off(self):
"""Turn the media player off."""
await self._state.set_power(False)
@property
def source(self):
"""Return the current input source."""
value = self._state.get_source()
if value is None:
return None
return value.name
@property
def source_list(self):
"""List of available input sources."""
return [x.name for x in self._state.get_source_list()]
@property
def sound_mode(self):
"""Name of the current sound mode."""
if self._state.zn != 1:
return None
if self._get_2ch():
value = self._state.get_decode_mode_2ch()
else:
value = self._state.get_decode_mode_mch()
if value:
return value.name
return None
@property
def sound_mode_list(self):
"""List of available sound modes."""
if self._state.zn != 1:
return None
if self._get_2ch():
return [x.name for x in DecodeMode2CH]
return [x.name for x in DecodeModeMCH]
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
value = self._state.get_mute()
if value is None:
return None
return value
@property
def volume_level(self):
"""Volume level of device."""
value = self._state.get_volume()
if value is None:
return None
return value / 99.0
@property
def media_content_type(self):
"""Content type of current playing media."""
source = self._state.get_source()
if source == SourceCodes.DAB:
value = MEDIA_TYPE_MUSIC
elif source == SourceCodes.FM:
value = MEDIA_TYPE_MUSIC
else:
value = None
return value
@property
def media_channel(self):
"""Channel currently playing."""
source = self._state.get_source()
if source == SourceCodes.DAB:
value = self._state.get_dab_station()
elif source == SourceCodes.FM:
value = self._state.get_rds_information()
else:
value = None
return value
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
source = self._state.get_source()
if source == SourceCodes.DAB:
value = self._state.get_dls_pdt()
else:
value = None
return value
@property
def media_title(self):
"""Title of current playing media."""
source = self._state.get_source()
if source is None:
return None
channel = self.media_channel
if channel:
value = "{} - {}".format(source.name, channel)
else:
value = source.name
return value

View File

@@ -0,0 +1,8 @@
{
"config": {
"title": "Arcam FMJ",
"step": {},
"error": {},
"abort": {}
}
}

View File

@@ -0,0 +1 @@
"""The Aurora ABB Powerone PV inverter sensor integration."""

View File

@@ -0,0 +1,10 @@
{
"domain": "aurora_abb_powerone",
"name": "Aurora ABB Solar PV",
"documentation": "https://www.home-assistant.io/components/aurora_abb_powerone/",
"dependencies": [],
"codeowners": [
"@davet2001"
],
"requirements": ["aurorapy==0.2.6"]
}

View File

@@ -0,0 +1,98 @@
"""Support for Aurora ABB PowerOne Solar Photvoltaic (PV) inverter."""
import logging
import voluptuous as vol
from aurorapy.client import AuroraSerialClient, AuroraError
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_ADDRESS, CONF_DEVICE, CONF_NAME, DEVICE_CLASS_POWER,
POWER_WATT)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_ADDRESS = 2
DEFAULT_NAME = "Solar PV"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICE): cv.string,
vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Aurora ABB PowerOne device."""
devices = []
comport = config[CONF_DEVICE]
address = config[CONF_ADDRESS]
name = config[CONF_NAME]
_LOGGER.debug("Intitialising com port=%s address=%s", comport, address)
client = AuroraSerialClient(address, comport, parity='N', timeout=1)
devices.append(AuroraABBSolarPVMonitorSensor(client, name, 'Power'))
add_entities(devices, True)
class AuroraABBSolarPVMonitorSensor(Entity):
"""Representation of a Sensor."""
def __init__(self, client, name, typename):
"""Initialize the sensor."""
self._name = "{} {}".format(name, typename)
self.client = client
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return POWER_WATT
@property
def device_class(self):
"""Return the device class."""
return DEVICE_CLASS_POWER
def update(self):
"""Fetch new state data for the sensor.
This is the only method that should fetch new data for Home Assistant.
"""
try:
self.client.connect()
# read ADC channel 3 (grid power output)
power_watts = self.client.measure(3, True)
self._state = round(power_watts, 1)
# _LOGGER.debug("Got reading %fW" % self._state)
except AuroraError as error:
# aurorapy does not have different exceptions (yet) for dealing
# with timeout vs other comms errors.
# This means the (normal) situation of no response during darkness
# raises an exception.
# aurorapy (gitlab) pull request merged 29/5/2019. When >0.2.6 is
# released, this could be modified to :
# except AuroraTimeoutError as e:
# Workaround: look at the text of the exception
if "No response after" in str(error):
_LOGGER.debug("No response from inverter (could be dark)")
else:
# print("Exception!!: {}".format(str(e)))
raise error
self._state = None
finally:
if self.client.serline.isOpen():
self.client.close()

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow
from homeassistant.util.dt import parse_datetime, utcnow
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -227,7 +227,9 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
state = await self.async_get_last_state()
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
last_triggered = state.attributes.get('last_triggered')
if last_triggered is not None:
self._last_triggered = parse_datetime(last_triggered)
_LOGGER.debug("Loaded automation %s with state %s from state "
" storage last state %s", self.entity_id,
enable_automation, state)

View File

@@ -3,13 +3,14 @@ import logging
import voluptuous as vol
from homeassistant import exceptions
from homeassistant.core import callback
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE, CONF_FOR)
from homeassistant.helpers.event import (
async_track_state_change, async_track_same_state)
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers import condition, config_validation as cv, template
TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'numeric_state',
@@ -17,7 +18,9 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Optional(CONF_BELOW): vol.Coerce(float),
vol.Optional(CONF_ABOVE): vol.Coerce(float),
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_FOR): vol.Any(
vol.All(cv.time_period, cv.positive_timedelta),
cv.template, cv.template_complex),
}), cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE))
_LOGGER = logging.getLogger(__name__)
@@ -29,9 +32,11 @@ async def async_trigger(hass, config, action, automation_info):
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
time_delta = config.get(CONF_FOR)
template.attach(hass, time_delta)
value_template = config.get(CONF_VALUE_TEMPLATE)
unsub_track_same = {}
entities_triggered = set()
period = {}
if value_template is not None:
value_template.hass = hass
@@ -67,6 +72,7 @@ async def async_trigger(hass, config, action, automation_info):
'above': above,
'from_state': from_s,
'to_state': to_s,
'for': time_delta if not time_delta else period[entity],
}
}, context=to_s.context))
@@ -78,8 +84,39 @@ async def async_trigger(hass, config, action, automation_info):
entities_triggered.add(entity)
if time_delta:
variables = {
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
'below': below,
'above': above,
}
}
try:
if isinstance(time_delta, template.Template):
period[entity] = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta.async_render(variables))
elif isinstance(time_delta, dict):
time_delta_data = {}
time_delta_data.update(
template.render_complex(time_delta, variables))
period[entity] = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta_data)
else:
period[entity] = time_delta
except (exceptions.TemplateError, vol.Invalid) as ex:
_LOGGER.error("Error rendering '%s' for template: %s",
automation_info['name'], ex)
entities_triggered.discard(entity)
return
unsub_track_same[entity] = async_track_same_state(
hass, time_delta, call_action, entity_ids=entity_id,
hass, period[entity], call_action, entity_ids=entity,
async_check_same_func=check_numeric_state)
else:
call_action()

View File

@@ -1,11 +1,16 @@
"""Offer state listening automation rules."""
import logging
import voluptuous as vol
from homeassistant import exceptions
from homeassistant.core import callback
from homeassistant.const import MATCH_ALL, CONF_PLATFORM, CONF_FOR
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.event import (
async_track_state_change, async_track_same_state)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_ENTITY_ID = 'entity_id'
CONF_FROM = 'from'
@@ -17,7 +22,9 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
# These are str on purpose. Want to catch YAML conversions
vol.Optional(CONF_FROM): str,
vol.Optional(CONF_TO): str,
vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_FOR): vol.Any(
vol.All(cv.time_period, cv.positive_timedelta),
cv.template, cv.template_complex),
}), cv.key_dependency(CONF_FOR, CONF_TO))
@@ -27,8 +34,10 @@ async def async_trigger(hass, config, action, automation_info):
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO, MATCH_ALL)
time_delta = config.get(CONF_FOR)
template.attach(hass, time_delta)
match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL)
unsub_track_same = {}
period = {}
@callback
def state_automation_listener(entity, from_s, to_s):
@@ -42,7 +51,7 @@ async def async_trigger(hass, config, action, automation_info):
'entity_id': entity,
'from_state': from_s,
'to_state': to_s,
'for': time_delta,
'for': time_delta if not time_delta else period[entity]
}
}, context=to_s.context))
@@ -55,10 +64,40 @@ async def async_trigger(hass, config, action, automation_info):
call_action()
return
variables = {
'trigger': {
'platform': 'state',
'entity_id': entity,
'from_state': from_s,
'to_state': to_s,
}
}
try:
if isinstance(time_delta, template.Template):
period[entity] = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta.async_render(variables))
elif isinstance(time_delta, dict):
time_delta_data = {}
time_delta_data.update(
template.render_complex(time_delta, variables))
period[entity] = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta_data)
else:
period[entity] = time_delta
except (exceptions.TemplateError, vol.Invalid) as ex:
_LOGGER.error("Error rendering '%s' for template: %s",
automation_info['name'], ex)
return
unsub_track_same[entity] = async_track_same_state(
hass, time_delta, call_action,
hass, period[entity], call_action,
lambda _, _2, to_state: to_state.state == to_s.state,
entity_ids=entity_id)
entity_ids=entity)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)

View File

@@ -5,17 +5,20 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_FOR
from homeassistant import exceptions
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_same_state, async_track_template)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import config_validation as cv, template
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'template',
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_FOR): vol.Any(
vol.All(cv.time_period, cv.positive_timedelta),
cv.template, cv.template_complex),
})
@@ -24,6 +27,7 @@ async def async_trigger(hass, config, action, automation_info):
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
time_delta = config.get(CONF_FOR)
template.attach(hass, time_delta)
unsub_track_same = None
@callback
@@ -40,6 +44,7 @@ async def async_trigger(hass, config, action, automation_info):
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
'for': time_delta if not time_delta else period
},
}, context=(to_s.context if to_s else None)))
@@ -47,8 +52,38 @@ async def async_trigger(hass, config, action, automation_info):
call_action()
return
variables = {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
}
try:
if isinstance(time_delta, template.Template):
period = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta.async_render(variables))
elif isinstance(time_delta, dict):
time_delta_data = {}
time_delta_data.update(
template.render_complex(time_delta, variables))
period = vol.All(
cv.time_period,
cv.positive_timedelta)(
time_delta_data)
else:
period = time_delta
except (exceptions.TemplateError, vol.Invalid) as ex:
_LOGGER.error("Error rendering '%s' for template: %s",
automation_info['name'], ex)
return
unsub_track_same = async_track_same_state(
hass, time_delta, call_action,
hass, period, call_action,
lambda _, _2, _3: condition.async_template(hass, value_template),
value_template.extract_entities())

View File

@@ -3,7 +3,8 @@
"abort": {
"already_configured": "Enheten er allerede konfigurert",
"bad_config_file": "D\u00e5rlig data fra konfigurasjonsfilen",
"link_local_address": "Linking av lokale adresser st\u00f8ttes ikke"
"link_local_address": "Linking av lokale adresser st\u00f8ttes ikke",
"not_axis_device": "Oppdaget enhet ikke en Axis enhet"
},
"error": {
"already_configured": "Enheten er allerede konfigurert",

View File

@@ -3,7 +3,8 @@
"abort": {
"already_configured": "Naprava je \u017ee konfigurirana",
"bad_config_file": "Napa\u010dni podatki iz konfiguracijske datoteke",
"link_local_address": "Lokalni naslovi povezave niso podprti"
"link_local_address": "Lokalni naslovi povezave niso podprti",
"not_axis_device": "Odkrita naprava ni naprava Axis"
},
"error": {
"already_configured": "Naprava je \u017ee konfigurirana",

View File

@@ -3,7 +3,8 @@
"abort": {
"already_configured": "Enheten \u00e4r redan konfigurerad",
"bad_config_file": "Felaktig data fr\u00e5n config fil",
"link_local_address": "Link local addresses are not supported"
"link_local_address": "Link local addresses are not supported",
"not_axis_device": "Uppt\u00e4ckte enhet som inte \u00e4r en Axis enhet"
},
"error": {
"already_configured": "Enheten \u00e4r redan konfigurerad",

View File

@@ -3,7 +3,7 @@
"name": "Blink",
"documentation": "https://www.home-assistant.io/components/blink",
"requirements": [
"blinkpy==0.14.0"
"blinkpy==0.14.1"
],
"dependencies": [],
"codeowners": [

View File

@@ -3,7 +3,8 @@
"name": "Braviatv",
"documentation": "https://www.home-assistant.io/components/braviatv",
"requirements": [
"braviarc-homeassistant==0.3.7.dev0"
"braviarc-homeassistant==0.3.7.dev0",
"getmac==0.8.1"
],
"dependencies": [
"configurator"

View File

@@ -1,7 +1,8 @@
"""Support for interface with a Sony Bravia TV."""
import ipaddress
import logging
import re
from getmac import get_mac_address
import voluptuous as vol
from homeassistant.components.media_player import (
@@ -40,19 +41,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def _get_mac_address(ip_address):
"""Get the MAC address of the device."""
from subprocess import Popen, PIPE
pid = Popen(["arp", "-n", ip_address], stdout=PIPE)
pid_component = pid.communicate()[0]
match = re.search(r"(([a-f\d]{1,2}\:){5}[a-f\d]{1,2})".encode('UTF-8'),
pid_component)
if match is not None:
return match.groups()[0]
return None
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Sony Bravia TV platform."""
host = config.get(CONF_HOST)
@@ -84,9 +72,15 @@ def setup_bravia(config, pin, hass, add_entities):
request_configuration(config, hass, add_entities)
return
mac = _get_mac_address(host)
if mac is not None:
mac = mac.decode('utf8')
try:
if ipaddress.ip_address(host).version == 6:
mode = 'ip6'
else:
mode = 'ip'
except ValueError:
mode = 'hostname'
mac = get_mac_address(**{mode: host})
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)

View File

@@ -186,7 +186,7 @@ def _get_camera_from_entity_id(hass, entity_id):
component = hass.data.get(DOMAIN)
if component is None:
raise HomeAssistantError('Camera component not set up')
raise HomeAssistantError('Camera integration not set up')
camera = component.get_entity(entity_id)

View File

@@ -286,7 +286,7 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
"""
_LOGGER.warning(
'Setting configuration for Cast via platform is deprecated. '
'Configure via Cast component instead.')
'Configure via Cast integration instead.')
await _async_setup_platform(
hass, config, async_add_entities, discovery_info)

View File

@@ -55,6 +55,7 @@ class SSLCertificate(Entity):
self.server_port = server_port
self._name = sensor_name
self._state = None
self._available = False
@property
def name(self):
@@ -76,34 +77,39 @@ class SSLCertificate(Entity):
"""Icon to use in the frontend, if any."""
return 'mdi:certificate'
@property
def available(self):
"""Icon to use in the frontend, if any."""
return self._available
def update(self):
"""Fetch the certificate information."""
ctx = ssl.create_default_context()
try:
ctx = ssl.create_default_context()
host_info = socket.getaddrinfo(self.server_name, self.server_port)
family = host_info[0][0]
sock = ctx.wrap_socket(
socket.socket(family=family), server_hostname=self.server_name)
sock.settimeout(TIMEOUT)
sock.connect((self.server_name, self.server_port))
address = (self.server_name, self.server_port)
with socket.create_connection(
address, timeout=TIMEOUT) as sock:
with ctx.wrap_socket(
sock, server_hostname=address[0]) as ssock:
cert = ssock.getpeercert()
except socket.gaierror:
_LOGGER.error("Cannot resolve hostname: %s", self.server_name)
self._available = False
return
except socket.timeout:
_LOGGER.error(
"Connection timeout with server: %s", self.server_name)
self._available = False
return
except OSError:
_LOGGER.error("Cannot connect to %s", self.server_name)
return
try:
cert = sock.getpeercert()
except OSError:
_LOGGER.error("Cannot fetch certificate from %s", self.server_name)
_LOGGER.error("Cannot fetch certificate from %s",
self.server_name, exc_info=1)
self._available = False
return
ts_seconds = ssl.cert_time_to_seconds(cert['notAfter'])
timestamp = datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.today()
self._available = True
self._state = expiry.days

View File

@@ -1,68 +1,42 @@
"""Provides functionality to interact with climate devices."""
from datetime import timedelta
import logging
import functools as ft
import logging
from typing import Any, Dict, List, Optional
import voluptuous as vol
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE,
SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.helpers.typing import (
ConfigType, HomeAssistantType, ServiceDataType)
from homeassistant.util.temperature import convert as convert_temperature
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_LIST,
ATTR_FAN_MODE,
ATTR_HOLD_MODE,
ATTR_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MIN_TEMP,
ATTR_OPERATION_LIST,
ATTR_OPERATION_MODE,
ATTR_SWING_LIST,
ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_STEP,
DOMAIN,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_HUMIDITY,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE,
SUPPORT_HOLD_MODE,
SUPPORT_SWING_MODE,
SUPPORT_AWAY_MODE,
SUPPORT_AUX_HEAT,
)
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS,
ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES,
ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODE_COOL,
HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES,
SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_TARGET_TEMPERATURE)
from .reproduce_state import async_reproduce_states # noqa
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
DEFAULT_MIN_HUMITIDY = 30
DEFAULT_MIN_HUMIDITY = 30
DEFAULT_MAX_HUMIDITY = 99
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -76,14 +50,9 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({
TURN_ON_OFF_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
@@ -96,20 +65,20 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
}
))
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
})
SET_HOLD_MODE_SCHEMA = vol.Schema({
SET_PRESET_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HOLD_MODE): cv.string,
vol.Required(ATTR_PRESET_MODE): vol.Maybe(cv.string),
})
SET_OPERATION_MODE_SCHEMA = vol.Schema({
SET_HVAC_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_OPERATION_MODE): cv.string,
vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
})
SET_HUMIDITY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
@@ -121,19 +90,27 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
})
async def async_setup(hass, config):
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Set up climate devices."""
component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
component.async_register_entity_service(
SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA,
async_service_away_mode
SERVICE_TURN_ON, TURN_ON_OFF_SCHEMA,
'async_turn_on'
)
component.async_register_entity_service(
SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA,
'async_set_hold_mode'
SERVICE_TURN_OFF, TURN_ON_OFF_SCHEMA,
'async_turn_off'
)
component.async_register_entity_service(
SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA,
'async_set_hvac_mode'
)
component.async_register_entity_service(
SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA,
'async_set_preset_mode'
)
component.async_register_entity_service(
SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA,
@@ -151,32 +128,20 @@ async def async_setup(hass, config):
SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA,
'async_set_fan_mode'
)
component.async_register_entity_service(
SERVICE_SET_OPERATION_MODE, SET_OPERATION_MODE_SCHEMA,
'async_set_operation_mode'
)
component.async_register_entity_service(
SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA,
'async_set_swing_mode'
)
component.async_register_entity_service(
SERVICE_TURN_OFF, ON_OFF_SERVICE_SCHEMA,
'async_turn_off'
)
component.async_register_entity_service(
SERVICE_TURN_ON, ON_OFF_SERVICE_SCHEMA,
'async_turn_on'
)
return True
async def async_setup_entry(hass, entry):
async def async_setup_entry(hass: HomeAssistantType, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
async def async_unload_entry(hass: HomeAssistantType, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
@@ -185,27 +150,23 @@ class ClimateDevice(Entity):
"""Representation of a climate device."""
@property
def state(self):
def state(self) -> str:
"""Return the current state."""
if self.is_on is False:
return STATE_OFF
if self.current_operation:
return self.current_operation
if self.is_on:
return STATE_ON
return None
return self.hvac_mode
@property
def precision(self):
def precision(self) -> float:
"""Return the precision of the system."""
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
return PRECISION_TENTHS
return PRECISION_WHOLE
@property
def state_attributes(self):
def state_attributes(self) -> Dict[str, Any]:
"""Return the optional state attributes."""
supported_features = self.supported_features
data = {
ATTR_HVAC_MODES: self.hvac_modes,
ATTR_CURRENT_TEMPERATURE: show_temp(
self.hass, self.current_temperature, self.temperature_unit,
self.precision),
@@ -215,21 +176,20 @@ class ClimateDevice(Entity):
ATTR_MAX_TEMP: show_temp(
self.hass, self.max_temp, self.temperature_unit,
self.precision),
ATTR_TEMPERATURE: show_temp(
self.hass, self.target_temperature, self.temperature_unit,
self.precision),
}
supported_features = self.supported_features
if self.target_temperature_step is not None:
if self.target_temperature_step:
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
if supported_features & SUPPORT_TARGET_TEMPERATURE:
data[ATTR_TEMPERATURE] = show_temp(
self.hass, self.target_temperature, self.temperature_unit,
self.precision)
if supported_features & SUPPORT_TARGET_TEMPERATURE_RANGE:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
self.precision)
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
@@ -239,301 +199,277 @@ class ClimateDevice(Entity):
if supported_features & SUPPORT_TARGET_HUMIDITY:
data[ATTR_HUMIDITY] = self.target_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
data[ATTR_MIN_HUMIDITY] = self.min_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
data[ATTR_MAX_HUMIDITY] = self.max_humidity
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
if supported_features & SUPPORT_FAN_MODE:
data[ATTR_FAN_MODE] = self.current_fan_mode
if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list
data[ATTR_FAN_MODE] = self.fan_mode
data[ATTR_FAN_MODES] = self.fan_modes
if supported_features & SUPPORT_OPERATION_MODE:
data[ATTR_OPERATION_MODE] = self.current_operation
if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list
if self.hvac_action:
data[ATTR_HVAC_ACTIONS] = self.hvac_action
if supported_features & SUPPORT_HOLD_MODE:
data[ATTR_HOLD_MODE] = self.current_hold_mode
if supported_features & SUPPORT_PRESET_MODE:
data[ATTR_PRESET_MODE] = self.preset_mode
data[ATTR_PRESET_MODES] = self.preset_modes
if supported_features & SUPPORT_SWING_MODE:
data[ATTR_SWING_MODE] = self.current_swing_mode
if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list
if supported_features & SUPPORT_AWAY_MODE:
is_away = self.is_away_mode_on
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
data[ATTR_SWING_MODE] = self.swing_mode
data[ATTR_SWING_MODES] = self.swing_modes
if supported_features & SUPPORT_AUX_HEAT:
is_aux_heat = self.is_aux_heat_on
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
return data
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement used by the platform."""
raise NotImplementedError
raise NotImplementedError()
@property
def current_humidity(self):
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return None
@property
def target_humidity(self):
def target_humidity(self) -> Optional[int]:
"""Return the humidity we try to reach."""
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
raise NotImplementedError()
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
raise NotImplementedError()
@property
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
return None
@property
def operation_list(self):
"""Return the list of available operation modes."""
return None
@property
def current_temperature(self):
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return None
@property
def target_temperature(self):
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
return None
@property
def target_temperature_step(self):
def target_temperature_step(self) -> Optional[float]:
"""Return the supported step of target temperature."""
return None
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
return None
def target_temperature_high(self) -> Optional[float]:
"""Return the highbound target temperature we try to reach.
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
"""
raise NotImplementedError
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
return None
def target_temperature_low(self) -> Optional[float]:
"""Return the lowbound target temperature we try to reach.
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
"""
raise NotImplementedError
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return None
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
raise NotImplementedError
@property
def current_hold_mode(self):
"""Return the current hold mode, e.g., home, away, temp."""
return None
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes.
Requires SUPPORT_PRESET_MODE.
"""
raise NotImplementedError
@property
def is_on(self):
"""Return true if on."""
return None
def is_aux_heat(self) -> Optional[bool]:
"""Return true if aux heater.
Requires SUPPORT_AUX_HEAT.
"""
raise NotImplementedError
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
return None
def fan_mode(self) -> Optional[str]:
"""Return the fan setting.
Requires SUPPORT_FAN_MODE.
"""
raise NotImplementedError
@property
def current_fan_mode(self):
"""Return the fan setting."""
return None
def fan_modes(self) -> Optional[List[str]]:
"""Return the list of available fan modes.
Requires SUPPORT_FAN_MODE.
"""
raise NotImplementedError
@property
def fan_list(self):
"""Return the list of available fan modes."""
return None
def swing_mode(self) -> Optional[str]:
"""Return the swing setting.
Requires SUPPORT_SWING_MODE.
"""
raise NotImplementedError
@property
def current_swing_mode(self):
"""Return the fan setting."""
return None
def swing_modes(self) -> Optional[List[str]]:
"""Return the list of available swing modes.
@property
def swing_list(self):
"""Return the list of available swing modes."""
return None
Requires SUPPORT_SWING_MODE.
"""
raise NotImplementedError
def set_temperature(self, **kwargs):
def set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
raise NotImplementedError()
def async_set_temperature(self, **kwargs):
"""Set new target temperature.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
await self.hass.async_add_executor_job(
ft.partial(self.set_temperature, **kwargs))
def set_humidity(self, humidity):
def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
raise NotImplementedError()
def async_set_humidity(self, humidity):
"""Set new target humidity.
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
await self.hass.async_add_executor_job(self.set_humidity, humidity)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_humidity, humidity)
def set_fan_mode(self, fan_mode):
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
raise NotImplementedError()
def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode.
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
raise NotImplementedError()
def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode.
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_operation_mode, operation_mode)
def set_swing_mode(self, swing_mode):
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
raise NotImplementedError()
def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation.
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
await self.hass.async_add_executor_job(self.set_swing_mode, swing_mode)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_swing_mode, swing_mode)
def turn_away_mode_on(self):
"""Turn away mode on."""
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
raise NotImplementedError()
def async_turn_away_mode_on(self):
"""Turn away mode on.
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
await self.hass.async_add_executor_job(
self.set_preset_mode, preset_mode)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_away_mode_on)
def turn_away_mode_off(self):
"""Turn away mode off."""
raise NotImplementedError()
def async_turn_away_mode_off(self):
"""Turn away mode off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_away_mode_off)
def set_hold_mode(self, hold_mode):
"""Set new target hold mode."""
raise NotImplementedError()
def async_set_hold_mode(self, hold_mode):
"""Set new target hold mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_hold_mode, hold_mode)
def turn_aux_heat_on(self):
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
raise NotImplementedError()
def async_turn_aux_heat_on(self):
"""Turn auxiliary heater on.
async def async_turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
await self.hass.async_add_executor_job(self.turn_aux_heat_on)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_on)
def turn_aux_heat_off(self):
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError()
def async_turn_aux_heat_off(self):
"""Turn auxiliary heater off.
async def async_turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
await self.hass.async_add_executor_job(self.turn_aux_heat_off)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_off)
async def async_turn_on(self) -> None:
"""Turn the entity on."""
if hasattr(self, 'turn_on'):
# pylint: disable=no-member
await self.hass.async_add_executor_job(self.turn_on)
return
def turn_on(self):
"""Turn device on."""
raise NotImplementedError()
# Fake turn on
for mode in (HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_COOL):
if mode not in self.hvac_modes:
continue
await self.async_set_hvac_mode(mode)
break
def async_turn_on(self):
"""Turn device on.
async def async_turn_off(self) -> None:
"""Turn the entity off."""
if hasattr(self, 'turn_off'):
# pylint: disable=no-member
await self.hass.async_add_executor_job(self.turn_off)
return
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_on)
def turn_off(self):
"""Turn device off."""
raise NotImplementedError()
def async_turn_off(self):
"""Turn device off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_off)
# Fake turn off
if HVAC_MODE_OFF in self.hvac_modes:
await self.async_set_hvac_mode(HVAC_MODE_OFF)
@property
def supported_features(self):
def supported_features(self) -> int:
"""Return the list of supported features."""
raise NotImplementedError()
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return the minimum temperature."""
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return the maximum temperature."""
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def min_humidity(self):
def min_humidity(self) -> int:
"""Return the minimum humidity."""
return DEFAULT_MIN_HUMITIDY
return DEFAULT_MIN_HUMIDITY
@property
def max_humidity(self):
def max_humidity(self) -> int:
"""Return the maximum humidity."""
return DEFAULT_MAX_HUMIDITY
async def async_service_away_mode(entity, service):
"""Handle away mode service."""
if service.data[ATTR_AWAY_MODE]:
await entity.async_turn_away_mode_on()
else:
await entity.async_turn_away_mode_off()
async def async_service_aux_heat(entity, service):
async def async_service_aux_heat(
entity: ClimateDevice, service: ServiceDataType
) -> None:
"""Handle aux heat service."""
if service.data[ATTR_AUX_HEAT]:
await entity.async_turn_aux_heat_on()
@@ -541,7 +477,9 @@ async def async_service_aux_heat(entity, service):
await entity.async_turn_aux_heat_off()
async def async_service_temperature_set(entity, service):
async def async_service_temperature_set(
entity: ClimateDevice, service: ServiceDataType
) -> None:
"""Handle set temperature service."""
hass = entity.hass
kwargs = {}

View File

@@ -1,20 +1,104 @@
"""Provides the constants needed for component."""
# All activity disabled / Device is off/standby
HVAC_MODE_OFF = 'off'
# Heating
HVAC_MODE_HEAT = 'heat'
# Cooling
HVAC_MODE_COOL = 'cool'
# The device supports heating/cooling to a range
HVAC_MODE_HEAT_COOL = 'heat_cool'
# The temperature is set based on a schedule, learned behavior, AI or some
# other related mechanism. User is not able to adjust the temperature
HVAC_MODE_AUTO = 'auto'
# Device is in Dry/Humidity mode
HVAC_MODE_DRY = 'dry'
# Only the fan is on, not fan and another mode like cool
HVAC_MODE_FAN_ONLY = 'fan_only'
HVAC_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_AUTO,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
]
# Device is running an energy-saving mode
PRESET_ECO = 'eco'
# Device is in away mode
PRESET_AWAY = 'away'
# Device turn all valve full up
PRESET_BOOST = 'boost'
# Device is in comfort mode
PRESET_COMFORT = 'comfort'
# Device is in home mode
PRESET_HOME = 'home'
# Device is prepared for sleep
PRESET_SLEEP = 'sleep'
# Device is reacting to activity (e.g. movement sensors)
PRESET_ACTIVITY = 'activity'
# Possible fan state
FAN_ON = "on"
FAN_OFF = "off"
FAN_AUTO = "auto"
FAN_LOW = "low"
FAN_MEDIUM = "medium"
FAN_HIGH = "high"
FAN_MIDDLE = "middle"
FAN_FOCUS = "focus"
FAN_DIFFUSE = "diffuse"
# Possible swing state
SWING_OFF = "off"
SWING_BOTH = "both"
SWING_VERTICAL = "vertical"
SWING_HORIZONTAL = "horizontal"
# This are support current states of HVAC
CURRENT_HVAC_OFF = 'off'
CURRENT_HVAC_HEAT = 'heating'
CURRENT_HVAC_COOL = 'cooling'
CURRENT_HVAC_DRY = 'drying'
CURRENT_HVAC_IDLE = 'idle'
CURRENT_HVAC_FAN = 'fan'
ATTR_AUX_HEAT = 'aux_heat'
ATTR_AWAY_MODE = 'away_mode'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_FAN_LIST = 'fan_list'
ATTR_FAN_MODES = 'fan_modes'
ATTR_FAN_MODE = 'fan_mode'
ATTR_HOLD_MODE = 'hold_mode'
ATTR_PRESET_MODE = 'preset_mode'
ATTR_PRESET_MODES = 'preset_modes'
ATTR_HUMIDITY = 'humidity'
ATTR_MAX_HUMIDITY = 'max_humidity'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_HUMIDITY = 'min_humidity'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_TEMP = 'min_temp'
ATTR_OPERATION_LIST = 'operation_list'
ATTR_OPERATION_MODE = 'operation_mode'
ATTR_SWING_LIST = 'swing_list'
ATTR_HVAC_ACTIONS = 'hvac_action'
ATTR_HVAC_MODES = 'hvac_modes'
ATTR_HVAC_MODE = 'hvac_mode'
ATTR_SWING_MODES = 'swing_modes'
ATTR_SWING_MODE = 'swing_mode'
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
@@ -28,33 +112,17 @@ DEFAULT_MAX_HUMIDITY = 99
DOMAIN = 'climate'
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
SERVICE_SET_AWAY_MODE = 'set_away_mode'
SERVICE_SET_FAN_MODE = 'set_fan_mode'
SERVICE_SET_HOLD_MODE = 'set_hold_mode'
SERVICE_SET_PRESET_MODE = 'set_preset_mode'
SERVICE_SET_HUMIDITY = 'set_humidity'
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
SERVICE_SET_HVAC_MODE = 'set_hvac_mode'
SERVICE_SET_SWING_MODE = 'set_swing_mode'
SERVICE_SET_TEMPERATURE = 'set_temperature'
STATE_HEAT = 'heat'
STATE_COOL = 'cool'
STATE_IDLE = 'idle'
STATE_AUTO = 'auto'
STATE_MANUAL = 'manual'
STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco'
SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
SUPPORT_TARGET_TEMPERATURE_LOW = 4
SUPPORT_TARGET_HUMIDITY = 8
SUPPORT_TARGET_HUMIDITY_HIGH = 16
SUPPORT_TARGET_HUMIDITY_LOW = 32
SUPPORT_FAN_MODE = 64
SUPPORT_OPERATION_MODE = 128
SUPPORT_HOLD_MODE = 256
SUPPORT_SWING_MODE = 512
SUPPORT_AWAY_MODE = 1024
SUPPORT_AUX_HEAT = 2048
SUPPORT_ON_OFF = 4096
SUPPORT_TARGET_TEMPERATURE_RANGE = 2
SUPPORT_TARGET_HUMIDITY = 4
SUPPORT_FAN_MODE = 8
SUPPORT_PRESET_MODE = 16
SUPPORT_SWING_MODE = 32
SUPPORT_AUX_HEAT = 64

View File

@@ -2,27 +2,24 @@
import asyncio
from typing import Iterable, Optional
from homeassistant.const import (
ATTR_TEMPERATURE, SERVICE_TURN_OFF,
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_HOLD_MODE,
ATTR_OPERATION_MODE,
ATTR_PRESET_MODE,
ATTR_HVAC_MODE,
ATTR_SWING_MODE,
ATTR_HUMIDITY,
SERVICE_SET_AWAY_MODE,
HVAC_MODES,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_TEMPERATURE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_HUMIDITY,
DOMAIN,
@@ -33,9 +30,9 @@ async def _async_reproduce_states(hass: HomeAssistantType,
state: State,
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
async def call_service(service: str, keys: Iterable):
async def call_service(service: str, keys: Iterable, data=None):
"""Call service with set of attributes given."""
data = {}
data = data or {}
data['entity_id'] = state.entity_id
for key in keys:
if key in state.attributes:
@@ -45,17 +42,13 @@ async def _async_reproduce_states(hass: HomeAssistantType,
DOMAIN, service, data,
blocking=True, context=context)
if state.state == STATE_ON:
await call_service(SERVICE_TURN_ON, [])
elif state.state == STATE_OFF:
await call_service(SERVICE_TURN_OFF, [])
if state.state in HVAC_MODES:
await call_service(
SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state})
if ATTR_AUX_HEAT in state.attributes:
await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT])
if ATTR_AWAY_MODE in state.attributes:
await call_service(SERVICE_SET_AWAY_MODE, [ATTR_AWAY_MODE])
if (ATTR_TEMPERATURE in state.attributes) or \
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
(ATTR_TARGET_TEMP_LOW in state.attributes):
@@ -64,21 +57,14 @@ async def _async_reproduce_states(hass: HomeAssistantType,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW])
if ATTR_HOLD_MODE in state.attributes:
await call_service(SERVICE_SET_HOLD_MODE,
[ATTR_HOLD_MODE])
if ATTR_OPERATION_MODE in state.attributes:
await call_service(SERVICE_SET_OPERATION_MODE,
[ATTR_OPERATION_MODE])
if ATTR_PRESET_MODE in state.attributes:
await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE])
if ATTR_SWING_MODE in state.attributes:
await call_service(SERVICE_SET_SWING_MODE,
[ATTR_SWING_MODE])
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
if ATTR_HUMIDITY in state.attributes:
await call_service(SERVICE_SET_HUMIDITY,
[ATTR_HUMIDITY])
await call_service(SERVICE_SET_HUMIDITY, [ATTR_HUMIDITY])
@bind_hass

View File

@@ -9,23 +9,14 @@ set_aux_heat:
aux_heat:
description: New value of axillary heater.
example: true
set_away_mode:
description: Turn away mode on/off for climate device.
set_preset_mode:
description: Set preset mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
away_mode:
description: New value of away mode.
example: true
set_hold_mode:
description: Turn hold mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
hold_mode:
description: New value of hold mode
preset_mode:
description: New value of preset mode
example: 'away'
set_temperature:
description: Set target temperature of climate device.
@@ -42,9 +33,9 @@ set_temperature:
target_temp_low:
description: New target low temperature for HVAC.
example: 20
operation_mode:
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
example: 'Heat'
hvac_mode:
description: HVAC operation mode to set temperature to.
example: 'heat'
set_humidity:
description: Set target humidity of climate device.
fields:
@@ -63,15 +54,15 @@ set_fan_mode:
fan_mode:
description: New value of fan mode.
example: On Low
set_operation_mode:
description: Set operation mode for climate device.
set_hvac_mode:
description: Set HVAC operation mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.nest'
operation_mode:
hvac_mode:
description: New value of operation mode.
example: Heat
example: heat
set_swing_mode:
description: Set swing operation for climate device.
fields:
@@ -81,20 +72,6 @@ set_swing_mode:
swing_mode:
description: New value of swing mode.
turn_on:
description: Turn climate device on.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
turn_off:
description: Turn climate device off.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
ecobee_set_fan_min_on_time:
description: Set the minimum fan on time.
fields:
@@ -138,12 +115,16 @@ nuheat_resume_program:
description: Name(s) of entities to change.
example: 'climate.kitchen'
sensibo_assume_state:
description: Set Sensibo device to external state.
turn_on:
description: Turn climate device on.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
turn_off:
description: Turn climate device off.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
state:
description: State to set.
example: 'idle'

View File

@@ -157,7 +157,13 @@ async def async_setup(hass, config):
await prefs.async_initialize()
# Cloud user
if not prefs.cloud_user:
user = None
if prefs.cloud_user:
# Fetch the user. It can happen that the user no longer exists if
# an image was restored without restoring the cloud prefs.
user = await hass.auth.async_get_user(prefs.cloud_user)
if user is None:
user = await hass.auth.async_create_system_user(
'Home Assistant Cloud', [GROUP_ID_ADMIN])
await prefs.async_update(cloud_user=user.id)

View File

@@ -0,0 +1,269 @@
"""Alexa configuration for Home Assistant Cloud."""
import asyncio
from datetime import timedelta
import logging
import aiohttp
import async_timeout
from hass_nabucasa import cloud_api
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers import entity_registry
from homeassistant.helpers.event import async_call_later
from homeassistant.util.dt import utcnow
from homeassistant.components.alexa import (
config as alexa_config,
errors as alexa_errors,
entities as alexa_entities,
state_report as alexa_state_report,
)
from .const import (
CONF_ENTITY_CONFIG, CONF_FILTER, PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
RequireRelink
)
_LOGGER = logging.getLogger(__name__)
# Time to wait when entity preferences have changed before syncing it to
# the cloud.
SYNC_DELAY = 1
class AlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration."""
def __init__(self, hass, config, prefs, cloud):
"""Initialize the Alexa config."""
super().__init__(hass)
self._config = config
self._prefs = prefs
self._cloud = cloud
self._token = None
self._token_valid = None
self._cur_entity_prefs = prefs.alexa_entity_configs
self._alexa_sync_unsub = None
self._endpoint = None
prefs.async_listen_updates(self._async_prefs_updated)
hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated
)
@property
def enabled(self):
"""Return if Alexa is enabled."""
return self._prefs.alexa_enabled
@property
def supports_auth(self):
"""Return if config supports auth."""
return True
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.alexa_report_state
@property
def endpoint(self):
"""Endpoint for report state."""
if self._endpoint is None:
raise ValueError("No endpoint available. Fetch access token first")
return self._endpoint
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG) or {}
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)
entity_configs = self._prefs.alexa_entity_configs
entity_config = entity_configs.get(entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
async def async_get_access_token(self):
"""Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow():
return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud)
body = await resp.json()
if resp.status == 400:
if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'):
if self.should_report_state:
await self._prefs.async_update(alexa_report_state=False)
self.hass.components.persistent_notification.async_create(
"There was an error reporting state to Alexa ({}). "
"Please re-link your Alexa skill via the Alexa app to "
"continue using it.".format(body['reason']),
"Alexa state reporting disabled",
"cloud_alexa_report",
)
raise RequireRelink
raise alexa_errors.NoTokenAvailable
self._token = body['access_token']
self._endpoint = body['event_endpoint']
self._token_valid = utcnow() + timedelta(seconds=body['expires_in'])
return self._token
async def _async_prefs_updated(self, prefs):
"""Handle updated preferences."""
if self.should_report_state != self.is_reporting_states:
if self.should_report_state:
await self.async_enable_proactive_mode()
else:
await self.async_disable_proactive_mode()
# State reporting is reported as a property on entities.
# So when we change it, we need to sync all entities.
await self.async_sync_entities()
return
# If entity prefs are the same or we have filter in config.yaml,
# don't sync.
if (self._cur_entity_prefs is prefs.alexa_entity_configs or
not self._config[CONF_FILTER].empty_filter):
return
if self._alexa_sync_unsub:
self._alexa_sync_unsub()
self._alexa_sync_unsub = async_call_later(
self.hass, SYNC_DELAY, self._sync_prefs)
async def _sync_prefs(self, _now):
"""Sync the updated preferences to Alexa."""
self._alexa_sync_unsub = None
old_prefs = self._cur_entity_prefs
new_prefs = self._prefs.alexa_entity_configs
seen = set()
to_update = []
to_remove = []
for entity_id, info in old_prefs.items():
seen.add(entity_id)
old_expose = info.get(PREF_SHOULD_EXPOSE)
if entity_id in new_prefs:
new_expose = new_prefs[entity_id].get(PREF_SHOULD_EXPOSE)
else:
new_expose = None
if old_expose == new_expose:
continue
if new_expose:
to_update.append(entity_id)
else:
to_remove.append(entity_id)
# Now all the ones that are in new prefs but never were in old prefs
for entity_id, info in new_prefs.items():
if entity_id in seen:
continue
new_expose = info.get(PREF_SHOULD_EXPOSE)
if new_expose is None:
continue
# Only test if we should expose. It can never be a remove action,
# as it didn't exist in old prefs object.
if new_expose:
to_update.append(entity_id)
# We only set the prefs when update is successful, that way we will
# retry when next change comes in.
if await self._sync_helper(to_update, to_remove):
self._cur_entity_prefs = new_prefs
async def async_sync_entities(self):
"""Sync all entities to Alexa."""
# Remove any pending sync
if self._alexa_sync_unsub:
self._alexa_sync_unsub()
self._alexa_sync_unsub = None
to_update = []
to_remove = []
for entity in alexa_entities.async_get_entities(self.hass, self):
if self.should_expose(entity.entity_id):
to_update.append(entity.entity_id)
else:
to_remove.append(entity.entity_id)
return await self._sync_helper(to_update, to_remove)
async def _sync_helper(self, to_update, to_remove) -> bool:
"""Sync entities to Alexa.
Return boolean if it was successful.
"""
if not to_update and not to_remove:
return True
# Make sure it's valid.
await self.async_get_access_token()
tasks = []
if to_update:
tasks.append(alexa_state_report.async_send_add_or_update_message(
self.hass, self, to_update
))
if to_remove:
tasks.append(alexa_state_report.async_send_delete_message(
self.hass, self, to_remove
))
try:
with async_timeout.timeout(10):
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
return True
except asyncio.TimeoutError:
_LOGGER.warning("Timeout trying to sync entitites to Alexa")
return False
except aiohttp.ClientError as err:
_LOGGER.warning("Error trying to sync entities to Alexa: %s", err)
return False
async def _handle_entity_registry_updated(self, event):
"""Handle when entity registry updated."""
if not self.enabled or not self._cloud.is_logged_in:
return
action = event.data['action']
entity_id = event.data['entity_id']
to_update = []
to_remove = []
if action == 'create' and self.should_expose(entity_id):
to_update.append(entity_id)
elif action == 'remove' and self.should_expose(entity_id):
to_remove.append(entity_id)
try:
await self._sync_helper(to_update, to_remove)
except alexa_errors.NoTokenAvailable:
pass

View File

@@ -2,257 +2,27 @@
import asyncio
from pathlib import Path
from typing import Any, Dict
from datetime import timedelta
import logging
import aiohttp
import async_timeout
from hass_nabucasa import cloud_api
from hass_nabucasa.client import CloudClient as Interface
from homeassistant.core import callback
from homeassistant.components.alexa import (
config as alexa_config,
errors as alexa_errors,
smart_home as alexa_sh,
entities as alexa_entities,
state_report as alexa_state_report,
)
from homeassistant.components.google_assistant import (
helpers as ga_h, smart_home as ga)
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers.event import async_call_later
from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers import entity_registry
from homeassistant.util.aiohttp import MockRequest
from homeassistant.util.dt import utcnow
from homeassistant.components.alexa import (
smart_home as alexa_sh,
errors as alexa_errors,
)
from . import utils
from .const import (
CONF_ENTITY_CONFIG, CONF_FILTER, DOMAIN, DISPATCHER_REMOTE_UPDATE,
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA, RequireRelink)
from . import utils, alexa_config, google_config
from .const import DISPATCHER_REMOTE_UPDATE
from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__)
# Time to wait when entity preferences have changed before syncing it to
# the cloud.
SYNC_DELAY = 1
class AlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration."""
def __init__(self, hass, config, prefs, cloud):
"""Initialize the Alexa config."""
super().__init__(hass)
self._config = config
self._prefs = prefs
self._cloud = cloud
self._token = None
self._token_valid = None
self._cur_entity_prefs = prefs.alexa_entity_configs
self._alexa_sync_unsub = None
self._endpoint = None
prefs.async_listen_updates(self._async_prefs_updated)
hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated
)
@property
def enabled(self):
"""Return if Alexa is enabled."""
return self._prefs.alexa_enabled
@property
def supports_auth(self):
"""Return if config supports auth."""
return True
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.alexa_report_state
@property
def endpoint(self):
"""Endpoint for report state."""
if self._endpoint is None:
raise ValueError("No endpoint available. Fetch access token first")
return self._endpoint
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG, {})
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)
entity_configs = self._prefs.alexa_entity_configs
entity_config = entity_configs.get(entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
async def async_get_access_token(self):
"""Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow():
return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud)
body = await resp.json()
if resp.status == 400:
if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'):
raise RequireRelink
raise alexa_errors.NoTokenAvailable
self._token = body['access_token']
self._endpoint = body['event_endpoint']
self._token_valid = utcnow() + timedelta(seconds=body['expires_in'])
return self._token
async def _async_prefs_updated(self, prefs):
"""Handle updated preferences."""
if self.should_report_state != self.is_reporting_states:
if self.should_report_state:
await self.async_enable_proactive_mode()
else:
await self.async_disable_proactive_mode()
# If entity prefs are the same or we have filter in config.yaml,
# don't sync.
if (self._cur_entity_prefs is prefs.alexa_entity_configs or
not self._config[CONF_FILTER].empty_filter):
return
if self._alexa_sync_unsub:
self._alexa_sync_unsub()
self._alexa_sync_unsub = async_call_later(
self.hass, SYNC_DELAY, self._sync_prefs)
async def _sync_prefs(self, _now):
"""Sync the updated preferences to Alexa."""
self._alexa_sync_unsub = None
old_prefs = self._cur_entity_prefs
new_prefs = self._prefs.alexa_entity_configs
seen = set()
to_update = []
to_remove = []
for entity_id, info in old_prefs.items():
seen.add(entity_id)
old_expose = info.get(PREF_SHOULD_EXPOSE)
if entity_id in new_prefs:
new_expose = new_prefs[entity_id].get(PREF_SHOULD_EXPOSE)
else:
new_expose = None
if old_expose == new_expose:
continue
if new_expose:
to_update.append(entity_id)
else:
to_remove.append(entity_id)
# Now all the ones that are in new prefs but never were in old prefs
for entity_id, info in new_prefs.items():
if entity_id in seen:
continue
new_expose = info.get(PREF_SHOULD_EXPOSE)
if new_expose is None:
continue
# Only test if we should expose. It can never be a remove action,
# as it didn't exist in old prefs object.
if new_expose:
to_update.append(entity_id)
# We only set the prefs when update is successful, that way we will
# retry when next change comes in.
if await self._sync_helper(to_update, to_remove):
self._cur_entity_prefs = new_prefs
async def async_sync_entities(self):
"""Sync all entities to Alexa."""
to_update = []
to_remove = []
for entity in alexa_entities.async_get_entities(self.hass, self):
if self.should_expose(entity.entity_id):
to_update.append(entity.entity_id)
else:
to_remove.append(entity.entity_id)
return await self._sync_helper(to_update, to_remove)
async def _sync_helper(self, to_update, to_remove) -> bool:
"""Sync entities to Alexa.
Return boolean if it was successful.
"""
if not to_update and not to_remove:
return True
tasks = []
if to_update:
tasks.append(alexa_state_report.async_send_add_or_update_message(
self.hass, self, to_update
))
if to_remove:
tasks.append(alexa_state_report.async_send_delete_message(
self.hass, self, to_remove
))
try:
with async_timeout.timeout(10):
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
return True
except asyncio.TimeoutError:
_LOGGER.warning("Timeout trying to sync entitites to Alexa")
return False
except aiohttp.ClientError as err:
_LOGGER.warning("Error trying to sync entities to Alexa: %s", err)
return False
async def _handle_entity_registry_updated(self, event):
"""Handle when entity registry updated."""
if not self.enabled or not self._cloud.is_logged_in:
return
action = event.data['action']
entity_id = event.data['entity_id']
to_update = []
to_remove = []
if action == 'create' and self.should_expose(entity_id):
to_update.append(entity_id)
elif action == 'remove' and self.should_expose(entity_id):
to_remove.append(entity_id)
await self._sync_helper(to_update, to_remove)
class CloudClient(Interface):
@@ -260,13 +30,14 @@ class CloudClient(Interface):
def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences,
websession: aiohttp.ClientSession,
alexa_cfg: Dict[str, Any], google_config: Dict[str, Any]):
alexa_user_config: Dict[str, Any],
google_user_config: Dict[str, Any]):
"""Initialize client interface to Cloud."""
self._hass = hass
self._prefs = prefs
self._websession = websession
self.google_user_config = google_config
self.alexa_user_config = alexa_cfg
self.google_user_config = google_user_config
self.alexa_user_config = alexa_user_config
self._alexa_config = None
self._google_config = None
self.cloud = None
@@ -307,53 +78,22 @@ class CloudClient(Interface):
return self._prefs.remote_enabled
@property
def alexa_config(self) -> AlexaConfig:
def alexa_config(self) -> alexa_config.AlexaConfig:
"""Return Alexa config."""
if self._alexa_config is None:
self._alexa_config = AlexaConfig(
assert self.cloud is not None
self._alexa_config = alexa_config.AlexaConfig(
self._hass, self.alexa_user_config, self._prefs, self.cloud)
return self._alexa_config
@property
def google_config(self) -> ga_h.Config:
def google_config(self) -> google_config.CloudGoogleConfig:
"""Return Google config."""
if not self._google_config:
google_conf = self.google_user_config
def should_expose(entity):
"""If an entity should be exposed."""
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not google_conf['filter'].empty_filter:
return google_conf['filter'](entity.entity_id)
entity_configs = self.prefs.google_entity_configs
entity_config = entity_configs.get(entity.entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
def should_2fa(entity):
"""If an entity should be checked for 2FA."""
entity_configs = self.prefs.google_entity_configs
entity_config = entity_configs.get(entity.entity_id, {})
return not entity_config.get(
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
username = self._hass.data[DOMAIN].claims["cognito:username"]
self._google_config = ga_h.Config(
should_expose=should_expose,
should_2fa=should_2fa,
secure_devices_pin=self._prefs.google_secure_devices_pin,
entity_config=google_conf.get(CONF_ENTITY_CONFIG),
agent_user_id=username,
)
# Set it to the latest.
self._google_config.secure_devices_pin = \
self._prefs.google_secure_devices_pin
assert self.cloud is not None
self._google_config = google_config.CloudGoogleConfig(
self.google_user_config, self._prefs, self.cloud)
return self._google_config
@@ -361,8 +101,14 @@ class CloudClient(Interface):
"""Initialize the client."""
self.cloud = cloud
if self.alexa_config.should_report_state and self.cloud.is_logged_in:
if (not self.alexa_config.should_report_state or
not self.cloud.is_logged_in):
return
try:
await self.alexa_config.async_enable_proactive_mode()
except alexa_errors.NoTokenAvailable:
pass
async def cleanups(self) -> None:
"""Cleanup some stuff after logout."""

View File

@@ -0,0 +1,52 @@
"""Google config for Cloud."""
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.components.google_assistant.helpers import AbstractConfig
from .const import (
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, CONF_ENTITY_CONFIG,
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
class CloudGoogleConfig(AbstractConfig):
"""HA Cloud Configuration for Google Assistant."""
def __init__(self, config, prefs, cloud):
"""Initialize the Alexa config."""
self._config = config
self._prefs = prefs
self._cloud = cloud
@property
def agent_user_id(self):
"""Return Agent User Id to use for query responses."""
return self._cloud.claims["cognito:username"]
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG) or {}
@property
def secure_devices_pin(self):
"""Return entity config."""
return self._prefs.google_secure_devices_pin
def should_expose(self, state):
"""If an entity should be exposed."""
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config['filter'].empty_filter:
return self._config['filter'](state.entity_id)
entity_configs = self._prefs.google_entity_configs
entity_config = entity_configs.get(state.entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
def should_2fa(self, state):
"""If an entity should be checked for 2FA."""
entity_configs = self._prefs.google_entity_configs
entity_config = entity_configs.get(state.entity_id, {})
return not entity_config.get(
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)

View File

@@ -14,13 +14,16 @@ from homeassistant.components.http.data_validator import (
RequestDataValidator)
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api import const as ws_const
from homeassistant.components.alexa import entities as alexa_entities
from homeassistant.components.alexa import (
entities as alexa_entities,
errors as alexa_errors,
)
from homeassistant.components.google_assistant import helpers as google_helpers
from .const import (
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks,
InvalidTrustedProxies, PREF_ALEXA_REPORT_STATE)
InvalidTrustedProxies, PREF_ALEXA_REPORT_STATE, RequireRelink)
_LOGGER = logging.getLogger(__name__)
@@ -375,6 +378,24 @@ async def websocket_update_prefs(hass, connection, msg):
changes = dict(msg)
changes.pop('id')
changes.pop('type')
# If we turn alexa linking on, validate that we can fetch access token
if changes.get(PREF_ALEXA_REPORT_STATE):
try:
with async_timeout.timeout(10):
await cloud.client.alexa_config.async_get_access_token()
except asyncio.TimeoutError:
connection.send_error(msg['id'], 'alexa_timeout',
'Timeout validating Alexa access token.')
return
except (alexa_errors.NoTokenAvailable, RequireRelink):
connection.send_error(
msg['id'], 'alexa_relink',
'Please go to the Alexa app and re-link the Home Assistant '
'skill and then try to enable state reporting.'
)
return
await cloud.client.prefs.async_update(**changes)
connection.send_message(websocket_api.result_message(msg['id']))
@@ -575,7 +596,15 @@ async def alexa_sync(hass, connection, msg):
cloud = hass.data[DOMAIN]
with async_timeout.timeout(10):
success = await cloud.client.alexa_config.async_sync_entities()
try:
success = await cloud.client.alexa_config.async_sync_entities()
except alexa_errors.NoTokenAvailable:
connection.send_error(
msg['id'], 'alexa_relink',
'Please go to the Alexa app and re-link the Home Assistant '
'skill.'
)
return
if success:
connection.send_result(msg['id'])

View File

@@ -1,12 +1,11 @@
"""Http views to control the config manager."""
from homeassistant import config_entries, data_entry_flow
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES
from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
from homeassistant.generated import config_flows
from homeassistant.loader import async_get_config_flows
async def async_setup(hass):
@@ -61,7 +60,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
'state': entry.state,
'connection_class': entry.connection_class,
'supports_options': hasattr(
config_entries.HANDLERS[entry.domain],
config_entries.HANDLERS.get(entry.domain),
'async_get_options_flow'),
} for entry in hass.config_entries.async_entries()])
@@ -173,7 +172,8 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
async def get(self, request):
"""List available flow handlers."""
return self.json(config_flows.FLOWS)
hass = request.app['hass']
return self.json(await async_get_config_flows(hass))
class OptionManagerFlowIndexView(FlowManagerIndexView):

View File

@@ -6,27 +6,26 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE)
DEFAULT_PORT = 10102
AVAILABLE_MODES = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_DRY,
STATE_FAN_ONLY]
AVAILABLE_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_AUTO, HVAC_MODE_FAN_ONLY]
CM_TO_HA_STATE = {
'heat': STATE_HEAT,
'cool': STATE_COOL,
'auto': STATE_AUTO,
'dry': STATE_DRY,
'fan': STATE_FAN_ONLY,
'heat': HVAC_MODE_HEAT,
'cool': HVAC_MODE_COOL,
'auto': HVAC_MODE_AUTO,
'dry': HVAC_MODE_DRY,
'fan': HVAC_MODE_FAN_ONLY,
}
HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()}
@@ -72,7 +71,8 @@ class CoolmasterClimate(ClimateDevice):
"""Initialize the climate device."""
self._device = device
self._uid = device.uid
self._operation_list = supported_modes
self._hvac_modes = supported_modes
self._hvac_mode = None
self._target_temperature = None
self._current_temperature = None
self._current_fan_mode = None
@@ -89,7 +89,10 @@ class CoolmasterClimate(ClimateDevice):
self._on = status['is_on']
device_mode = status['mode']
self._current_operation = CM_TO_HA_STATE[device_mode]
if self._on:
self._hvac_mode = CM_TO_HA_STATE[device_mode]
else:
self._hvac_mode = HVAC_MODE_OFF
if status['unit'] == 'celsius':
self._unit = TEMP_CELSIUS
@@ -127,27 +130,22 @@ class CoolmasterClimate(ClimateDevice):
return self._target_temperature
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
def hvac_mode(self):
"""Return hvac target hvac state."""
return self._hvac_mode
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operation_list
return self._hvac_modes
@property
def is_on(self):
"""Return true if the device is on."""
return self._on
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_MODES
@@ -165,11 +163,16 @@ class CoolmasterClimate(ClimateDevice):
fan_mode)
self._device.set_fan_speed(fan_mode)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
_LOGGER.debug("Setting operation mode of %s to %s", self.unique_id,
operation_mode)
self._device.set_mode(HA_STATE_TO_CM[operation_mode])
hvac_mode)
if hvac_mode == HVAC_MODE_OFF:
self.turn_off()
else:
self._device.set_mode(HA_STATE_TO_CM[hvac_mode])
self.turn_on()
def turn_on(self):
"""Turn on."""

View File

@@ -8,10 +8,15 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
ATTR_MARKER_TYPE = 'marker_type'
ATTR_MARKER_LOW_LEVEL = 'marker_low_level'
ATTR_MARKER_HIGH_LEVEL = 'marker_high_level'
ATTR_PRINTER_NAME = 'printer_name'
ATTR_DEVICE_URI = 'device_uri'
ATTR_PRINTER_INFO = 'printer_info'
ATTR_PRINTER_IS_SHARED = 'printer_is_shared'
@@ -23,11 +28,14 @@ ATTR_PRINTER_TYPE = 'printer_type'
ATTR_PRINTER_URI_SUPPORTED = 'printer_uri_supported'
CONF_PRINTERS = 'printers'
CONF_IS_CUPS_SERVER = 'is_cups_server'
DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 631
DEFAULT_IS_CUPS_SERVER = True
ICON = 'mdi:printer'
ICON_PRINTER = 'mdi:printer'
ICON_MARKER = 'mdi:water'
SCAN_INTERVAL = timedelta(minutes=1)
@@ -39,6 +47,8 @@ PRINTER_STATES = {
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PRINTERS): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_IS_CUPS_SERVER,
default=DEFAULT_IS_CUPS_SERVER): cv.boolean,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
@@ -49,21 +59,44 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
printers = config.get(CONF_PRINTERS)
is_cups = config.get(CONF_IS_CUPS_SERVER)
try:
data = CupsData(host, port)
if is_cups:
data = CupsData(host, port, None)
data.update()
except RuntimeError:
_LOGGER.error("Unable to connect to CUPS server: %s:%s", host, port)
return False
if data.available is False:
_LOGGER.error("Unable to connect to CUPS server: %s:%s",
host, port)
raise PlatformNotReady()
dev = []
for printer in printers:
if printer not in data.printers:
_LOGGER.error("Printer is not present: %s", printer)
continue
dev.append(CupsSensor(data, printer))
if "marker-names" in data.attributes[printer]:
for marker in data.attributes[printer]["marker-names"]:
dev.append(MarkerSensor(data, printer, marker, True))
add_entities(dev, True)
return
data = CupsData(host, port, printers)
data.update()
if data.available is False:
_LOGGER.error("Unable to connect to IPP printer: %s:%s",
host, port)
raise PlatformNotReady()
dev = []
for printer in printers:
if printer in data.printers:
dev.append(CupsSensor(data, printer))
else:
_LOGGER.error("Printer is not present: %s", printer)
continue
dev.append(IPPSensor(data, printer))
if "marker-names" in data.attributes[printer]:
for marker in data.attributes[printer]["marker-names"]:
dev.append(MarkerSensor(data, printer, marker, False))
add_entities(dev, True)
@@ -76,6 +109,7 @@ class CupsSensor(Entity):
self.data = data
self._name = printer
self._printer = None
self._available = False
@property
def name(self):
@@ -85,56 +119,231 @@ class CupsSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
if self._printer is not None:
try:
return next(v for k, v in PRINTER_STATES.items()
if self._printer['printer-state'] == k)
except StopIteration:
return self._printer['printer-state']
if self._printer is None:
return None
key = self._printer['printer-state']
return PRINTER_STATES.get(key, key)
@property
def available(self):
"""Return True if entity is available."""
return self._available
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return ICON
return ICON_PRINTER
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
if self._printer is not None:
return {
ATTR_DEVICE_URI: self._printer['device-uri'],
ATTR_PRINTER_INFO: self._printer['printer-info'],
ATTR_PRINTER_IS_SHARED: self._printer['printer-is-shared'],
ATTR_PRINTER_LOCATION: self._printer['printer-location'],
ATTR_PRINTER_MODEL: self._printer['printer-make-and-model'],
ATTR_PRINTER_STATE_MESSAGE:
self._printer['printer-state-message'],
ATTR_PRINTER_STATE_REASON:
self._printer['printer-state-reasons'],
ATTR_PRINTER_TYPE: self._printer['printer-type'],
ATTR_PRINTER_URI_SUPPORTED:
self._printer['printer-uri-supported'],
}
if self._printer is None:
return None
return {
ATTR_DEVICE_URI: self._printer['device-uri'],
ATTR_PRINTER_INFO: self._printer['printer-info'],
ATTR_PRINTER_IS_SHARED: self._printer['printer-is-shared'],
ATTR_PRINTER_LOCATION: self._printer['printer-location'],
ATTR_PRINTER_MODEL: self._printer['printer-make-and-model'],
ATTR_PRINTER_STATE_MESSAGE:
self._printer['printer-state-message'],
ATTR_PRINTER_STATE_REASON:
self._printer['printer-state-reasons'],
ATTR_PRINTER_TYPE: self._printer['printer-type'],
ATTR_PRINTER_URI_SUPPORTED:
self._printer['printer-uri-supported'],
}
def update(self):
"""Get the latest data and updates the states."""
self.data.update()
self._printer = self.data.printers.get(self._name)
self._available = self.data.available
class IPPSensor(Entity):
"""Implementation of the IPPSensor.
This sensor represents the status of the printer.
"""
def __init__(self, data, name):
"""Initialize the sensor."""
self.data = data
self._name = name
self._attributes = None
self._available = False
@property
def name(self):
"""Return the name of the sensor."""
return self._attributes['printer-make-and-model']
@property
def icon(self):
"""Return the icon to use in the frontend."""
return ICON_PRINTER
@property
def available(self):
"""Return True if entity is available."""
return self._available
@property
def state(self):
"""Return the state of the sensor."""
if self._attributes is None:
return None
key = self._attributes['printer-state']
return PRINTER_STATES.get(key, key)
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
if self._attributes is None:
return None
state_attributes = {}
if 'printer-info' in self._attributes:
state_attributes[ATTR_PRINTER_INFO] = \
self._attributes['printer-info']
if 'printer-location' in self._attributes:
state_attributes[ATTR_PRINTER_LOCATION] = \
self._attributes['printer-location']
if 'printer-state-message' in self._attributes:
state_attributes[ATTR_PRINTER_STATE_MESSAGE] = \
self._attributes['printer-state-message']
if 'printer-state-reasons' in self._attributes:
state_attributes[ATTR_PRINTER_STATE_REASON] = \
self._attributes['printer-state-reasons']
if 'printer-uri-supported' in self._attributes:
state_attributes[ATTR_PRINTER_URI_SUPPORTED] = \
self._attributes['printer-uri-supported']
return state_attributes
def update(self):
"""Fetch new state data for the sensor."""
self.data.update()
self._attributes = self.data.attributes.get(self._name)
self._available = self.data.available
class MarkerSensor(Entity):
"""Implementation of the MarkerSensor.
This sensor represents the percentage of ink or toner.
"""
def __init__(self, data, printer, name, is_cups):
"""Initialize the sensor."""
self.data = data
self._name = name
self._printer = printer
self._index = data.attributes[printer]['marker-names'].index(name)
self._is_cups = is_cups
self._attributes = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend."""
return ICON_MARKER
@property
def state(self):
"""Return the state of the sensor."""
if self._attributes is None:
return None
return self._attributes[self._printer]['marker-levels'][self._index]
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return "%"
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
if self._attributes is None:
return None
high_level = self._attributes[self._printer]['marker-high-levels']
if isinstance(high_level, list):
high_level = high_level[self._index]
low_level = self._attributes[self._printer]['marker-low-levels']
if isinstance(low_level, list):
low_level = low_level[self._index]
marker_types = self._attributes[self._printer]['marker-types']
if isinstance(marker_types, list):
marker_types = marker_types[self._index]
if self._is_cups:
printer_name = self._printer
else:
printer_name = \
self._attributes[self._printer]['printer-make-and-model']
return {
ATTR_MARKER_HIGH_LEVEL: high_level,
ATTR_MARKER_LOW_LEVEL: low_level,
ATTR_MARKER_TYPE: marker_types,
ATTR_PRINTER_NAME: printer_name
}
def update(self):
"""Update the state of the sensor."""
# Data fetching is done by CupsSensor/IPPSensor
self._attributes = self.data.attributes
# pylint: disable=no-name-in-module
class CupsData:
"""Get the latest data from CUPS and update the state."""
def __init__(self, host, port):
def __init__(self, host, port, ipp_printers):
"""Initialize the data object."""
self._host = host
self._port = port
self._ipp_printers = ipp_printers
self.is_cups = (ipp_printers is None)
self.printers = None
self.attributes = {}
self.available = False
def update(self):
"""Get the latest data from CUPS."""
cups = importlib.import_module('cups')
conn = cups.Connection(host=self._host, port=self._port)
self.printers = conn.getPrinters()
try:
conn = cups.Connection(host=self._host, port=self._port)
if self.is_cups:
self.printers = conn.getPrinters()
for printer in self.printers:
self.attributes[printer] = conn.getPrinterAttributes(
name=printer)
else:
for ipp_printer in self._ipp_printers:
self.attributes[ipp_printer] = conn.getPrinterAttributes(
uri="ipp://{}:{}/{}"
.format(self._host, self._port, ipp_printer))
self.available = True
except RuntimeError:
self.available = False

View File

@@ -5,14 +5,17 @@ import re
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_AWAY_MODE, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
ATTR_OPERATION_MODE, ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS)
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE,
HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
PRESET_AWAY, PRESET_HOME,
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
ATTR_HVAC_MODE, ATTR_SWING_MODE,
ATTR_PRESET_MODE)
import homeassistant.helpers.config_validation as cv
from . import DOMAIN as DAIKIN_DOMAIN
@@ -27,26 +30,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
HA_STATE_TO_DAIKIN = {
STATE_FAN_ONLY: 'fan',
STATE_DRY: 'dry',
STATE_COOL: 'cool',
STATE_HEAT: 'hot',
STATE_AUTO: 'auto',
STATE_OFF: 'off',
HVAC_MODE_FAN_ONLY: 'fan',
HVAC_MODE_DRY: 'dry',
HVAC_MODE_COOL: 'cool',
HVAC_MODE_HEAT: 'hot',
HVAC_MODE_HEAT_COOL: 'auto',
HVAC_MODE_OFF: 'off',
}
DAIKIN_TO_HA_STATE = {
'fan': STATE_FAN_ONLY,
'dry': STATE_DRY,
'cool': STATE_COOL,
'hot': STATE_HEAT,
'auto': STATE_AUTO,
'off': STATE_OFF,
'fan': HVAC_MODE_FAN_ONLY,
'dry': HVAC_MODE_DRY,
'cool': HVAC_MODE_COOL,
'hot': HVAC_MODE_HEAT,
'auto': HVAC_MODE_HEAT_COOL,
'off': HVAC_MODE_OFF,
}
HA_PRESET_TO_DAIKIN = {
PRESET_AWAY: 'on',
PRESET_HOME: 'off'
}
HA_ATTR_TO_DAIKIN = {
ATTR_AWAY_MODE: 'en_hol',
ATTR_OPERATION_MODE: 'mode',
ATTR_PRESET_MODE: 'en_hol',
ATTR_HVAC_MODE: 'mode',
ATTR_FAN_MODE: 'f_rate',
ATTR_SWING_MODE: 'f_dir',
ATTR_INSIDE_TEMPERATURE: 'htemp',
@@ -80,7 +88,7 @@ class DaikinClimate(ClimateDevice):
self._api = api
self._list = {
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_HVAC_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_FAN_MODE: self._api.device.fan_rate,
ATTR_SWING_MODE: list(
map(
@@ -90,12 +98,10 @@ class DaikinClimate(ClimateDevice):
),
}
self._supported_features = (SUPPORT_ON_OFF
| SUPPORT_OPERATION_MODE
| SUPPORT_TARGET_TEMPERATURE)
self._supported_features = SUPPORT_TARGET_TEMPERATURE
if self._api.device.support_away_mode:
self._supported_features |= SUPPORT_AWAY_MODE
self._supported_features |= SUPPORT_PRESET_MODE
if self._api.device.support_fan_rate:
self._supported_features |= SUPPORT_FAN_MODE
@@ -127,7 +133,7 @@ class DaikinClimate(ClimateDevice):
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_SWING_MODE:
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_OPERATION_MODE:
elif key == ATTR_HVAC_MODE:
# Daikin can return also internal states auto-1 or auto-7
# and we need to translate them as AUTO
daikin_mode = re.sub(
@@ -135,6 +141,10 @@ class DaikinClimate(ClimateDevice):
self._api.device.represent(daikin_attr)[1])
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
value = ha_mode
elif key == ATTR_PRESET_MODE:
away = (self._api.device.represent(daikin_attr)[1]
!= HA_STATE_TO_DAIKIN[HVAC_MODE_OFF])
value = PRESET_AWAY if away else PRESET_HOME
if value is None:
_LOGGER.error("Invalid value requested for key %s", key)
@@ -154,15 +164,17 @@ class DaikinClimate(ClimateDevice):
values = {}
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
ATTR_OPERATION_MODE]:
ATTR_HVAC_MODE, ATTR_PRESET_MODE]:
value = settings.get(attr)
if value is None:
continue
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
if daikin_attr is not None:
if attr == ATTR_OPERATION_MODE:
if attr == ATTR_HVAC_MODE:
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
elif attr == ATTR_PRESET_MODE:
values[daikin_attr] = HA_PRESET_TO_DAIKIN[value]
elif value in self._list[attr]:
values[daikin_attr] = value.lower()
else:
@@ -218,21 +230,21 @@ class DaikinClimate(ClimateDevice):
await self._set(kwargs)
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self.get(ATTR_OPERATION_MODE)
return self.get(ATTR_HVAC_MODE)
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._list.get(ATTR_OPERATION_MODE)
return self._list.get(ATTR_HVAC_MODE)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set HVAC mode."""
await self._set({ATTR_OPERATION_MODE: operation_mode})
await self._set({ATTR_HVAC_MODE: hvac_mode})
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.get(ATTR_FAN_MODE)
@@ -241,12 +253,12 @@ class DaikinClimate(ClimateDevice):
await self._set({ATTR_FAN_MODE: fan_mode})
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return self._list.get(ATTR_FAN_MODE)
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the fan setting."""
return self.get(ATTR_SWING_MODE)
@@ -255,10 +267,24 @@ class DaikinClimate(ClimateDevice):
await self._set({ATTR_SWING_MODE: swing_mode})
@property
def swing_list(self):
def swing_modes(self):
"""List of available swing modes."""
return self._list.get(ATTR_SWING_MODE)
@property
def preset_mode(self):
"""Return the fan setting."""
return self.get(ATTR_PRESET_MODE)
async def async_set_preset_mode(self, preset_mode):
"""Set new target temperature."""
await self._set({ATTR_PRESET_MODE: preset_mode})
@property
def preset_modes(self):
"""List of available swing modes."""
return list(HA_PRESET_TO_DAIKIN)
async def async_update(self):
"""Retrieve latest state."""
await self._api.async_update()
@@ -267,36 +293,3 @@ class DaikinClimate(ClimateDevice):
def device_info(self):
"""Return a device description for device registry."""
return self._api.device_info
@property
def is_on(self):
"""Return true if on."""
return self._api.device.represent(
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
async def async_turn_on(self):
"""Turn device on."""
await self._api.device.set({})
async def async_turn_off(self):
"""Turn device off."""
await self._api.device.set({
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]:
HA_STATE_TO_DAIKIN[STATE_OFF]
})
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._api.device.represent(
HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '1'})
async def async_turn_away_mode_off(self):
"""Turn away mode off."""
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '0'})

View File

@@ -2,7 +2,9 @@
"config": {
"abort": {
"already_configured": "Bridge ist bereits konfiguriert",
"already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt.",
"no_bridges": "Keine deCON-Bridges entdeckt",
"not_deconz_bridge": "Keine deCONZ Bridge entdeckt",
"one_instance_only": "Komponente unterst\u00fctzt nur eine deCONZ-Instanz",
"updated_instance": "deCONZ-Instanz mit neuer Host-Adresse aktualisiert"
},

View File

@@ -3,7 +3,7 @@ from pydeconz.sensor import Thermostat
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
from homeassistant.core import callback
@@ -13,6 +13,8 @@ from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@@ -51,32 +53,28 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class DeconzThermostat(DeconzDevice, ClimateDevice):
"""Representation of a deCONZ thermostat."""
def __init__(self, device, gateway):
"""Set up thermostat device."""
super().__init__(device, gateway)
self._features = SUPPORT_ON_OFF
self._features |= SUPPORT_TARGET_TEMPERATURE
@property
def supported_features(self):
"""Return the list of supported features."""
return self._features
return SUPPORT_TARGET_TEMPERATURE
@property
def is_on(self):
"""Return true if on."""
return self._device.state_on
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
async def async_turn_on(self):
"""Turn on switch."""
data = {'mode': 'auto'}
await self._device.async_set_config(data)
Need to be one of HVAC_MODE_*.
"""
if self._device.on:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
async def async_turn_off(self):
"""Turn off switch."""
data = {'mode': 'off'}
await self._device.async_set_config(data)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def current_temperature(self):
@@ -97,6 +95,15 @@ class DeconzThermostat(DeconzDevice, ClimateDevice):
await self._device.async_set_config(data)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
data = {'mode': 'auto'}
elif hvac_mode == HVAC_MODE_OFF:
data = {'mode': 'off'}
await self._device.async_set_config(data)
@property
def temperature_unit(self):
"""Return the unit of measurement."""

View File

@@ -1,85 +1,138 @@
"""Demo platform that offers a fake climate device."""
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SUPPORT_AUX_HEAT,
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE,
SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE, HVAC_MODE_AUTO)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
SUPPORT_FLAGS = 0
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Demo climate devices."""
add_entities([
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
None, None, None, None, 'heat', None, None,
None, True),
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
67, 54, 'Off', 'cool', False, None, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21, None)
DemoClimate(
name='HeatPump',
target_temperature=68,
unit_of_measurement=TEMP_FAHRENHEIT,
preset=None,
current_temperature=77,
fan_mode=None,
target_humidity=None,
current_humidity=None,
swing_mode=None,
hvac_mode=HVAC_MODE_HEAT,
hvac_action=CURRENT_HVAC_HEAT,
aux=None,
target_temp_high=None,
target_temp_low=None,
hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF]
),
DemoClimate(
name='Hvac',
target_temperature=21,
unit_of_measurement=TEMP_CELSIUS,
preset=None,
current_temperature=22,
fan_mode='On High',
target_humidity=67,
current_humidity=54,
swing_mode='Off',
hvac_mode=HVAC_MODE_COOL,
hvac_action=CURRENT_HVAC_COOL,
aux=False,
target_temp_high=None,
target_temp_low=None,
hvac_modes=[mode for mode in HVAC_MODES
if mode != HVAC_MODE_HEAT_COOL]
),
DemoClimate(
name='Ecobee',
target_temperature=None,
unit_of_measurement=TEMP_CELSIUS,
preset='home',
preset_modes=['home', 'eco'],
current_temperature=23,
fan_mode='Auto Low',
target_humidity=None,
current_humidity=None,
swing_mode='Auto',
hvac_mode=HVAC_MODE_HEAT_COOL,
hvac_action=None,
aux=None,
target_temp_high=24,
target_temp_low=21,
hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT])
])
class DemoClimate(ClimateDevice):
"""Representation of a demo climate device."""
def __init__(self, name, target_temperature, unit_of_measurement,
away, hold, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
current_operation, aux, target_temp_high, target_temp_low,
is_on):
def __init__(
self,
name,
target_temperature,
unit_of_measurement,
preset,
current_temperature,
fan_mode,
target_humidity,
current_humidity,
swing_mode,
hvac_mode,
hvac_action,
aux,
target_temp_high,
target_temp_low,
hvac_modes,
preset_modes=None,
):
"""Initialize the climate device."""
self._name = name
self._support_flags = SUPPORT_FLAGS
if target_temperature is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE
if away is not None:
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
if hold is not None:
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
if current_fan_mode is not None:
if preset is not None:
self._support_flags = self._support_flags | SUPPORT_PRESET_MODE
if fan_mode is not None:
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
if target_humidity is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_HUMIDITY
if current_swing_mode is not None:
if swing_mode is not None:
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
if current_operation is not None:
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
if hvac_action is not None:
self._support_flags = self._support_flags
if aux is not None:
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
if target_temp_high is not None:
if (HVAC_MODE_HEAT_COOL in hvac_modes or
HVAC_MODE_AUTO in hvac_modes):
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
if target_temp_low is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW
if is_on is not None:
self._support_flags = self._support_flags | SUPPORT_ON_OFF
self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE
self._target_temperature = target_temperature
self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement
self._away = away
self._hold = hold
self._preset = preset
self._preset_modes = preset_modes
self._current_temperature = current_temperature
self._current_humidity = current_humidity
self._current_fan_mode = current_fan_mode
self._current_operation = current_operation
self._current_fan_mode = fan_mode
self._hvac_action = hvac_action
self._hvac_mode = hvac_mode
self._aux = aux
self._current_swing_mode = current_swing_mode
self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
self._operation_list = ['heat', 'cool', 'auto', 'off']
self._swing_list = ['Auto', '1', '2', '3', 'Off']
self._current_swing_mode = swing_mode
self._fan_modes = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
self._hvac_modes = hvac_modes
self._swing_modes = ['Auto', '1', '2', '3', 'Off']
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
self._on = is_on
@property
def supported_features(self):
@@ -132,46 +185,56 @@ class DemoClimate(ClimateDevice):
return self._target_humidity
@property
def current_operation(self):
def hvac_action(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
return self._hvac_action
@property
def operation_list(self):
def hvac_mode(self):
"""Return hvac target hvac state."""
return self._hvac_mode
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operation_list
return self._hvac_modes
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
def preset_mode(self):
"""Return preset mode."""
return self._preset
@property
def current_hold_mode(self):
"""Return hold mode setting."""
return self._hold
def preset_modes(self):
"""Return preset modes."""
return self._preset_modes
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heat is on."""
return self._aux
@property
def is_on(self):
"""Return true if the device is on."""
return self._on
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._fan_list
return self._fan_modes
def set_temperature(self, **kwargs):
@property
def swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_modes(self):
"""List of available swing modes."""
return self._swing_modes
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
@@ -179,69 +242,39 @@ class DemoClimate(ClimateDevice):
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_humidity(self, humidity):
async def async_set_humidity(self, humidity):
"""Set new humidity level."""
self._target_humidity = humidity
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_swing_mode(self, swing_mode):
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
self._current_swing_mode = swing_mode
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
self._current_fan_mode = fan_mode
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
self._current_operation = operation_mode
self.schedule_update_ha_state()
self._hvac_mode = hvac_mode
self.async_write_ha_state()
@property
def current_swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.schedule_update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.schedule_update_ha_state()
def set_hold_mode(self, hold_mode):
"""Update hold_mode on."""
self._hold = hold_mode
self.schedule_update_ha_state()
async def async_set_preset_mode(self, preset_mode):
"""Update preset_mode on."""
self._preset = preset_mode
self.async_write_ha_state()
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
self._aux = True
self.schedule_update_ha_state()
self.async_write_ha_state()
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
self._aux = False
self.schedule_update_ha_state()
def turn_on(self):
"""Turn on."""
self._on = True
self.schedule_update_ha_state()
def turn_off(self):
"""Turn off."""
self._on = False
self.schedule_update_ha_state()
self.async_write_ha_state()

View File

@@ -13,6 +13,8 @@ _LOGGER = logging.getLogger(__name__)
CONF_DESTINATION = 'to'
CONF_START = 'from'
CONF_OFFSET = 'offset'
DEFAULT_OFFSET = timedelta(minutes=0)
CONF_ONLY_DIRECT = 'only_direct'
DEFAULT_ONLY_DIRECT = False
@@ -23,6 +25,7 @@ SCAN_INTERVAL = timedelta(minutes=2)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DESTINATION): cv.string,
vol.Required(CONF_START): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.time_period,
vol.Optional(CONF_ONLY_DIRECT, default=DEFAULT_ONLY_DIRECT): cv.boolean,
})
@@ -31,18 +34,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Deutsche Bahn Sensor."""
start = config.get(CONF_START)
destination = config.get(CONF_DESTINATION)
offset = config.get(CONF_OFFSET)
only_direct = config.get(CONF_ONLY_DIRECT)
add_entities([DeutscheBahnSensor(start, destination, only_direct)], True)
add_entities([DeutscheBahnSensor(start, destination,
offset, only_direct)], True)
class DeutscheBahnSensor(Entity):
"""Implementation of a Deutsche Bahn sensor."""
def __init__(self, start, goal, only_direct):
def __init__(self, start, goal, offset, only_direct):
"""Initialize the sensor."""
self._name = '{} to {}'.format(start, goal)
self.data = SchieneData(start, goal, only_direct)
self.data = SchieneData(start, goal, offset, only_direct)
self._state = None
@property
@@ -81,12 +86,13 @@ class DeutscheBahnSensor(Entity):
class SchieneData:
"""Pull data from the bahn.de web page."""
def __init__(self, start, goal, only_direct):
def __init__(self, start, goal, offset, only_direct):
"""Initialize the sensor."""
import schiene
self.start = start
self.goal = goal
self.offset = offset
self.only_direct = only_direct
self.schiene = schiene.Schiene()
self.connections = [{}]
@@ -94,7 +100,8 @@ class SchieneData:
def update(self):
"""Update the connection data."""
self.connections = self.schiene.connections(
self.start, self.goal, dt_util.as_local(dt_util.utcnow()),
self.start, self.goal,
dt_util.as_local(dt_util.utcnow()+self.offset),
self.only_direct)
if not self.connections:

View File

@@ -37,7 +37,7 @@ async def async_unload_entry(hass, entry):
return await hass.data[DOMAIN].async_unload_entry(entry)
class DeviceTrackerEntity(Entity):
class BaseTrackerEntity(Entity):
"""Represent a tracked device."""
@property
@@ -48,6 +48,27 @@ class DeviceTrackerEntity(Entity):
"""
return None
@property
def source_type(self):
"""Return the source type, eg gps or router, of the device."""
raise NotImplementedError
@property
def state_attributes(self):
"""Return the device state attributes."""
attr = {
ATTR_SOURCE_TYPE: self.source_type
}
if self.battery_level:
attr[ATTR_BATTERY_LEVEL] = self.battery_level
return attr
class TrackerEntity(BaseTrackerEntity):
"""Represent a tracked device."""
@property
def location_accuracy(self):
"""Return the location accuracy of the device.
@@ -71,11 +92,6 @@ class DeviceTrackerEntity(Entity):
"""Return longitude value of the device."""
return NotImplementedError
@property
def source_type(self):
"""Return the source type, eg gps or router, of the device."""
raise NotImplementedError
@property
def state(self):
"""Return the state of the device."""
@@ -99,16 +115,27 @@ class DeviceTrackerEntity(Entity):
@property
def state_attributes(self):
"""Return the device state attributes."""
attr = {
ATTR_SOURCE_TYPE: self.source_type
}
attr = {}
attr.update(super().state_attributes)
if self.latitude is not None:
attr[ATTR_LATITUDE] = self.latitude
attr[ATTR_LONGITUDE] = self.longitude
attr[ATTR_GPS_ACCURACY] = self.location_accuracy
if self.battery_level:
attr[ATTR_BATTERY_LEVEL] = self.battery_level
return attr
class ScannerEntity(BaseTrackerEntity):
"""Represent a tracked device that is on a scanned network."""
@property
def state(self):
"""Return the state of the device."""
if self.is_connected:
return STATE_HOME
return STATE_NOT_HOME
@property
def is_connected(self):
"""Return true if the device is connected to the network."""
raise NotImplementedError

View File

@@ -14,6 +14,7 @@ from homeassistant.components.zone import async_active_zone
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_registry import async_get_registry
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import GPSType, HomeAssistantType
from homeassistant import util
@@ -115,6 +116,7 @@ class DeviceTracker:
This method is a coroutine.
"""
registry = await async_get_registry(self.hass)
if mac is None and dev_id is None:
raise HomeAssistantError('Neither mac or device id passed in')
if mac is not None:
@@ -134,6 +136,14 @@ class DeviceTracker:
await device.async_update_ha_state()
return
# Guard from calling see on entity registry entities.
entity_id = ENTITY_ID_FORMAT.format(dev_id)
if registry.async_is_registered(entity_id):
LOGGER.error(
"The see service is not supported for this entity %s",
entity_id)
return
# If no device can be found, create it
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
device = Device(

View File

@@ -3,7 +3,7 @@
"name": "Discord",
"documentation": "https://www.home-assistant.io/components/discord",
"requirements": [
"discord.py==1.1.1"
"discord.py==1.2.2"
],
"dependencies": [],
"codeowners": []

View File

@@ -3,7 +3,7 @@
"name": "Dlna dmr",
"documentation": "https://www.home-assistant.io/components/dlna_dmr",
"requirements": [
"async-upnp-client==0.14.7"
"async-upnp-client==0.14.10"
],
"dependencies": [],
"codeowners": []

View File

@@ -79,6 +79,11 @@ def setup(hass, config):
"downloading '%s' failed, status_code=%d",
url,
req.status_code)
hass.bus.fire(
"{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), {
'url': url,
'filename': filename
})
else:
if filename is None and \

View File

@@ -1,22 +1,24 @@
"""Support for Dyson Pure Hot+Cool link fan."""
import logging
from libpurecool.const import HeatMode, HeatState, FocusMode, HeatTarget
from libpurecool.dyson_pure_state import DysonPureHotCoolState
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL,
HVAC_MODE_HEAT, SUPPORT_FAN_MODE, FAN_FOCUS,
FAN_DIFFUSE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DYSON_DEVICES
_LOGGER = logging.getLogger(__name__)
STATE_DIFFUSE = "Diffuse Mode"
STATE_FOCUS = "Focus Mode"
FAN_LIST = [STATE_FOCUS, STATE_DIFFUSE]
OPERATION_LIST = [STATE_HEAT, STATE_COOL]
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
| SUPPORT_OPERATION_MODE)
SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE]
SUPPORT_HVAG = [HVAC_MODE_COOL, HVAC_MODE_HEAT]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -24,7 +26,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
# Get Dyson Devices from parent component.
add_devices(
[DysonPureHotCoolLinkDevice(device)
@@ -43,17 +44,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(self._device.add_message_listener,
self.on_message)
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Call when new messages received from the climate."""
from libpurecool.dyson_pure_state import DysonPureHotCoolState
if not isinstance(message, DysonPureHotCoolState):
return
if isinstance(message, DysonPureHotCoolState):
_LOGGER.debug("Message received for climate device %s : %s",
self.name, message)
self.schedule_update_ha_state()
_LOGGER.debug(
"Message received for climate device %s : %s", self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self):
@@ -101,32 +102,46 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
from libpurecool.const import HeatMode, HeatState
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
return HVAC_MODE_HEAT
return HVAC_MODE_COOL
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAG
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
return STATE_HEAT
return STATE_IDLE
return STATE_COOL
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
return CURRENT_HVAC_COOL
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
from libpurecool.const import FocusMode
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
return STATE_FOCUS
return STATE_DIFFUSE
return FAN_FOCUS
return FAN_DIFFUSE
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_LIST
return SUPPORT_FAN
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -138,7 +153,6 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
from libpurecool.const import HeatTarget, HeatMode
self._device.set_configuration(
heat_target=HeatTarget.celsius(target_temp),
heat_mode=HeatMode.HEAT_ON)
@@ -146,19 +160,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
from libpurecool.const import FocusMode
if fan_mode == STATE_FOCUS:
if fan_mode == FAN_FOCUS:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
elif fan_mode == STATE_DIFFUSE:
elif fan_mode == FAN_DIFFUSE:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_OFF)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
from libpurecool.const import HeatMode
if operation_mode == STATE_HEAT:
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, hvac_mode)
if hvac_mode == HVAC_MODE_HEAT:
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
elif operation_mode == STATE_COOL:
elif hvac_mode == HVAC_MODE_COOL:
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
@property

View File

@@ -59,6 +59,6 @@ set_speed:
entity_id:
description: Name(s) of the entities to set the speed for
example: 'fan.living_room'
timer:
dyson_speed:
description: Speed
example: 1

View File

@@ -59,7 +59,7 @@ def setup(hass, config):
conf.get(CONF_HOST), conf.get(CONF_PORT))
try:
_LOGGER.debug("Ebusd component setup started")
_LOGGER.debug("Ebusd integration setup started")
import ebusdpy
ebusdpy.init(server_address)
hass.data[DOMAIN] = EbusdData(server_address, circuit)
@@ -74,7 +74,7 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_EBUSD_WRITE, hass.data[DOMAIN].write)
_LOGGER.debug("Ebusd component setup completed")
_LOGGER.debug("Ebusd integration setup completed")
return True
except (socket.timeout, socket.error):
return False

View File

@@ -1,19 +1,21 @@
"""Support for Ecobee Thermostats."""
import collections
import logging
from typing import Optional
import voluptuous as vol
from homeassistant.components import ecobee
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE,
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_LOW)
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_FAN_MODE,
PRESET_AWAY, FAN_AUTO, FAN_ON, CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT,
CURRENT_HVAC_COOL, SUPPORT_PRESET_MODE
)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
_CONFIGURING = {}
@@ -23,9 +25,34 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
PRESET_TEMPERATURE = 'temp'
PRESET_VACATION = 'vacation'
PRESET_AUX_HEAT_ONLY = 'aux_heat_only'
PRESET_HOLD_NEXT_TRANSITION = 'next_transition'
PRESET_HOLD_INDEFINITE = 'indefinite'
AWAY_MODE = 'awayMode'
PRESET_HOME = 'home'
PRESET_SLEEP = 'sleep'
# Order matters, because for reverse mapping we don't want to map HEAT to AUX
ECOBEE_HVAC_TO_HASS = collections.OrderedDict([
('heat', HVAC_MODE_HEAT),
('cool', HVAC_MODE_COOL),
('auto', HVAC_MODE_AUTO),
('off', HVAC_MODE_OFF),
('auxHeatOnly', HVAC_MODE_HEAT),
])
PRESET_TO_ECOBEE_HOLD = {
PRESET_HOLD_NEXT_TRANSITION: 'nextTransition',
PRESET_HOLD_INDEFINITE: 'indefinite',
}
PRESET_MODES = [
PRESET_AWAY,
PRESET_HOME,
PRESET_SLEEP
]
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
@@ -40,11 +67,9 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE |
SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_entities, discovery_info=None):
@@ -114,9 +139,10 @@ class Thermostat(ClimateDevice):
self.hold_temp = hold_temp
self.vacation = None
self._climate_list = self.climate_list
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self._fan_list = ['auto', 'on']
self._operation_list = [
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF
]
self._fan_modes = [FAN_AUTO, FAN_ON]
self.update_without_throttle = False
def update(self):
@@ -153,25 +179,25 @@ class Thermostat(ClimateDevice):
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.thermostat['runtime']['desiredHeat'] / 10.0
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return None
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
return self.thermostat['runtime']['desiredHeat'] / 10.0
if self.current_operation == STATE_COOL:
if self.hvac_mode == HVAC_MODE_COOL:
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@@ -180,70 +206,63 @@ class Thermostat(ClimateDevice):
"""Return the current fan status."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
return STATE_OFF
return HVAC_MODE_OFF
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.thermostat['runtime']['desiredFanMode']
@property
def current_hold_mode(self):
"""Return current hold mode."""
mode = self._current_hold_mode
return None if mode == AWAY_MODE else mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the available fan modes."""
return self._fan_list
return self._fan_modes
@property
def _current_hold_mode(self):
def preset_mode(self):
"""Return current preset mode."""
events = self.thermostat['events']
for event in events:
if event['running']:
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
# A permanent hold from away climate
return AWAY_MODE
if event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
if event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
if event['type'] == 'vacation':
self.vacation = event['name']
return VACATION_HOLD
if not event['running']:
continue
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return PRESET_AWAY
# A permanent hold from away climate
return PRESET_AWAY
if event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
# Any hold not based on a climate is a temp hold
return PRESET_TEMPERATURE
if event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
if event['type'] == 'vacation':
self.vacation = event['name']
return PRESET_VACATION
if self.is_aux_heat:
return PRESET_AUX_HEAT_ONLY
return None
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation."""
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
return self.operation_mode
return ECOBEE_HVAC_TO_HASS[self.thermostat['settings']['hvacMode']]
@property
def operation_list(self):
def hvac_modes(self):
"""Return the operation modes list."""
return self._operation_list
@property
def operation_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self.thermostat['settings']['hvacMode']
@property
def mode(self):
def climate_mode(self):
"""Return current mode, as the user-visible name."""
cur = self.thermostat['program']['currentClimateRef']
climates = self.thermostat['program']['climates']
@@ -251,80 +270,79 @@ class Thermostat(ClimateDevice):
return current[0]['name']
@property
def fan_min_on_time(self):
"""Return current fan minimum on time."""
return self.thermostat['settings']['fanMinOnTime']
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self.thermostat['runtime']['actualHumidity']
@property
def hvac_action(self):
"""Return current HVAC action."""
status = self.thermostat['equipmentStatus']
operation = None
if status == '':
operation = CURRENT_HVAC_OFF
elif 'Cool' in status:
operation = CURRENT_HVAC_COOL
elif 'auxHeat' in status or 'heatPump' in status:
operation = CURRENT_HVAC_HEAT
return operation
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
# Move these to Thermostat Device and make them global
status = self.thermostat['equipmentStatus']
operation = None
if status == '':
operation = STATE_IDLE
elif 'Cool' in status:
operation = STATE_COOL
elif 'auxHeat' in status:
operation = STATE_HEAT
elif 'heatPump' in status:
operation = STATE_HEAT
else:
operation = status
return {
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
"fan": self.fan,
"climate_mode": self.mode,
"operation": operation,
"climate_mode": self.climate_mode,
"equipment_running": status,
"climate_list": self.climate_list,
"fan_min_on_time": self.fan_min_on_time
"fan_min_on_time": self.thermostat['settings']['fanMinOnTime']
}
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._current_hold_mode == AWAY_MODE
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heater."""
return 'auxHeat' in self.thermostat['equipmentStatus']
def turn_away_mode_on(self):
"""Turn away mode on by setting it on away hold indefinitely."""
if self._current_hold_mode != AWAY_MODE:
def set_preset_mode(self, preset_mode):
"""Activate a preset."""
if preset_mode == self.preset_mode:
return
self.update_without_throttle = True
# If we are currently in vacation mode, cancel it.
if self.preset_mode == PRESET_VACATION:
self.data.ecobee.delete_vacation(
self.thermostat_index, self.vacation)
if preset_mode == PRESET_AWAY:
self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
'indefinite')
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
if self._current_hold_mode == AWAY_MODE:
elif preset_mode == PRESET_TEMPERATURE:
self.set_temp_hold(self.current_temperature)
elif preset_mode in (
PRESET_HOLD_NEXT_TRANSITION, PRESET_HOLD_INDEFINITE):
self.data.ecobee.set_climate_hold(
self.thermostat_index, PRESET_TO_ECOBEE_HOLD[preset_mode],
self.hold_preference())
elif preset_mode is None:
self.data.ecobee.resume_program(self.thermostat_index)
else:
self.data.ecobee.set_climate_hold(
self.thermostat_index, preset_mode, self.hold_preference())
self.update_without_throttle = True
def set_hold_mode(self, hold_mode):
"""Set hold mode (away, home, temp, sleep, etc.)."""
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
if hold_mode == 'None' or hold_mode is None:
if hold == VACATION_HOLD:
self.data.ecobee.delete_vacation(
self.thermostat_index, self.vacation)
else:
self.data.ecobee.resume_program(self.thermostat_index)
else:
if hold_mode == TEMPERATURE_HOLD:
self.set_temp_hold(self.current_temperature)
else:
self.data.ecobee.set_climate_hold(
self.thermostat_index, hold_mode, self.hold_preference())
self.update_without_throttle = True
@property
def preset_modes(self):
"""Return available preset modes."""
return PRESET_MODES
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""
@@ -352,7 +370,8 @@ class Thermostat(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set the fan mode. Valid values are "on" or "auto"."""
if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
if fan_mode.lower() != STATE_ON and \
fan_mode.lower() != HVAC_MODE_AUTO:
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
_LOGGER.error(error)
return
@@ -376,8 +395,8 @@ class Thermostat(ClimateDevice):
heatCoolMinDelta property.
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
"""
if self.current_operation == STATE_HEAT or self.current_operation == \
STATE_COOL:
if self.hvac_mode == HVAC_MODE_HEAT or \
self.hvac_mode == HVAC_MODE_COOL:
heat_temp = temp
cool_temp = temp
else:
@@ -392,7 +411,7 @@ class Thermostat(ClimateDevice):
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temp = kwargs.get(ATTR_TEMPERATURE)
if self.current_operation == STATE_AUTO and \
if self.hvac_mode == HVAC_MODE_AUTO and \
(low_temp is not None or high_temp is not None):
self.set_auto_temp_hold(low_temp, high_temp)
elif temp is not None:
@@ -405,9 +424,14 @@ class Thermostat(ClimateDevice):
"""Set the humidity level."""
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
ecobee_value = next((k for k, v in ECOBEE_HVAC_TO_HASS.items()
if v == hvac_mode), None)
if ecobee_value is None:
_LOGGER.error("Invalid mode for set_hvac_mode: %s", hvac_mode)
return
self.data.ecobee.set_hvac_mode(self.thermostat_index, ecobee_value)
self.update_without_throttle = True
def set_fan_min_on_time(self, fan_min_on_time):

View File

@@ -1,15 +1,20 @@
"""Support for control of Elk-M1 connected thermostats."""
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.const import PRECISION_WHOLE, STATE_ON
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
SUPPORT_HVAC = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO,
HVAC_MODE_FAN_ONLY]
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Create the Elk-M1 thermostat platform."""
@@ -32,9 +37,8 @@ class ElkThermostat(ElkEntity, ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
| SUPPORT_TARGET_TEMPERATURE_HIGH
| SUPPORT_TARGET_TEMPERATURE_LOW)
return (SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
| SUPPORT_TARGET_TEMPERATURE_RANGE)
@property
def temperature_unit(self):
@@ -78,14 +82,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return self._element.humidity
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._state
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY]
return SUPPORT_HVAC
@property
def precision(self):
@@ -93,7 +97,7 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return PRECISION_WHOLE
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return if aux heater is on."""
from elkm1_lib.const import ThermostatMode
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
@@ -109,11 +113,11 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return 99
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
from elkm1_lib.const import ThermostatFan
if self._element.fan == ThermostatFan.AUTO.value:
return STATE_AUTO
return HVAC_MODE_AUTO
if self._element.fan == ThermostatFan.ON.value:
return STATE_ON
return None
@@ -125,17 +129,19 @@ class ElkThermostat(ElkEntity, ClimateDevice):
if fan is not None:
self._element.set(ThermostatSetting.FAN.value, fan)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set thermostat operation mode."""
from elkm1_lib.const import ThermostatFan, ThermostatMode
settings = {
STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
STATE_HEAT: (ThermostatMode.HEAT.value, None),
STATE_COOL: (ThermostatMode.COOL.value, None),
STATE_AUTO: (ThermostatMode.AUTO.value, None),
STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value)
HVAC_MODE_OFF:
(ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None),
HVAC_MODE_COOL: (ThermostatMode.COOL.value, None),
HVAC_MODE_AUTO: (ThermostatMode.AUTO.value, None),
HVAC_MODE_FAN_ONLY:
(ThermostatMode.OFF.value, ThermostatFan.ON.value)
}
self._elk_set(settings[operation_mode][0], settings[operation_mode][1])
self._elk_set(settings[hvac_mode][0], settings[hvac_mode][1])
async def async_turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
@@ -148,14 +154,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
self._elk_set(ThermostatMode.HEAT.value, None)
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return [STATE_AUTO, STATE_ON]
return [HVAC_MODE_AUTO, STATE_ON]
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
from elkm1_lib.const import ThermostatFan
if fan_mode == STATE_AUTO:
if fan_mode == HVAC_MODE_AUTO:
self._elk_set(None, ThermostatFan.AUTO.value)
elif fan_mode == STATE_ON:
self._elk_set(None, ThermostatFan.ON.value)
@@ -175,13 +181,13 @@ class ElkThermostat(ElkEntity, ClimateDevice):
def _element_changed(self, element, changeset):
from elkm1_lib.const import ThermostatFan, ThermostatMode
mode_to_state = {
ThermostatMode.OFF.value: STATE_IDLE,
ThermostatMode.COOL.value: STATE_COOL,
ThermostatMode.HEAT.value: STATE_HEAT,
ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT,
ThermostatMode.AUTO.value: STATE_AUTO,
ThermostatMode.OFF.value: HVAC_MODE_OFF,
ThermostatMode.COOL.value: HVAC_MODE_COOL,
ThermostatMode.HEAT.value: HVAC_MODE_HEAT,
ThermostatMode.EMERGENCY_HEAT.value: HVAC_MODE_HEAT,
ThermostatMode.AUTO.value: HVAC_MODE_AUTO,
}
self._state = mode_to_state.get(self._element.mode)
if self._state == STATE_IDLE and \
if self._state == HVAC_MODE_OFF and \
self._element.fan == ThermostatFan.ON.value:
self._state = STATE_FAN_ONLY
self._state = HVAC_MODE_FAN_ONLY

View File

@@ -3,7 +3,7 @@
"name": "Enphase envoy",
"documentation": "https://www.home-assistant.io/components/enphase_envoy",
"requirements": [
"envoy_reader==0.4"
"envoy_reader==0.8"
],
"dependencies": [],
"codeowners": []

View File

@@ -7,21 +7,26 @@ from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, POWER_WATT)
CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, POWER_WATT, ENERGY_WATT_HOUR)
_LOGGER = logging.getLogger(__name__)
SENSORS = {
"production": ("Envoy Current Energy Production", POWER_WATT),
"daily_production": ("Envoy Today's Energy Production", "Wh"),
"seven_days_production": ("Envoy Last Seven Days Energy Production", "Wh"),
"lifetime_production": ("Envoy Lifetime Energy Production", "Wh"),
"consumption": ("Envoy Current Energy Consumption", "W"),
"daily_consumption": ("Envoy Today's Energy Consumption", "Wh"),
"daily_production": ("Envoy Today's Energy Production", ENERGY_WATT_HOUR),
"seven_days_production": ("Envoy Last Seven Days Energy Production",
ENERGY_WATT_HOUR),
"lifetime_production": ("Envoy Lifetime Energy Production",
ENERGY_WATT_HOUR),
"consumption": ("Envoy Current Energy Consumption", POWER_WATT),
"daily_consumption": ("Envoy Today's Energy Consumption",
ENERGY_WATT_HOUR),
"seven_days_consumption": ("Envoy Last Seven Days Energy Consumption",
"Wh"),
"lifetime_consumption": ("Envoy Lifetime Energy Consumption", "Wh")
ENERGY_WATT_HOUR),
"lifetime_consumption": ("Envoy Lifetime Energy Consumption",
ENERGY_WATT_HOUR),
"inverters": ("Envoy Inverter", POWER_WATT)
}
@@ -34,15 +39,29 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(cv.ensure_list, [vol.In(list(SENSORS))])})
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Enphase Envoy sensor."""
from envoy_reader.envoy_reader import EnvoyReader
ip_address = config[CONF_IP_ADDRESS]
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
entities = []
# Iterate through the list of sensors
for condition in monitored_conditions:
add_entities([Envoy(ip_address, condition, SENSORS[condition][0],
SENSORS[condition][1])], True)
if condition == "inverters":
inverters = await EnvoyReader(ip_address).inverters_production()
if isinstance(inverters, dict):
for inverter in inverters:
entities.append(Envoy(ip_address, condition,
"{} {}".format(SENSORS[condition][0],
inverter),
SENSORS[condition][1]))
else:
entities.append(Envoy(ip_address, condition, SENSORS[condition][0],
SENSORS[condition][1]))
async_add_entities(entities)
class Envoy(Entity):
@@ -76,8 +95,23 @@ class Envoy(Entity):
"""Icon to use in the frontend, if any."""
return ICON
def update(self):
async def async_update(self):
"""Get the energy production data from the Enphase Envoy."""
from envoy_reader import EnvoyReader
from envoy_reader.envoy_reader import EnvoyReader
self._state = getattr(EnvoyReader(self._ip_address), self._type)()
if self._type != "inverters":
_state = await getattr(EnvoyReader(self._ip_address), self._type)()
if isinstance(_state, int):
self._state = _state
else:
_LOGGER.error(_state)
self._state = None
elif self._type == "inverters":
inverters = await (EnvoyReader(self._ip_address)
.inverters_production())
if isinstance(inverters, dict):
serial_number = self._name.split(" ")[2]
self._state = inverters[serial_number]
else:
self._state = None

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