Compare commits

..

205 Commits

Author SHA1 Message Date
Paulus Schoutsen
224c258876 Bumped version to 0.87.0b1 2019-02-01 14:35:23 -08:00
Oliver Völker
e4d76d5c44 InfluxDB - change connection test method (#20666) 2019-02-01 14:10:39 -08:00
emontnemery
c702e1e3c6 Add PLATFORM_SCHEMA_BASE support to check_config.py (#20663) 2019-02-01 14:10:39 -08:00
zewelor
47660f9312 Fix parsing yeelight custom effects, when not present in config (#20658) 2019-02-01 14:10:38 -08:00
Kevin Fronczak
1a5028f56f Upgrade blinkpy to re-enable motion detection (#20651) 2019-02-01 14:10:38 -08:00
Paulus Schoutsen
ca729b178b Fix geofency requiring a configuration.yaml entry (#20631) 2019-02-01 14:10:37 -08:00
emkay82
557b745053 Fix pjlink issue (#20510)
* Fix issue #16606

Some projectors do not respond to pjlink requests during a short period after the status changes or when its in standby, resulting in pypjlink2 throwing an error. This fix catches these errors. Furthermore, only the status 'on' and 'warm-up' is interpreted as switched on, because 'cooling' is actually a switched off status.

* Update pjlink.py

Improved error handling

* Update pjlink.py

Improved error handling

* Update pjlink.py

Clean up
2019-02-01 14:10:37 -08:00
Paulus Schoutsen
0400e29f7a Updated frontend to 20190201.0 2019-02-01 12:51:17 -08:00
Paulus Schoutsen
8db2152230 Bumped version to 0.87.0b0 2019-01-30 17:37:56 -08:00
Paulus Schoutsen
47848e26f9 Merge remote-tracking branch 'origin/master' into dev 2019-01-30 17:37:38 -08:00
Diogo Gomes
c7ff8d4996 fix #20571 (#20589) 2019-01-30 17:33:52 -08:00
Andrew Sayre
69ec7980ad Add SmartThings component and switch platform (#20148)
* Added SmartThings component and switch platform

* Corrected comment typos.

* Embedded switch platform.

* Replaced custom view usage with webhook component.

* Replaced urls with tokens in strings.

* Fixed line length.

* Use generated webhook id instead of static one.

* Reuse core constant instead of defining again.

* Optimizations in anticipation of future platforms.

* Use async_generate_path instead of hard-coded path.

* Fixed line length.

* Updates per review feedback.

* Updates per latest review feedback.
2019-01-30 17:31:59 -08:00
Jasper van der Neut - Stulen
5a0c707a37 Fix duplicate luftdaten entities (#20226)
* Use same data schema for configflow, make sensor_id a positive integer.

* Change sensor_id to int and remove duplicate Luftdaten config entries.

This fixes #18838, and also fixes the root cause
of #19981 and #19622.

* Use pure type for boolean.
2019-01-30 17:12:59 -08:00
Paulus Schoutsen
473bf93973 Updated frontend to 20190130.1 2019-01-30 17:00:36 -08:00
David F. Mulcahey
ed75549123 ZHA component rewrite part 4 - add device module (#20469)
* add device module

* spelling

* review comments

* filter out endpoint id 0 (ZDO)

* review comments

* change name

* remove return
2019-01-30 16:44:22 -05:00
Alok Saboo
3e98aad8a2 Added code to Abode Alarm control panel (#20611) 2019-01-30 13:02:23 -08:00
Gido
542160fc56 Add sensor platform for Rova Garbage Collection (#18868)
* Adding sensor for SolarEdge Monitoring API support

* Adding support for Rova garbage calendar

* Added Rova platform to retrieve garbage pickup times

* Update async to new syntax
Added async_update to sensor class
Added Throttle to Rova update function
Minor code style changes

* Small style fixes

* Removed domain

* Update debug to error messages
Change CONF_MONITORED_VARIABLES to CONF_MONITORED_CONDITIONS
Update async update call to normal update

* Update requirements to rova v0.0.2
Add address check to see if ROVA collects in the given area

* Rename entity names to English
Add json_key to Sensor entities

* Add device_class to the RovaSensor

* Fix pylint and flake8 messages

* Add check for None in case collection date is unknown

* Return device class constant
2019-01-30 21:58:41 +01:00
Paulus Schoutsen
e2cc1564a0 Add lovelace systeam health (#20592) 2019-01-30 12:57:56 -08:00
William Scanlon
2836ff86fe Update to the newest python-wink and fix push updates! (#20594)
* Update to the newest python-wink and make post session call to Wink to keep pubnub working.

* Update __init__.py
2019-01-30 11:35:47 -08:00
Paulus Schoutsen
cb07ea0d60 RFC: Add system health component (#20436)
* Add system health component

* Remove stale comment

* Fix confusing syntax

* Update test_init.py

* Address comments

* Lint

* Move distro check to updater

* Convert to websocket

* Lint

* Make info callback async

* Fix tests

* Fix tests

* Lint

* Catch exceptions
2019-01-30 10:57:53 -08:00
Anders Melchiorsen
91aa874c0c Fix LIFX for single-zone strip extensions (#20604) 2019-01-30 10:47:28 -08:00
Paulus Schoutsen
ca0ee509e7 Fix map icons (#20602) 2019-01-30 09:50:58 -08:00
Paulus Schoutsen
8062f48973 Add remove commmand to entity registry (#20597) 2019-01-30 09:50:32 -08:00
choss
ed299a9137 Add support for FRITZ DECT 100 (temp sensor) (#20308) 2019-01-30 18:44:36 +01:00
starkillerOG
349de19316 Philips Hue: add prompt to update bridge/bulb (#20590)
* Philips Hue: add prompt to update bridge/bulb

* bump aiohue to v1.9.0

* bump aiohue to v1.9.0

* bump aiohue to v1.9.0
2019-01-30 09:42:18 -08:00
Paulus Schoutsen
7c9597f824 Fix area registry config being loaded (#20598)
* Fi area registry config being loaded

* Mark area_id optional
2019-01-30 09:41:55 -08:00
Alok Saboo
a011048a4e Change Unifi timeout (#20606) 2019-01-30 18:40:40 +01:00
Bram Kragten
0ef9882e2e Fix map icons 2019-01-30 17:09:56 +01:00
mindakas
ec4495bd0c Bump pymodbus to 1.5.2 (#20582) 2019-01-30 11:57:14 +00:00
Tommy Jonsson
c5c64e738e html5 notifications add VAPID support (#20415)
* html5 notifications add VAPID support

* fix lint errors

* replace httpapi with websocketapi

* Address my own comment
2019-01-29 14:49:33 -08:00
emontnemery
89fc3b2a1b Disable extra=vol.ALLOW_EXTRA for MQTT platforms. (#20562) 2019-01-29 09:29:02 -08:00
merc1031
48f0e8311b add empy all groups view. Makes Brilliant Lightpad work (#20564) 2019-01-29 09:26:07 -08:00
Jc2k
e0e5b860e4 Homekit Motion Sensor Review feedback (#20568) 2019-01-29 11:25:18 -05:00
kennedyshead
988bcf9399 Fixing the openssl issue (#20570) 2019-01-29 07:42:38 -08:00
MatteGary
e95c50c742 New Transmission component (#19230)
* New Transmission component and interaction

First commit for New Transmission component and interaction

* Fix commit

* Fix commit

* Fix + Switch checkin

Fix according to failed build and request, first checkin for Turtle Mode Switch in Transmission, still have to figure it out why it's not working.

* Bugfixing

* Fix commit

Multiple fix

* Fix

* fix for missing config

* Update on requirements_all.txt

* Fix in requirements_all.txt

* Fix

* Fix for build

* fix

* Fix

* Fix (again)

* Fix

* Fix indentation

* Fix indentation

* Fix Throttle

* Update .coveragerc

* Fix import and coveragerc
2019-01-29 10:27:26 +01:00
Stealth Hacker
6ff4ea1126 Add Recollect Waste (#20121)
* Added Recollect Waste Curbside Collection sensor for tracking next collection date and for which types of waste.

* Added missing schema attributes.

* Adding requirements and coverage entries for Recollect Waste platform.

* Added exception handling, some other fixes and suggestions from code review.

* Fixed reference to incorrect exception type.

* Updated requirements_all.txt with new version of recollect-waste.

* Added true to add_entities. Created constant for default time interval. Used different pylint exclusion comment.

* Using HA's CONF_SCAN_INTERVAL now. Unique_id is now set in @property.

* Changed parameter of timedelta from seconds to days.

* Added test run of recollect client during setup_platform. Using built in SCAN_INTERVAL now.

* Return nothing in setup_platform if there is an exception.
2019-01-29 09:38:01 +01:00
Richard Mitchell
a7c74151bc Treat each player as a 'device' for non-client devices. (#20074) 2019-01-29 09:15:42 +01:00
Julien Brochet
6859d5216e Add Synology SRM device tracker (#20320) 2019-01-29 09:12:10 +01:00
Paulus Schoutsen
73a0c664b8 Allow usernames to be case-insensitive (#20558)
* Allow usernames to be case-insensitive

* Fix typing

* FLAKE*
2019-01-29 08:28:52 +01:00
Diogo Gomes
b0ff51b0ef Add an Integration sensor (#19703)
* initial version

* Tested

* set state added

* lint

* lint

* remove artifacts

* Use Decimal instead of float

* simplify

* travis lint fix

* addres comments by @ottowinter

* remove job

* better sanity check

* lower error -> warning

* hound

* fix state validation

* refactor energy -> integration

* address @MartinHjelmare comments

* new style string format

* remove async_set_state

* patching the source function
2019-01-29 08:25:36 +01:00
Jc2k
e22802a4d4 Add support for HomeKit motion sensor devices (#20555) 2019-01-28 20:30:56 -08:00
Paulus Schoutsen
aa29eeba04 Merge pull request #20559 from home-assistant/rc
0.86.4
2019-01-28 20:28:08 -08:00
Rohan Kapoor
cc74035c3b Move CONF_UPDATE_INTERVAL to homeassistant.const (#20526) 2019-01-28 20:26:31 -08:00
Rohan Kapoor
fe47253f68 Fix linting error (#20488) 2019-01-28 20:26:02 -08:00
emontnemery
3ee3acd550 Update device registry of MQTT light (#20441)
* Update device registry of MQTT light

* Move config_entry to constructor
2019-01-28 19:45:34 -08:00
Paulus Schoutsen
234c759b45 Bumped version to 0.86.4 2019-01-28 17:52:42 -08:00
Fredrik Erlandsson
ba4d4bcd29 fix #20387 devices without model/protocol (#20530) 2019-01-28 17:52:36 -08:00
starkillerOG
d7bbdb033d Add check to validate gamut (#20518)
* color.util - Add check to validate gamut

* fix indents

* fix typo

* Add check to validate gamut

* Add tests for gamut checker

* fix test

* fix pylint issues

* fix hue light gamut tests

* add check to validate gamut

* move None check

* Move None check

* Include prompt to update bridge/bulb on error

* fix wrong commit

* fix error message

* Update light.py
2019-01-28 17:52:35 -08:00
kennedyshead
63fd5f2d31 Bumps aioasuswrt (#20432) 2019-01-28 17:52:35 -08:00
starkillerOG
f353d51ab1 Add check to validate gamut (#20518)
* color.util - Add check to validate gamut

* fix indents

* fix typo

* Add check to validate gamut

* Add tests for gamut checker

* fix test

* fix pylint issues

* fix hue light gamut tests

* add check to validate gamut

* move None check

* Move None check

* Include prompt to update bridge/bulb on error

* fix wrong commit

* fix error message

* Update light.py
2019-01-28 17:52:00 -08:00
Per Osbäck
3f484228cb Add missing switch for motion_detect (#20540) 2019-01-28 16:48:55 -08:00
Fredrik Erlandsson
717a0c2b2d fix #20387 devices without model/protocol (#20530) 2019-01-28 16:46:37 -08:00
David F. Mulcahey
34090bd021 ZHA component rewrite part 3 - update helpers (#20463)
* update helpers

* address comments

* remove ieee

* cluster id as hex too
2019-01-28 16:40:00 -08:00
emontnemery
bb1583c453 Add discovery update support to MQTT camera (#20529) 2019-01-28 16:21:38 -08:00
emontnemery
d7ba2aad1d Add COMPONENT_SCHEMA and use it in alarm_control_panel (#20224)
* Add COMPONENT_SCHEMA and use in alarm and mqtt

* Revert MQTT changes

* Lint

* Small tweak

* Add tests

* Rename COMPONENT_SCHEMA to PLATFORM_SCHEMA_BASE

* Fix tests

* Improve tests
2019-01-28 16:14:55 -08:00
emontnemery
bb4ca1f525 Cleanup if discovered mqtt vacuum can't be added (#20549) 2019-01-28 15:56:47 -08:00
Robert Svensson
bd335e1ac1 Area registry (#20435)
* First draft of area registry

* Refactor based on input

* Add tests for areas
Add tests for updating device

* Updating a device shouldn't require area

* Fix Martins comment

* Require admin

* Save after deleting

* Rename read to list_areas
Fix device entry_dict
Remove area id from device when deleting area

* Fix tests
2019-01-28 15:52:42 -08:00
Aaron Bach
2c7060896b Make Ambient PWS async and cloud-push (#20332)
* Moving existing sensor file

* Initial functionality in place

* Added test for config flow

* Updated coverage and CODEOWNERS

* Linting

* Linting

* Member comments

* Hound

* Moving socket disconnect on HASS stop

* Member comments

* Removed unnecessary dispatcher call

* Config entry fix

* Added support in config flow for good accounts with no devices

* Hound

* Updated comment

* Member comments

* Stale docstrings

* Stale docstring
2019-01-28 15:35:39 -08:00
Jc2k
abeb875c61 Homekit controller BLE groundwork (part 2) (#20548)
* Only fetch values of characteristics we are tracking.

* Use callbacks on subclasses to update individual values

* Update alarm_control_panel to use update callbacks

* Update climate to use update callbacks

* Update cover to use update callbacks

* Update light to use update callbacks

* Update lock to use update callbacks

* Update switch to use update callbacks

* Remove compatibility code as all entities migrated

* pylint by name rather than code
2019-01-28 21:27:26 +01:00
Jc2k
41c1997b88 Homekit controller BLE groundwork (#20538)
* Define the characteristics to poll (or subscribe to) up front

* Configure characteristics immediately instead of during first poll

* Do as much cover configuration upfront as possible

* Remove test workaround as no longer needed

* Remove switch code that is already handled by HomeKitEntity

* Remove lock code already handled by HomeKitEntity

* Remove light code already handled by HomeKitEntity

* Remove alarm code already handled by HomeKitEntity

* Remove climate code already handled by HomeKitEntity
2019-01-28 17:21:20 +01:00
Jc2k
995758b8ac Add more HomeKit controller tests (#20515)
* homekit_controller tests: automatically find entity ids in tests

Some entities use dynamic ids because of the nature of the test fakes it is
hard to predict the name of the entity that will be created. This inspects the
EntityComponent of the domain to find the freshly created entity.

* homekit_controller: Tests can now define their own Service models.

All existing tests use models as defined upstream. But upstream only defines a
few service models. This adds a generic model helper for creating test
service/characteristic models.

* homekit_controller: Add cover tests

* homekit_controller: Add lock tests

* homekit_controller: Add alarm_control_panel tests

* homekit_controller: Update light tests for color_temp.

* Revert "homekit_controller tests: automatically find entity ids in tests"

This reverts commit 506caa4c3e.

* homekit_controller: Mock entity name so entity_id is consistent.

Also remove spurious subclass overrides that are identical to parent class.

* homekit_controler: Make tests less awkward as allowed top level imports
2019-01-28 13:20:32 +01:00
Christian Biamont
f33e432cab Reset Brottsplatskartan incident types every day (#20117)
* Reset the incident types count every day

* Remove functionality that was never implemented

We don't need to keep track of previous incidents because it's not used
anywhere.

* Create empty dictionary with a pair of braces: {}
2019-01-28 12:30:15 +01:00
David Lie
29984efd8c Use more up-to-date version of pyfoscam library (#20419)
* Change foscam python library to pyfoscam, which is more up to date and has several critical bug fixes.

* Update requirements_all.txt to match.

* Inserting automatically generated requirements.txt
2019-01-28 08:07:39 +01:00
Rohan Kapoor
d179686edf Load/unload gpslogger entities correctly between component and platform (#20448)
* Embed device_tracker in gpslogger

* Load/unload gpslogger entities correctly between component and platform

* Await the coroutine directly
2019-01-27 15:37:19 -08:00
Rohan Kapoor
0c87fb421e Load/unload locative entities correctly between component and platform (#20498)
* Embed device_tracker in locative

* Load/unload locative entities correctly between component and platform

* Await the coroutine directly

* Await the correct coroutine
2019-01-27 23:43:16 +01:00
Rohan Kapoor
f575d1d3a6 Load/unload geofency entities correctly between component and platform (#20497)
* Load/unload geofency entities correctly between component and platform

* Lint

* Await the coroutine directly
2019-01-27 13:18:20 -08:00
Eliseo Martelli
3d4f2926e9 Add Iliad Italy (Mobile Telephony Provider) Sensor (#19645)
* working state

* Attrs

* > requirements generated

* linting

* fixes

* coveragerc

* ordered imports and fixed auth

* Added Throttle

* moved throttle to decorator

* remove scan interval

* lower throttle

* moved to scan interval

* Add attribution
2019-01-27 21:44:19 +01:00
Heine Furubotten
7412d0f97c Add nilu air_quality platform (#19674)
* Add nilu air_pollutants platform

* Code Review - validation, DRYs, rm state override, new attr
- Repeated code moved to own method.
- Removed override of state property.
- New attr for showing nilu pollution recommendations.
- More validation of stations input.
- Minor fixes and typos.

* Removed unused prop

* Check for none result from client before entity add

* Moved platform to air_quality component

* Updated outdated docstrings

* Minor changes
2019-01-27 21:32:23 +01:00
Daniel Høyer Iversen
f91ff76b95 Upgrade mill library (#20514) 2019-01-27 19:20:43 +01:00
emontnemery
648adcc708 Small cleanup of MQTT platforms (#20503)
* Move CONF_UNIQUE_ID to init

* Sort imports

* Update ordering
2019-01-27 18:54:52 +01:00
emontnemery
8804f55fcc Update device registry of MQTT Vacuum (#20500) 2019-01-27 17:43:16 +01:00
emontnemery
1f93984fd5 Update device registry of MQTT Lock (#20501) 2019-01-27 17:42:45 +01:00
Daniel Høyer Iversen
ae84a91ea8 Upgrade tibber library (#20504)
* Upgrade tibber library

* Upgrade tibber library
2019-01-27 17:39:56 +01:00
Steven Looman
2aab646be2 Upgrade to async-upnp-client==0.14.3 (#20502) 2019-01-27 13:52:51 +01:00
Jc2k
10e3698fd7 Add homekit_controller tests (#20457)
* Add a test for a homekit_controller switch

* Add a test for a homekit_controller lightbulb

* Add a test for homekit_controller thermostat

* Changes from review

* Patch utcnow to known time in HK tests

* Neater fixture use per review
2019-01-27 12:34:49 +01:00
Rohan Kapoor
7368c623d4 Split out dovado to a component and sensor platform (#20339)
* Split out dovado to a component and sensor platform

* Lint

* Address code review comments (#20339)

* Switch to using a notify platform for dovado SMS (#20339)

* Optimizing imports

* Remove return on `setup_platform`.

* Clean up unneeded constants
2019-01-27 10:36:10 +01:00
Rohan Kapoor
d82e5ecbb0 Upgrade zm-py to 0.3.1 (#20489) 2019-01-27 10:28:20 +01:00
Aaron Bach
239c60c09f Use HASS latitude/longitude as defaults for Lyft (#20491) 2019-01-26 23:21:51 -07:00
Rohan Kapoor
38c33bd01e Fix linting error (#20488) 2019-01-26 17:11:09 -08:00
Paulus Schoutsen
86f5f0226c Bumped version to 0.87.0.dev0 2019-01-26 14:15:36 -08:00
Shantanu Tushar
a368db9ad4 Include exception details in the error log (#20461)
* Include exception details in the error log

I see this error quite often in my HA logs and this will be helpful for anyone who is attempting to debug this.

* Minor change

* Remove line break
2019-01-26 14:11:16 -08:00
Paulus Schoutsen
87316c4e83 Warn for old slugs/entity ids (#20478)
* Warn for old slugs/entity ids

* add comments

* Lint

* LInt

* Lint

* Lint
2019-01-26 14:09:41 -08:00
Fabian Affolter
38b1ce3fe0 Upgrade psutil to 5.5.0 (#20462) 2019-01-26 22:11:36 +01:00
Fabian Affolter
9920699bb8 Upgrade sqlalchemy to 1.2.16 (#20474) 2019-01-26 22:11:00 +01:00
starkillerOG
b9bf6963fd Philips Hue, include debug message for color gamut (#20455) 2019-01-26 12:50:34 -08:00
emontnemery
60dc337f3f Update device registry of MQTT cover (#20443)
* Update device registry of MQTT cover

* Move config_entry to constructor
2019-01-26 10:52:41 -08:00
emontnemery
85c72fbca6 Update device registry of MQTT alarm (#20439) 2019-01-26 10:48:35 -08:00
emontnemery
85ccd71d39 Update device registry of MQTT sensor (#20440) 2019-01-26 10:48:18 -08:00
Paulus Schoutsen
09cbcb74bc Merge pull request #20442 from emontnemery/mqtt_binary_sensor_update_device_info
Update device registry of MQTT binary_sensor
2019-01-26 10:47:57 -08:00
Paulus Schoutsen
0e453fe492 Update device registry of MQTT climate (#20444) 2019-01-26 10:47:29 -08:00
emontnemery
1d16bb2cd4 Update device registry of MQTT fan (#20445) 2019-01-26 10:46:41 -08:00
David F. Mulcahey
05d41bc0ee introduce gateway (#20460) 2019-01-26 13:28:13 -05:00
Jef D
f3285f96bb Add Co2signal sensor (#19204)
* Initial commit for the co2signal sensor

* Clean code

* Run script gen_requirements_all.py

* remove unintended character

* Remove redundancy

* Remove unused imports

* Code style

* Code style fixes

* Code style

* Fix comments PR

Comments by @fabaff
* Remove redundant comments and variables
* Follow the latest home-assistant guidelines

* Bump CO2Signal version

* Round API result

* Improve default latitude/longitude handling

* Improve friendly name

* Improve config handling

* Make lines shorter

* Style

* Convert default to variable

None does not pass cv.string

* Message if not inclusive

* Shorten line

* Update requirements

* Update co2signal.py

Group imports;  remove empty lines; refactor use of location_type; remove logging messages; remove unused functions; add global variables

* Update co2signal.py

Import platform schema from sensor

* Small fix

* Update co2signal.py

Remove last mentions of location_type

* Review changes

Add attribution
Formatting

* Missing whitespace

* Update co2signal.py

Fix pylint

* Update co2signal.py

Change blank lines

* Update co2signal.py

Initialise _data
2019-01-26 19:02:46 +01:00
Diogo Gomes
1d5ffe9ad5 Utility meter (#19718)
* initial commit

* test service calls

* lint

* float -> Decimal

* extra tests

* lint

* lint

* lint

* lint

* fix self reset

* clean

* add services

* improve service example description

* add optional paused initialization

* fix

* travis fix

* fix YEARLY

* add tests for previous bug

* address comments and suggestions from @ottowinter

* lint

* remove debug

* add discoverability capabilities

* no need for _hass

* Update homeassistant/components/sensor/utility_meter.py

Co-Authored-By: dgomes <diogogomes@gmail.com>

* Update homeassistant/components/sensor/utility_meter.py

Co-Authored-By: dgomes <diogogomes@gmail.com>

* correct comment

* improve error handling

* address @MartinHjelmare comments

* address @MartinHjelmare comments

* one patch is enought

* follow @ballob suggestion in https://github.com/home-assistant/architecture/issues/131

* fix tests

* review fixes

* major refactor

* lint

* lint

* address comments by @MartinHjelmare

* rename variable
2019-01-26 16:33:11 +01:00
Jonas Pedersen
ed6e349515 Correct minor comments from PR#20138. (#20454) 2019-01-26 15:55:25 +01:00
Fabian Affolter
a85e018bc4 Upgrade astral to 1.8 (#20459) 2019-01-26 15:54:35 +01:00
David F. Mulcahey
a0b93c2add ZHA component rewrite part 1 (#20456)
* rearrange files

* add init to module

* update imports

* update coveragerc

* put blank line back... git raw view be damned
2019-01-26 08:54:49 -05:00
Anders Melchiorsen
e593383b4d Error handling for recorder purge (#20424) 2019-01-26 11:02:16 +01:00
Austin Drummond
b3c3721a79 Add alarm type workaround zwave lock Yale YRD240 (#20438) 2019-01-26 11:01:04 +01:00
Adam Belebczuk
310c073c64 WeMo - Fix device discovery issues (#20446) 2019-01-26 11:00:06 +01:00
Louis Matthijssen
d39784906b Fix HTTP login attempts check triggering too late (#20431) 2019-01-26 03:13:44 +01:00
emontnemery
6d2e7db123 Update device registry of MQTT climate 2019-01-26 09:04:02 +08:00
emontnemery
d8e43978b7 Update device registry of MQTT binary_sensor 2019-01-26 08:58:08 +08:00
kennedyshead
76c0295403 Bumps aioasuswrt (#20432) 2019-01-25 23:27:44 +01:00
coreGreenberet
2bc7444427 Fix homematicip cloud alarm_arm_home (#20321) 2019-01-25 19:45:42 +01:00
coreGreenberet
4518e6bdf7 Fix minor homematicip cloud binary sensor issues (#20398)
* fix for smoke detection

* a tilted window is now considered as "open"/on

* changed comparison to enum

* line length

* insert brackets for line length and comparison

* indentation should now be ok for hound
changed api version to 0.10.4

* indentation should now be ok for hound
changed api version to 0.10.4

* updating requirement files

* satisfy lint
2019-01-25 19:00:37 +01:00
zewelor
d6c12e47f4 Fix cast platform album name property (#20411) 2019-01-25 18:49:50 +01:00
Jc2k
cea2bf94bd Move homekit_controller entity types under homekit_controller platform (#20376)
* Move homekit_controller entity types under homekit_controller platform

* Update coveragerc as homekit_controller moved
2019-01-25 07:43:01 -08:00
jonudewux
1fcaaf93ad Upgrade youtube_dl to 2019.01.24 (#20408) 2019-01-25 11:57:13 +01:00
Andrey Kupreychik
d4c7515681 Add time_throttle filter to sensor.filter (#20334)
* Added time_throttle filter

* Added time_throttle filter test

* Updated comments for time_throttle filter
2019-01-25 10:07:45 +00:00
Anders Melchiorsen
c94834d8f6 Add LIFX listen port advanced configuration (#20299) 2019-01-24 22:50:26 -08:00
emontnemery
ec5da05804 Add character encoding to MQTT automation. (#20292) 2019-01-24 22:43:56 -08:00
emontnemery
d84cd01cbf Cleanup if discovered mqtt light can't be added (#19740)
* Cleanup if discovered mqtt light can't be added

* No bare except

* Clear ALREADY_DISCOVERED list with helper

* Use constant instead of string literal
2019-01-24 22:40:52 -08:00
emontnemery
a1da6a677a Update device registry of MQTT Switch (#19540)
* MQTT Switch: Update device registry

* Move config_entry to constructor

* Remove duplicated code

* Fix merge error
2019-01-24 22:39:16 -08:00
Jasper van der Neut - Stulen
55943cfac0 Return windspeed and windgust in km/h instead of m/s. (#20340)
Darksky dev docs state (https://darksky/dev/docs):
`ca: same as si, except that windSpeed and windGust are in kilometers per
hour`
2019-01-24 22:36:25 -08:00
Daniel Perna
400aaf8a3a Update pyhomematic to 0.1.55 (#20397) 2019-01-24 17:53:31 -08:00
Fabian Affolter
046683ee3f Upgrade numpy to 1.16.0 (#20396) 2019-01-24 17:53:17 -08:00
Paulus Schoutsen
c7f5beb794 history allowed to load states with invalid entity IDs (#20399) 2019-01-24 17:53:01 -08:00
starkillerOG
c508ba166c Philips Hue - Remove unnessesary warning (#20394)
for white hue bulbs and bulbs from other brands like Ikea and Innr, this warning will be issued while this is not really a problem.
So just remove the warning.
2019-01-24 23:46:55 +01:00
mindigmarton
68bd5f5df8 Upgrade emulated_roku to 0.1.8 to fix invalid encodings, fixes #20388 (#20390) 2019-01-24 22:51:15 +01:00
Anders Melchiorsen
70c5807976 Improve deprecation warnings (#20391) 2019-01-24 13:37:30 -08:00
Daniel Høyer Iversen
3b1534c126 Remove logging from tibber (#20382)
* Remove logging from tibber

* keep guard
2019-01-24 21:56:44 +01:00
zewelor
2559bc4226 Add yeelight start_flow service and ability to declare custom effects (#20107) 2019-01-24 17:41:07 +01:00
Fréderic Kinnaer
0be922dc9c SongPal: do not crash if active_source is not (yet) available - fixes #20343 (#20344)
* SongPal: error handling if active_source can't be detected

* sonpal: Add comment to the use of getattr() for property source

* songpal: make comment single-line
2019-01-24 12:55:39 +01:00
Fabian Affolter
7038dd484a Upgrade TwitterAPI to 2.5.9 (#20372) 2019-01-24 09:37:25 +01:00
Daniel Høyer Iversen
1bd31e3459 Change STATE_UNKOWN to None (#20337)
* Change STATE_UNKOWN to None

* Change STATE_UNKOWN to None

* tests

* tests

* tests

* tests

* tests

* style

* fix comments

* fix comments

* update fan test
2019-01-24 08:20:20 +01:00
Diogo Gomes
074fcd96ed Fix error when API doesn't return a forecast. (#20365)
* add guard

* wrong logic
2019-01-23 21:14:21 -08:00
Paulus Schoutsen
5580bec1d3 Calling save before load would crash Lovelace storage (#20368) 2019-01-23 21:13:55 -08:00
Paulus Schoutsen
af3afb673a Fix restore state crashing invalid entity ID (#20367) 2019-01-23 21:12:38 -08:00
Daniel Høyer Iversen
697c331903 Clean up concord232 (#20353)
* Clean up concord232

* concord cleanup

* clean up

* fix import
2019-01-23 17:05:56 -08:00
Eliseo Martelli
971d933140 [FIX] Time reporting incorrect in sensor.gtt (#20362)
* quick fix

* remove print statement

* fixes

* remove lambda

* added pylint disable

* should be fine now
2019-01-23 17:05:16 -08:00
Paulus Schoutsen
0300ef2040 Fix entity registry comments (#20357) 2019-01-23 16:33:21 -08:00
Jc2k
a396ee2cb5 Bump homekit==0.12.2 + improve controller reliability (#20325)
Sessions were timing out and requiring a HA restart in order to restore
functionality. Network disconnects are now handled and sessions will be
automatically recovered after they fail.
2019-01-23 20:44:21 +01:00
Kevin Fronczak
7ca7951526 Hotfix for blink initialization failure. Fixes #20335 (#20351) 2019-01-23 20:37:21 +01:00
Daniel Høyer Iversen
5bf3b2dd9f clean up of islamic_prayer_times (#20352)
update_sensors was not awaited in async_track_point_in_time()
2019-01-23 20:17:45 +01:00
Jonas Pedersen
c6cee1ccd3 Add Danfoss Air HRV support (#20138)
* Add support for Danfoss Air HRV systems.

* Correct lint errors after initial commit of Danfoss Air HRV support.

* A couple of lint fixes for danfoss_air.

* Refactor to comply with HA standards.

* Style fix.

* Use wildcard for danfoss_air in .coveragerc.

* Remove config example from header documentation.
Correct import name for platforms.
2019-01-23 11:58:45 -05:00
Corey Edwards
16a4180fab Fix mpd logging format string field (#20333)
* Fix format string field

* Remove str.format and let _LOGGER handle formatting

* Remove trailing period from log message
2019-01-23 11:50:04 -05:00
shbatm
e8f0e534f9 Update Requirement for PyISY Package in isy994 Component to v1.1.1 (#20349) 2019-01-23 11:48:35 -05:00
Fabien Piuzzi
07f1e2ce75 Add Octoprint custom path (#20302)
* Added custom path config option to octoprint component

* Added some debug logging

* removed debug logging for base url

* Fixed single/double quotes style
2019-01-23 12:50:17 +01:00
Malte Franken
eaa9c4d437 Remove creation of geolocation default group (#20338) 2019-01-23 10:04:41 +01:00
Frank
db277ad023 Add data/data_template/title to alert component (#17616)
* Add data/data_template/title to alert component

* Fix line length

* Fix tests

* Fix lint

* fix line length

* Fix tests, make title templatable

* Fix test

* Fix test

* Optimize data, make title templated

* Fix line length

* Add title template

* typo

* Fix tests
2019-01-23 08:47:37 +01:00
Sebastian Muszynski
3484e506e8 Fix xiaomi speed attribute name clash (#20312) 2019-01-23 05:04:13 +01:00
Paulus Schoutsen
e964750ac1 Fix invalid entity ID in entity registry (#20328) 2019-01-22 14:07:17 -08:00
Paulus Schoutsen
c87c5797db Updated frontend to 20190121.1 2019-01-22 12:58:53 -08:00
Richard Mitchell
5b25188474 Should require the 'GATTOOL' setup extras which includes pexpect. (#20263)
* Should require the 'GATTOOL' setup extras which includes pexpect.

* Also fix skybeacon's requirement and requirements_all.
2019-01-22 18:50:21 +01:00
Fabian Affolter
91ef78adc5 Upgrade youtube_dl to 2019.01.17 (#20318) 2019-01-22 18:45:16 +01:00
rolfberkenbosch
e2a4fdeadf Update locationsharinglib to version 3.0.11 (#20322)
* Update google_maps.py

There are known bug in locationsharinglib version 3.0.9, the developer has fixed this in 3.0.11. (https://github.com/costastf/locationsharinglib/issues/42)

* Update requirements_all.txt
2019-01-22 08:24:40 -08:00
Mateusz Korniak
9b7780edf0 Ecoal (esterownik.pl) static fuel boiler and pump controller (#18480) - matkor
* Starting work on ecoal boiler controller iface.

* Sending some values/states to controller.

* Basic status parsing, and simple settings.

* Platform configuration.

* Temp sensors seems be working.

* Switch from separate h/m/s to datetime.

* Vocabulary updates.

* secondary_central_heating_pump -> central_heating_pump2

* Pumps as switches.

* Optional enabling pumps via config.

* requests==2.20.1 added to REQUIREMENTS.

* Optional enabling temp sensors from configuration yaml.

* autopep8, black, pylint.

* flake8.

* pydocstyle

* All style checkers again.

* requests==2.20.1 required by  homeassistant.components.sensor.ecoal_boiler.

* Verify / set switches in update().
Code cleanup.

* script/lint + travis issues.

* Cleanup, imperative mood.

* pylint, travis.

* Updated .coveragerc.

* Using configuration consts from homeassistant.const

* typo.

* Replace global ECOAL_CONTR with hass.data[DATA_ECOAL_BOILER].
Remove requests from REQUIREMENTS.

* Killed .update()/reread_update() in Entities __init__()s.
Removed debug/comments.

* Removed debug/comments.

* script/lint fixes.

* script/gen_requirements_all.py run.

* Travis fixes.

* Configuration now validated.

* Split controller code to separate package.

* Replace in module docs with link to https://home-assistant.io .

* Correct component module path in .coveragerc.
More vals from  const.py.
Use dict[key] for required config keys.
Check if credentials are correct during component setup.
Renamed add_devices to add_entities.

* Sensor/switch depends on ecoal_boiler component.
EcoalSwitch inherits from SwitchDevice.
Killed same as default should_poll().
Remove not neede schedule_update_ha_state() calls from turn_on/off.

* lint fixes.

* Move sensors/switches configuration to component setup.

* Lint fixes.

* Invalidating ecoal iface cache instead of force read in turn_on/off().

* Fail component setup before adding any platform entities.
Kill NOTE.

* Disallow setting entity names from config file, use code defined default names.

* Rework configuration file to use monitored_conditions like in rainmachine component.

* Killed pylint exception.
Log error when connection to controller fails.

* A few fixes.

* Linted.
2019-01-22 08:14:27 -05:00
Jason Hu
f84c0ee473 Upgrade python-nest to 4.1.0 (#20313) 2019-01-22 13:23:33 +01:00
Mattias Welponer
89ba374d51 HomematicIP add cover FROLL and BROLL devices (#19794)
* Add cover FROLL and BROLL devices

* Fix import

* Fix async calls

* Update cover functions and async fixes

* Update test

* Cleanup code

* Update header

* Merge imports

* Update

* Remove init

* Update coveragerc file

* Update coveragerc
2019-01-22 09:22:45 +01:00
krygal
a8ef7a2774 Add device tracker support for EE Brightbox 2 router (#19611)
* Added device tracker support for EE Brightbox 2

* removed timeago dependency

* get scanner checks and improved tests

* fixed lint issues

* removed redundant timeago from test requirements

* fixed variable naming in test

* removed unecessary blank line
2019-01-22 09:16:35 +01:00
Johann Kellerman
5a30b0507d Add git to the development Dockerfile (#20276)
* git_on_dev

* feedback
2019-01-22 08:21:43 +01:00
Fabien Piuzzi
d419471372 Fix typo C02 to CO2 (#20306)
* Fix type C02 to CO2 and added VOC to air quality platform

* singularized volatile_organic_compound

* Remove VOC prop

* Update __init__.py

* Update __init__.py
2019-01-21 21:22:38 -08:00
Fabien Piuzzi
3e056a24dd Bugfix: prevent error notification when octoprint server auto detected but no configuration present. (#20303) 2019-01-21 21:21:59 -08:00
Johann Kellerman
6511e11ec9 Config Validator: schema_with_slug_keys (#20298)
* schema_with_slug_keys

* Update config_validation.py

* Update config_validation.py
2019-01-21 16:36:04 -08:00
Ted Drain
4b3cdb9f4e Add radiotherm is_on method to return on/off (#20283)
* Added state method to return current operating state to fix #18244 for radiotherm component.

* Changed to set the is_on property when actively heating or cooling.
2019-01-21 12:05:42 -08:00
Andrew Sayre
bb21cb6c89 Remove trailing slash from base_url and added ability to generate webhook path. (#20295) 2019-01-21 20:50:41 +01:00
Johann Kellerman
c36c708068 Align valid_entity_id with new slugify (#20231)
* slug

* ensure a dot

* fix

* schema_with_slug_keys

* lint

* test
2019-01-21 09:45:11 -08:00
Paulus Schoutsen
6ca0da5c52 Updated frontend to 20190121.0 2019-01-21 09:21:11 -08:00
Daniel Høyer Iversen
e4f42d1282 Update Tibber lib (#20289) 2019-01-21 16:12:03 +01:00
Andrey Kupreychik
ec9575a86f Added Xiaomi AirPurifier 2S profile (#20285) 2019-01-21 09:22:44 +01:00
Andrew Sayre
5c208da82e Added recursive detection of functools.partial. (#20284) 2019-01-20 22:27:32 -08:00
Jon Caruana
9482a6303d Add EverLights light component (#19817)
* EverLights light integration. Supports single color (with color and brightness parameters) or saved pattern (with effect parameter).

* Fix pylint parameter name warning.

* Code review feedback.

* Add tests for the two helper functions of EverLights component.

* Fixes for review feedback.

* Change test style.

* Style fixes for hound.
2019-01-21 04:23:36 +01:00
Paulus Schoutsen
5999df1953 Clean up build artifacts correctly 2019-01-20 17:31:09 -08:00
Phil Bruckner
935e5c67a3 Handle non-string values in JSON renderer (#20233)
Handle the case of async_render_with_possible_json_value's value argument
being something other than a string. This can happen, e.g., when using the
SQL sensor to extract a datetime column such as last_changed and also using
its value_template to convert that datetime to another format. This was
causing a TypeError from json.loads, but async_render_with_possible_json_value
was only catching ValueError's.
2019-01-20 16:46:14 -08:00
emontnemery
3fcbcd5a38 Add JSON attribute topic to MQTT alarm (#20238) 2019-01-20 16:42:56 -08:00
emontnemery
dbba3eb0d4 Add JSON attribute topic to MQTT climate (#20239) 2019-01-20 16:42:35 -08:00
emontnemery
89e9d827a2 Add JSON attribute topic to MQTT fan (#20240) 2019-01-20 16:42:17 -08:00
emontnemery
ab4e4787e3 Add JSON attribute topic to MQTT lock (#20241) 2019-01-20 16:41:50 -08:00
Paulus Schoutsen
b6e1675c46 Add JSON attribute topic to MQTT vacuum (#20242) 2019-01-20 16:36:24 -08:00
Teemu R
e69ca810e4 Print a message when reconnected after a connection failure, requirement for IQS silver (#20261) 2019-01-20 16:36:01 -08:00
Anders Melchiorsen
62844e237c Allow 'all' entity_id in service schema (#20278) 2019-01-20 16:33:39 -08:00
Anders Melchiorsen
1218127d83 Fix 'all' entity_id in service call extraction (#20281) 2019-01-20 16:33:11 -08:00
koreth
08a57959b9 Reduce log noise from Envisalink component (#20282) 2019-01-20 16:32:01 -08:00
Paulus Schoutsen
f771667c14 Updated frontend to 20190120.0 2019-01-20 16:22:42 -08:00
Andre Lengwenus
d5dcb8f140 Add discovery_info check to LCN light platform (#20280)
* Added discovery_info check to LCN light platform

* Removed whitespaces
2019-01-20 15:49:28 -08:00
Anders Melchiorsen
362ac725bf Remove double logging of automation action (#20264) 2019-01-21 00:10:12 +01:00
Ville Skyttä
58bb6f2e99 Add type hints to helpers.condition (#20266) 2019-01-21 00:03:12 +01:00
NotoriousBDG
5b8cb10ad7 Make Netatmo battery_percent icon dynamic (#20275) 2019-01-20 22:30:17 +01:00
Daniel Høyer Iversen
2eb5ce9dfe Update Tibber library (#20273) 2019-01-20 21:37:02 +01:00
koreth
fd2cff6b1c Fire events for Lutron RadioRA2 keypad buttons (#20090)
* Add binary sensor for Lutron RadioRA2 keypad buttons

Allow automations to be triggered from RadioRA2 keypads by exposing
each button as a binary sensor.

* Remove binary sensor component; fire events directly instead.

* Address comments from code review
2019-01-20 21:16:48 +01:00
kbickar
0e5fa010a7 Updated sense library to 0.6.0 (#20271) 2019-01-20 21:02:36 +01:00
Andre Lengwenus
7c25389f0d Add LCN switch platform (#20267)
* Add LCN switch platform

* Added guard clause for discovery_info check and removed unnecessary parathesis
2019-01-20 20:49:30 +01:00
Andre Lengwenus
a8d3a904e7 Support for relay ports for LCN light platform (#19632)
* Added relay ports to LCN lights platform

* Exchanged validation for ports with uppercase validator. Makes interfacing with pypck enums much more simple.

* Removed supported_features property as it is correctly inherited from parent

* Removed type annotations.
2019-01-20 14:50:29 +01:00
Matthew Wegner
6bf42ad43d Added Search Configuration to IMAP Sensor (#19749)
* Added Search Configuration to IMAP Sensor

The IMAP sensor currently only counts unread emails in a folder.  By exposing the IMAP search parameter, the sensor can be used to count other results:

- All emails in an inbox
- Emails sent from an address
- Emails matching a subject
- Other advanced searches, especially with vendor-specific extensions.  Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc)

For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread.  I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data.  This is my first Home Assistant contribution, so apologies in advance if something is out of place!  It's a pretty minimal modification.

* Added Server Response Checking

Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4).

* IMAP Warning -> Error, Count Initializes to None

IMAP search response parsing throws an error instead of a warning.

Email count initializes as None instead 0.

Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc).

Fixed line length on error so it fits under 80 characters.

* Fixed Indent on Logger Error

Sorry about the churn!  Python is pretty far from my daily-use language.  (I did run this one through pep8, at least)
2019-01-19 21:37:02 +01:00
Daniel Høyer Iversen
0987219b28 Tibber Pulse for homes without subscriptions (#20246) 2019-01-19 10:23:22 -08:00
Victor Vostrikov
fb52f66da0 Use local IP to discover IGD device (#20035)
* Use local_ip from config to discover IGD device

In case of multi-homed server UPNP discovery finds IGD device on some "default" interface. WIth this modification discovery will be performed from 'local_ip'.

* Update device.py

* Changed version of async_upnp_client in requirements

* Used aysnc_upnp_client==0.14.0

* Changed requirement to async_upnp_client==0.14.0.dev0

* Changed requirement to async_upnp_client==0.14.0.dev0

* Changed requirement to async_upnp_client==0.14.0.dev0

* Fixed code style

* Fixed code style

* Changed version of async_upnp_client in requerements

* Changed version of async_upnp_client in requirements

* Regenerated requirements (new async_upnp_client)

* Regenerated requirements (new async_upnp_client)

* Changed requirement to async_upnp_client=0.14.1

* Changed requirement to async_upnp_client=0.14.1

* Updated requirements

* Updated requirements.txt

* Corrected requirements

* Corrected import of DeviceState

* Constants changed according new async_upnp_client

* Upgraded for async_upnp_client==0.14.2
2019-01-19 17:08:53 +00:00
Otto Winter
8000b97180 Bump aioesphomeapi to 1.4.2 (#20247)
* Bump aioesphomeapi to 1.4.2

* Update requirements_all.txt
2019-01-19 17:13:32 +01:00
Erik
5b8f64093b Add JSON attribute topic to MQTT vacuum 2019-01-19 11:58:21 +01:00
Joakim Lindbom
440d479be8 Fix for issue #19086 (#20225) 2019-01-18 22:12:56 -08:00
Louis Matthijssen
e80702a45c Fix unused friendly name for SolarEdge sensor (#20109) 2019-01-18 15:02:27 +01:00
Anders Melchiorsen
63b19094c1 Improve Sonos discovery (#20196) 2019-01-18 13:43:48 +01:00
Rohan Kapoor
84b1fcbc36 Add verify_ssl to restful_command and switch.rest (#20199) (#20207) 2019-01-18 13:42:52 +01:00
zhujisheng
81a5208762 Add platform image_processing.qrcode (#20215)
* Add platform image_processing.qrcode

* Update qrcode.py
2019-01-18 13:40:49 +01:00
ehendrix23
afa019ae47 Set ehendrix23 as owner for harmony platform (#20203)
Put myself (ehendrix23) as code owner for remote.harmony platform
2019-01-17 19:35:45 -07:00
emontnemery
6800871c13 Log exceptions thrown by signal callbacks (#20015)
* Log exceptions thrown by signal callbacks

* Fix unsub

* Simplify traceback print

* Typing

* Add test

* lint

* Review comments

* Rework MQTT test case

* Fix bad merge

* Fix bad merge
2019-01-17 14:44:57 -08:00
emontnemery
f094a7369d Add JSON attribute topic to MQTT switch (#20192) 2019-01-17 10:55:22 -08:00
emontnemery
234f348ba1 Add JSON attribute topic to MQTT light (#20191) 2019-01-17 10:54:22 -08:00
emontnemery
d1c6eb4f3e Add JSON attribute topic to MQTT cover (#20190)
* Add JSON attribute topic to MQTT cover

* Lint
2019-01-17 10:53:52 -08:00
Rohan Kapoor
5232df34cb Add a Zoneminder availability sensor (#20184)
* Embed zoneminder platforms into component

* Add a binary sensor for ZoneMinder availability

* Lint

* Add missing docstrings
2019-01-17 10:52:53 -08:00
Paulus Schoutsen
0fe5d567a2 Add command to refresh auth (#20183) 2019-01-17 19:33:01 +01:00
Paulus Schoutsen
136364f5db Distribute reconnect (#20181) 2019-01-17 19:30:47 +01:00
Rohan Kapoor
2de6a94506 Embed zoneminder platforms into component (#20182) 2019-01-17 11:13:15 +01:00
Paulus Schoutsen
e1b63d9706 Sensibo to use HA operation modes (#20180) 2019-01-16 23:12:18 -08:00
Aaron Godfrey
27a8171a8b Remove color call to set lights to black. (#20176)
Calling clear all is enough to turn off the light.  Calling the color
command makes the light no longer function until clear all is called
again.  The component calls clear all beforing turning it on which is
why it works through home assistant.  However if you try to control the
light via the hyperion app or through kodi after it has been turned off
via home assistant it will not function until you call clear all again.
2019-01-16 21:19:52 -08:00
366 changed files with 13574 additions and 2549 deletions

View File

@@ -19,6 +19,9 @@ omit =
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
homeassistant/components/ambient_station/__init__.py
homeassistant/components/ambient_station/sensor.py
homeassistant/components/amcrest.py
homeassistant/components/*/amcrest.py
@@ -80,17 +83,24 @@ omit =
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/danfoss_air/*
homeassistant/components/dominos.py
homeassistant/components/doorbird.py
homeassistant/components/*/doorbird.py
homeassistant/components/dovado/*
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
homeassistant/components/eight_sleep.py
homeassistant/components/*/eight_sleep.py
homeassistant/components/ecoal_boiler.py
homeassistant/components/*/ecoal_boiler.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
@@ -163,13 +173,13 @@ omit =
homeassistant/components/hlk_sw16.py
homeassistant/components/*/hlk_sw16.py
homeassistant/components/homekit_controller/__init__.py
homeassistant/components/*/homekit_controller.py
homeassistant/components/homekit_controller/*
homeassistant/components/homematic/__init__.py
homeassistant/components/*/homematic.py
homeassistant/components/homematicip_cloud.py
homeassistant/components/homematicip_cloud/hap.py
homeassistant/components/homematicip_cloud/device.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/homeworks.py
@@ -384,6 +394,9 @@ omit =
homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py
homeassistant/components/transmission.py
homeassistant/components/*/transmission.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
@@ -443,15 +456,19 @@ omit =
homeassistant/components/zha/sensor.py
homeassistant/components/zha/switch.py
homeassistant/components/zha/api.py
homeassistant/components/zha/entities/*
homeassistant/components/zha/helpers.py
homeassistant/components/zha/entity.py
homeassistant/components/zha/device_entity.py
homeassistant/components/zha/core/helpers.py
homeassistant/components/zha/core/const.py
homeassistant/components/zha/core/device.py
homeassistant/components/zha/core/listeners.py
homeassistant/components/zha/core/gateway.py
homeassistant/components/*/zha.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zoneminder/*
homeassistant/components/*/zoneminder.py
homeassistant/components/tuya.py
homeassistant/components/*/tuya.py
@@ -460,6 +477,7 @@ omit =
homeassistant/components/*/spider.py
homeassistant/components/air_quality/opensensemap.py
homeassistant/components/air_quality/nilu.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -552,6 +570,7 @@ omit =
homeassistant/components/device_tracker/sky_hub.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/device_tracker/swisscom.py
homeassistant/components/device_tracker/synology_srm.py
homeassistant/components/device_tracker/tado.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tile.py
@@ -574,6 +593,7 @@ omit =
homeassistant/components/image_processing/dlib_face_identify.py
homeassistant/components/image_processing/seven_segments.py
homeassistant/components/image_processing/tensorflow.py
homeassistant/components/image_processing/qrcode.py
homeassistant/components/keyboard_remote.py
homeassistant/components/keyboard.py
homeassistant/components/light/avion.py
@@ -581,6 +601,7 @@ omit =
homeassistant/components/light/blinkt.py
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/decora.py
homeassistant/components/light/everlights.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/futurenow.py
homeassistant/components/light/greenwave.py
@@ -719,7 +740,6 @@ omit =
homeassistant/components/sensor/aftership.py
homeassistant/components/sensor/airvisual.py
homeassistant/components/sensor/alpha_vantage.py
homeassistant/components/sensor/ambient_station.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
@@ -744,7 +764,6 @@ omit =
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/discogs.py
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/domain_expiry.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/dublin_bus_transport.py
@@ -781,6 +800,7 @@ omit =
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/iliad_italy.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
@@ -835,7 +855,9 @@ omit =
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/rainbird.py
homeassistant/components/sensor/recollect_waste.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/rova.py
homeassistant/components/sensor/rtorrent.py
homeassistant/components/sensor/ruter.py
homeassistant/components/sensor/scrape.py
@@ -874,7 +896,6 @@ omit =
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/trafikverket_weatherstation.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
@@ -919,7 +940,6 @@ omit =
homeassistant/components/switch/switchmate.py
homeassistant/components/switch/telnet.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/vesync.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py

View File

@@ -95,6 +95,7 @@ homeassistant/components/notify/syslog.py @fabaff
homeassistant/components/notify/xmpp.py @fabaff
homeassistant/components/notify/yessssms.py @flowolf
homeassistant/components/plant.py @ChristianKuehnel
homeassistant/components/remote/harmony.py @ehendrix23
homeassistant/components/scene/lifx_cloud.py @amelchio
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/alpha_vantage.py @fabaff
@@ -152,6 +153,7 @@ homeassistant/components/weather/openweathermap.py @fabaff
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
# A
homeassistant/components/ambient_station/* @bachya
homeassistant/components/arduino.py @fabaff
homeassistant/components/*/arduino.py @fabaff
homeassistant/components/*/arest.py @fabaff
@@ -234,6 +236,7 @@ homeassistant/components/*/rfxtrx.py @danielhiversen
# S
homeassistant/components/simplisafe/* @bachya
homeassistant/components/smartthings/* @andrewsayre
# T
homeassistant/components/tahoma.py @philklei
@@ -268,8 +271,7 @@ homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
# Z
homeassistant/components/zoneminder/ @rohankapoorcom
homeassistant/components/*/zoneminder.py @rohankapoorcom
homeassistant/components/zoneminder/* @rohankapoorcom
# Other code
homeassistant/scripts/check_config.py @kellerza

View File

@@ -2,7 +2,8 @@
import base64
from collections import OrderedDict
import logging
from typing import Any, Dict, List, Optional, cast
from typing import Any, Dict, List, Optional, Set, cast # noqa: F401
import bcrypt
import voluptuous as vol
@@ -52,6 +53,9 @@ class Data:
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
self._data = None # type: Optional[Dict[str, Any]]
# Legacy mode will allow usernames to start/end with whitespace
# and will compare usernames case-insensitive.
# Remove in 2020 or when we launch 1.0.
self.is_legacy = False
@callback
@@ -60,7 +64,7 @@ class Data:
if self.is_legacy:
return username
return username.strip()
return username.strip().casefold()
async def async_load(self) -> None:
"""Load stored data."""
@@ -71,9 +75,26 @@ class Data:
'users': []
}
seen = set() # type: Set[str]
for user in data['users']:
username = user['username']
# check if we have duplicates
folded = username.casefold()
if folded in seen:
self.is_legacy = True
logging.getLogger(__name__).warning(
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that are case-insensitive"
"equivalent. Please change the username: '%s'.", username)
break
seen.add(folded)
# check if we have unstripped usernames
if username != username.strip():
self.is_legacy = True
@@ -81,7 +102,7 @@ class Data:
logging.getLogger(__name__).warning(
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that start or end in a "
"space. Please change the username.")
"space. Please change the username: '%s'.", username)
break
@@ -103,7 +124,7 @@ class Data:
# Compare all users to avoid timing attacks.
for user in self.users:
if username == user['username']:
if self.normalize_username(user['username']) == username:
found = user
if found is None:

View File

@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
ATTR_AQI = 'air_quality_index'
ATTR_ATTRIBUTION = 'attribution'
ATTR_C02 = 'carbon_dioxide'
ATTR_CO2 = 'carbon_dioxide'
ATTR_CO = 'carbon_monoxide'
ATTR_N2O = 'nitrogen_oxide'
ATTR_NO = 'nitrogen_monoxide'
@@ -35,7 +35,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
PROP_TO_ATTR = {
'air_quality_index': ATTR_AQI,
'attribution': ATTR_ATTRIBUTION,
'carbon_dioxide': ATTR_C02,
'carbon_dioxide': ATTR_CO2,
'carbon_monoxide': ATTR_CO,
'nitrogen_oxide': ATTR_N2O,
'nitrogen_monoxide': ATTR_NO,

View File

@@ -0,0 +1,252 @@
"""
Sensor for checking the air quality around Norway.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/air_quality.nilu/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.air_quality import (
PLATFORM_SCHEMA, AirQualityEntity)
from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_SHOW_ON_MAP)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['niluclient==0.1.2']
_LOGGER = logging.getLogger(__name__)
ATTR_AREA = 'area'
ATTR_POLLUTION_INDEX = 'nilu_pollution_index'
ATTRIBUTION = "Data provided by luftkvalitet.info and nilu.no"
CONF_AREA = 'area'
CONF_STATION = 'stations'
DEFAULT_NAME = 'NILU'
SCAN_INTERVAL = timedelta(minutes=30)
CONF_ALLOWED_AREAS = [
'Bergen',
'Birkenes',
'Bodø',
'Brumunddal',
'Bærum',
'Drammen',
'Elverum',
'Fredrikstad',
'Gjøvik',
'Grenland',
'Halden',
'Hamar',
'Harstad',
'Hurdal',
'Karasjok',
'Kristiansand',
'Kårvatn',
'Lillehammer',
'Lillesand',
'Lillestrøm',
'Lørenskog',
'Mo i Rana',
'Moss',
'Narvik',
'Oslo',
'Prestebakke',
'Sandve',
'Sarpsborg',
'Stavanger',
'Sør-Varanger',
'Tromsø',
'Trondheim',
'Tustervatn',
'Zeppelinfjellet',
'Ålesund',
]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Inclusive(CONF_LATITUDE, 'coordinates',
'Latitude and longitude must exist together'): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, 'coordinates',
'Latitude and longitude must exist together'): cv.longitude,
vol.Exclusive(CONF_AREA, 'station_collection',
'Can only configure one specific station or '
'stations in a specific area pr sensor. '
'Please only configure station or area.'
): vol.All(cv.string, vol.In(CONF_ALLOWED_AREAS)),
vol.Exclusive(CONF_STATION, 'station_collection',
'Can only configure one specific station or '
'stations in a specific area pr sensor. '
'Please only configure station or area.'
): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the NILU air quality sensor."""
import niluclient as nilu
name = config.get(CONF_NAME)
area = config.get(CONF_AREA)
stations = config.get(CONF_STATION)
show_on_map = config.get(CONF_SHOW_ON_MAP)
sensors = []
if area:
stations = nilu.lookup_stations_in_area(area)
elif not area and not stations:
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
location_client = nilu.create_location_client(latitude, longitude)
stations = location_client.station_names
for station in stations:
client = NiluData(nilu.create_station_client(station))
client.update()
if client.data.sensors:
sensors.append(NiluSensor(client, name, show_on_map))
else:
_LOGGER.warning("%s didn't give any sensors results", station)
add_entities(sensors, True)
class NiluData:
"""Class for handling the data retrieval."""
def __init__(self, api):
"""Initialize the data object."""
self.api = api
@property
def data(self):
"""Get data cached in client."""
return self.api.data
@Throttle(SCAN_INTERVAL)
def update(self):
"""Get the latest data from nilu API."""
self.api.update()
class NiluSensor(AirQualityEntity):
"""Single nilu station air sensor."""
def __init__(self, api_data: NiluData, name: str, show_on_map: bool):
"""Initialize the sensor."""
self._api = api_data
self._name = "{} {}".format(name, api_data.data.name)
self._max_aqi = None
self._attrs = {}
if show_on_map:
self._attrs[CONF_LATITUDE] = api_data.data.latitude
self._attrs[CONF_LONGITUDE] = api_data.data.longitude
@property
def attribution(self) -> str:
"""Return the attribution."""
return ATTRIBUTION
@property
def device_state_attributes(self) -> dict:
"""Return other details about the sensor state."""
return self._attrs
@property
def name(self) -> str:
"""Return the name of the sensor."""
return self._name
@property
def air_quality_index(self) -> str:
"""Return the Air Quality Index (AQI)."""
return self._max_aqi
@property
def carbon_monoxide(self) -> str:
"""Return the CO (carbon monoxide) level."""
from niluclient import CO
return self.get_component_state(CO)
@property
def carbon_dioxide(self) -> str:
"""Return the CO2 (carbon dioxide) level."""
from niluclient import CO2
return self.get_component_state(CO2)
@property
def nitrogen_oxide(self) -> str:
"""Return the N2O (nitrogen oxide) level."""
from niluclient import NOX
return self.get_component_state(NOX)
@property
def nitrogen_monoxide(self) -> str:
"""Return the NO (nitrogen monoxide) level."""
from niluclient import NO
return self.get_component_state(NO)
@property
def nitrogen_dioxide(self) -> str:
"""Return the NO2 (nitrogen dioxide) level."""
from niluclient import NO2
return self.get_component_state(NO2)
@property
def ozone(self) -> str:
"""Return the O3 (ozone) level."""
from niluclient import OZONE
return self.get_component_state(OZONE)
@property
def particulate_matter_2_5(self) -> str:
"""Return the particulate matter 2.5 level."""
from niluclient import PM25
return self.get_component_state(PM25)
@property
def particulate_matter_10(self) -> str:
"""Return the particulate matter 10 level."""
from niluclient import PM10
return self.get_component_state(PM10)
@property
def particulate_matter_0_1(self) -> str:
"""Return the particulate matter 0.1 level."""
from niluclient import PM1
return self.get_component_state(PM1)
@property
def sulphur_dioxide(self) -> str:
"""Return the SO2 (sulphur dioxide) level."""
from niluclient import SO2
return self.get_component_state(SO2)
def get_component_state(self, component_name: str) -> str:
"""Return formatted value of specified component."""
if component_name in self._api.data.sensors:
sensor = self._api.data.sensors[component_name]
return sensor.value
return None
def update(self) -> None:
"""Update the sensor."""
import niluclient as nilu
self._api.update()
sensors = self._api.data.sensors.values()
if sensors:
max_index = max([s.pollution_index for s in sensors])
self._max_aqi = max_index
self._attrs[ATTR_POLLUTION_INDEX] = \
nilu.POLLUTION_INDEX[self._max_aqi]
self._attrs[ATTR_AREA] = self._api.data.area

View File

@@ -13,7 +13,8 @@ from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent

View File

@@ -6,9 +6,9 @@ https://home-assistant.io/components/alarm_control_panel.abode/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
@@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(alarm_devices)
class AbodeAlarm(AbodeDevice, AlarmControlPanel):
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
"""An alarm_control_panel implementation for Abode."""
def __init__(self, data, device, name):
@@ -57,6 +57,11 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
state = None
return state
@property
def code_format(self):
"""Return one or more digits/characters."""
return alarm.FORMAT_NUMBER
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._device.set_standby()

View File

@@ -13,7 +13,7 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
@@ -57,7 +57,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._username = username
self._password = password
self._websession = async_get_clientsession(self._hass)
self._state = STATE_UNKNOWN
self._state = None
self._alarm = Alarmdotcom(
username, password, self._websession, hass.loop)
@@ -93,7 +93,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
if self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
return STATE_UNKNOWN
return None
@property
def device_state_attributes(self):

View File

@@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.concord232/
"""
import datetime
from datetime import timedelta
import logging
import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
REQUIREMENTS = ['concord232==0.15']
@@ -26,7 +25,7 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
SCAN_INTERVAL = timedelta(seconds=10)
SCAN_INTERVAL = datetime.timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
@@ -44,33 +43,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
url = 'http://{}:{}'.format(host, port)
try:
add_entities([Concord232Alarm(hass, url, name)])
add_entities([Concord232Alarm(url, name)], True)
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return
class Concord232Alarm(alarm.AlarmControlPanel):
"""Representation of the Concord232-based alarm panel."""
def __init__(self, hass, url, name):
def __init__(self, url, name):
"""Initialize the Concord232 alarm panel."""
from concord232 import client as concord232_client
self._state = STATE_UNKNOWN
self._hass = hass
self._state = None
self._name = name
self._url = url
try:
client = concord232_client.Client(self._url)
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
self._alarm = client
self._alarm = concord232_client.Client(self._url)
self._alarm.partitions = self._alarm.list_partitions()
self._alarm.last_partition_update = datetime.datetime.now()
self.update()
@property
def name(self):
@@ -94,22 +84,17 @@ class Concord232Alarm(alarm.AlarmControlPanel):
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
dict(host=self._url, reason=ex))
newstate = STATE_UNKNOWN
return
except IndexError:
_LOGGER.error("Concord232 reports no partitions")
newstate = STATE_UNKNOWN
return
if part['arming_level'] == 'Off':
newstate = STATE_ALARM_DISARMED
self._state = STATE_ALARM_DISARMED
elif 'Home' in part['arming_level']:
newstate = STATE_ALARM_ARMED_HOME
self._state = STATE_ALARM_ARMED_HOME
else:
newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state:
_LOGGER.info("State change from %s to %s", self._state, newstate)
self._state = newstate
return self._state
self._state = STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -76,7 +76,7 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
await self._home.set_security_zones_activation(True, False)
await self._home.set_security_zones_activation(False, True)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""

View File

@@ -14,7 +14,7 @@ from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, CONF_NAME,
STATE_ALARM_ARMED_CUSTOM_BYPASS)
@@ -52,7 +52,7 @@ class TotalConnect(alarm.AlarmControlPanel):
self._name = name
self._username = username
self._password = password
self._state = STATE_UNKNOWN
self._state = None
self._client = TotalConnectClient.TotalConnectClient(
username, password)
@@ -85,7 +85,7 @@ class TotalConnect(alarm.AlarmControlPanel):
elif status == self._client.DISARMING:
state = STATE_ALARM_DISARMING
else:
state = STATE_UNKNOWN
state = None
self._state = state

View File

@@ -11,8 +11,7 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
from homeassistant.components.verisure import HUB as hub
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
_LOGGER = logging.getLogger(__name__)
@@ -44,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
def __init__(self):
"""Initialize the Verisure alarm panel."""
self._state = STATE_UNKNOWN
self._state = None
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None

View File

@@ -9,8 +9,7 @@ import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
_LOGGER = logging.getLogger(__name__)
@@ -52,7 +51,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
elif wink_state == "night":
state = STATE_ALARM_ARMED_HOME
else:
state = STATE_UNKNOWN
state = None
return state
def alarm_disarm(self, code=None):

View File

@@ -5,19 +5,19 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alert/
"""
import asyncio
from datetime import datetime, timedelta
import logging
from datetime import datetime, timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, DOMAIN as DOMAIN_NOTIFY)
from homeassistant.const import (
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
@@ -30,6 +30,8 @@ CONF_REPEAT = 'repeat'
CONF_SKIP_FIRST = 'skip_first'
CONF_ALERT_MESSAGE = 'message'
CONF_DONE_MESSAGE = 'done_message'
CONF_TITLE = 'title'
CONF_DATA = 'data'
DEFAULT_CAN_ACK = True
DEFAULT_SKIP_FIRST = False
@@ -43,13 +45,14 @@ ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean,
vol.Optional(CONF_ALERT_MESSAGE): cv.template,
vol.Optional(CONF_DONE_MESSAGE): cv.template,
vol.Optional(CONF_TITLE): cv.template,
vol.Optional(CONF_DATA): dict,
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA),
}, extra=vol.ALLOW_EXTRA)
ALERT_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
})
@@ -77,12 +80,14 @@ async def async_setup(hass, config):
done_message_template = cfg.get(CONF_DONE_MESSAGE)
notifiers = cfg.get(CONF_NOTIFIERS)
can_ack = cfg.get(CONF_CAN_ACK)
title_template = cfg.get(CONF_TITLE)
data = cfg.get(CONF_DATA)
entities.append(Alert(hass, object_id, name,
watched_entity_id, alert_state, repeat,
skip_first, message_template,
done_message_template, notifiers,
can_ack))
can_ack, title_template, data))
if not entities:
return False
@@ -127,12 +132,14 @@ class Alert(ToggleEntity):
def __init__(self, hass, entity_id, name, watched_entity_id,
state, repeat, skip_first, message_template,
done_message_template, notifiers, can_ack):
done_message_template, notifiers, can_ack, title_template,
data):
"""Initialize the alert."""
self.hass = hass
self._name = name
self._alert_state = state
self._skip_first = skip_first
self._data = data
self._message_template = message_template
if self._message_template is not None:
@@ -142,6 +149,10 @@ class Alert(ToggleEntity):
if self._done_message_template is not None:
self._done_message_template.hass = hass
self._title_template = title_template
if self._title_template is not None:
self._title_template.hass = hass
self._notifiers = notifiers
self._can_ack = can_ack
@@ -251,9 +262,20 @@ class Alert(ToggleEntity):
await self._send_notification_message(message)
async def _send_notification_message(self, message):
msg_payload = {ATTR_MESSAGE: message}
if self._title_template is not None:
title = self._title_template.async_render()
msg_payload.update({ATTR_TITLE: title})
if self._data:
msg_payload.update({ATTR_DATA: self._data})
_LOGGER.debug(msg_payload)
for target in self._notifiers:
await self.hass.services.async_call(
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: message})
DOMAIN_NOTIFY, target, msg_payload)
async def async_turn_on(self, **kwargs):
"""Async Unacknowledge alert."""

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "Application Key and/or API Key already registered",
"invalid_key": "Invalid API Key and/or Application Key",
"no_devices": "No devices found in account"
},
"step": {
"user": {
"data": {
"api_key": "API Key",
"app_key": "Application Key"
},
"title": "Fill in your information"
}
},
"title": "Ambient PWS"
}
}

View File

@@ -0,0 +1,212 @@
"""
Support for Ambient Weather Station Service.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ambient_station/
"""
import logging
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
CONF_UNIT_SYSTEM, EVENT_HOMEASSISTANT_STOP)
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from .config_flow import configured_instances
from .const import (
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI,
UNITS_US)
REQUIREMENTS = ['aioambient==0.1.0']
_LOGGER = logging.getLogger(__name__)
DEFAULT_SOCKET_MIN_RETRY = 15
SENSOR_TYPES = {
'24hourrainin': ['24 Hr Rain', 'in'],
'baromabsin': ['Abs Pressure', 'inHg'],
'baromrelin': ['Rel Pressure', 'inHg'],
'battout': ['Battery', ''],
'co2': ['co2', 'ppm'],
'dailyrainin': ['Daily Rain', 'in'],
'dewPoint': ['Dew Point', ['°F', '°C']],
'eventrainin': ['Event Rain', 'in'],
'feelsLike': ['Feels Like', ['°F', '°C']],
'hourlyrainin': ['Hourly Rain Rate', 'in/hr'],
'humidity': ['Humidity', '%'],
'humidityin': ['Humidity In', '%'],
'lastRain': ['Last Rain', ''],
'maxdailygust': ['Max Gust', 'mph'],
'monthlyrainin': ['Monthly Rain', 'in'],
'solarradiation': ['Solar Rad', 'W/m^2'],
'tempf': ['Temp', ['°F', '°C']],
'tempinf': ['Inside Temp', ['°F', '°C']],
'totalrainin': ['Lifetime Rain', 'in'],
'uv': ['uv', 'Index'],
'weeklyrainin': ['Weekly Rain', 'in'],
'winddir': ['Wind Dir', '°'],
'winddir_avg10m': ['Wind Dir Avg 10m', '°'],
'winddir_avg2m': ['Wind Dir Avg 2m', 'mph'],
'windgustdir': ['Gust Dir', '°'],
'windgustmph': ['Wind Gust', 'mph'],
'windspdmph_avg10m': ['Wind Avg 10m', 'mph'],
'windspdmph_avg2m': ['Wind Avg 2m', 'mph'],
'windspeedmph': ['Wind Speed', 'mph'],
'yearlyrainin': ['Yearly Rain', 'in'],
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN:
vol.Schema({
vol.Required(CONF_APP_KEY):
cv.string,
vol.Required(CONF_API_KEY):
cv.string,
vol.Optional(
CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_UNIT_SYSTEM):
vol.In([UNITS_SI, UNITS_US]),
})
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the Ambient PWS component."""
hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_CLIENT] = {}
if DOMAIN not in config:
return True
conf = config[DOMAIN]
if conf[CONF_APP_KEY] in configured_instances(hass):
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={'source': SOURCE_IMPORT}, data=conf))
return True
async def async_setup_entry(hass, config_entry):
"""Set up the Ambient PWS as config entry."""
from aioambient import Client
from aioambient.errors import WebsocketConnectionError
session = aiohttp_client.async_get_clientsession(hass)
try:
ambient = AmbientStation(
hass,
config_entry,
Client(
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY], session),
config_entry.data.get(
CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)),
config_entry.data.get(CONF_UNIT_SYSTEM))
hass.loop.create_task(ambient.ws_connect())
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient
except WebsocketConnectionError as err:
_LOGGER.error('Config entry failed: %s', err)
raise ConfigEntryNotReady
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect())
return True
async def async_unload_entry(hass, config_entry):
"""Unload an Ambient PWS config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
hass.async_create_task(ambient.ws_disconnect())
await hass.config_entries.async_forward_entry_unload(
config_entry, 'sensor')
return True
class AmbientStation:
"""Define a class to handle the Ambient websocket."""
def __init__(
self, hass, config_entry, client, monitored_conditions,
unit_system):
"""Initialize."""
self._config_entry = config_entry
self._hass = hass
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
self.client = client
self.monitored_conditions = monitored_conditions
self.stations = {}
self.unit_system = unit_system
async def ws_connect(self):
"""Register handlers and connect to the websocket."""
from aioambient.errors import WebsocketError
def on_connect():
"""Define a handler to fire when the websocket is connected."""
_LOGGER.info('Connected to websocket')
def on_data(data):
"""Define a handler to fire when the data is received."""
mac_address = data['macAddress']
if data != self.stations[mac_address][ATTR_LAST_DATA]:
_LOGGER.debug('New data received: %s', data)
self.stations[mac_address][ATTR_LAST_DATA] = data
async_dispatcher_send(self._hass, TOPIC_UPDATE)
def on_disconnect():
"""Define a handler to fire when the websocket is disconnected."""
_LOGGER.info('Disconnected from websocket')
def on_subscribed(data):
"""Define a handler to fire when the subscription is set."""
for station in data['devices']:
if station['macAddress'] in self.stations:
continue
_LOGGER.debug('New station subscription: %s', data)
self.stations[station['macAddress']] = {
ATTR_LAST_DATA: station['lastData'],
ATTR_LOCATION: station['info']['location'],
ATTR_NAME: station['info']['name'],
}
self._hass.async_create_task(
self._hass.config_entries.async_forward_entry_setup(
self._config_entry, 'sensor'))
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
self.client.websocket.on_connect(on_connect)
self.client.websocket.on_data(on_data)
self.client.websocket.on_disconnect(on_disconnect)
self.client.websocket.on_subscribed(on_subscribed)
try:
await self.client.websocket.connect()
except WebsocketError as err:
_LOGGER.error("Error with the websocket connection: %s", err)
self._ws_reconnect_delay = min(
2 * self._ws_reconnect_delay, 480)
async_call_later(
self._hass, self._ws_reconnect_delay, self.ws_connect)
async def ws_disconnect(self):
"""Disconnect from the websocket."""
await self.client.websocket.disconnect()

View File

@@ -0,0 +1,72 @@
"""Config flow to configure the Ambient PWS component."""
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import CONF_APP_KEY, DOMAIN
@callback
def configured_instances(hass):
"""Return a set of configured Ambient PWS instances."""
return set(
entry.data[CONF_APP_KEY]
for entry in hass.config_entries.async_entries(DOMAIN))
@config_entries.HANDLERS.register(DOMAIN)
class AmbientStationFlowHandler(config_entries.ConfigFlow):
"""Handle an Ambient PWS config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
async def _show_form(self, errors=None):
"""Show the form to the user."""
data_schema = vol.Schema({
vol.Required(CONF_API_KEY): str,
vol.Required(CONF_APP_KEY): str,
})
return self.async_show_form(
step_id='user',
data_schema=data_schema,
errors=errors if errors else {},
)
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)
async def async_step_user(self, user_input=None):
"""Handle the start of the config flow."""
from aioambient import Client
from aioambient.errors import AmbientError
if not user_input:
return await self._show_form()
if user_input[CONF_APP_KEY] in configured_instances(self.hass):
return await self._show_form({CONF_APP_KEY: 'identifier_exists'})
session = aiohttp_client.async_get_clientsession(self.hass)
client = Client(
user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session)
try:
devices = await client.api.get_devices()
except AmbientError:
return await self._show_form({'base': 'invalid_key'})
if not devices:
return await self._show_form({'base': 'no_devices'})
# The Application Key (which identifies each config entry) is too long
# to show nicely in the UI, so we take the first 12 characters (similar
# to how GitHub does it):
return self.async_create_entry(
title=user_input[CONF_APP_KEY][:12], data=user_input)

View File

@@ -0,0 +1,13 @@
"""Define constants for the Ambient PWS component."""
DOMAIN = 'ambient_station'
ATTR_LAST_DATA = 'last_data'
CONF_APP_KEY = 'app_key'
DATA_CLIENT = 'data_client'
TOPIC_UPDATE = 'update'
UNITS_SI = 'si'
UNITS_US = 'us'

View File

@@ -0,0 +1,115 @@
"""
Support for Ambient Weather Station Service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ambient_station/
"""
import logging
from homeassistant.components.ambient_station import SENSOR_TYPES
from homeassistant.helpers.entity import Entity
from homeassistant.const import ATTR_NAME
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI, UNITS_US)
DEPENDENCIES = ['ambient_station']
_LOGGER = logging.getLogger(__name__)
UNIT_SYSTEM = {UNITS_US: 0, UNITS_SI: 1}
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up an Ambient PWS sensor based on existing config."""
pass
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up an Ambient PWS sensor based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
if ambient.unit_system:
sys_units = ambient.unit_system
elif hass.config.units.is_metric:
sys_units = UNITS_SI
else:
sys_units = UNITS_US
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions:
name, unit = SENSOR_TYPES[condition]
if isinstance(unit, list):
unit = unit[UNIT_SYSTEM[sys_units]]
sensor_list.append(
AmbientWeatherSensor(
ambient, mac_address, station[ATTR_NAME], condition, name,
unit))
async_add_entities(sensor_list, True)
class AmbientWeatherSensor(Entity):
"""Define an Ambient sensor."""
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
units):
"""Initialize the sensor."""
self._ambient = ambient
self._async_unsub_dispatcher_connect = None
self._mac_address = mac_address
self._sensor_name = sensor_name
self._sensor_type = sensor_type
self._state = None
self._station_name = station_name
self._units = units
@property
def name(self):
"""Return the name of the sensor."""
return '{0}_{1}'.format(self._station_name, self._sensor_name)
@property
def should_poll(self):
"""Disable polling."""
return False
@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 self._units
@property
def unique_id(self):
"""Return a unique, unchanging string that represents this sensor."""
return '{0}_{1}'.format(self._mac_address, self._sensor_name)
async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()
async def async_update(self):
"""Fetch new state data for the sensor."""
self._state = self._ambient.stations[
self._mac_address][ATTR_LAST_DATA].get(self._sensor_type)

View File

@@ -0,0 +1,19 @@
{
"config": {
"title": "Ambient PWS",
"step": {
"user": {
"title": "Fill in your information",
"data": {
"api_key": "API Key",
"app_key": "Application Key"
}
}
},
"error": {
"identifier_exists": "Application Key and/or API Key already registered",
"invalid_key": "Invalid API Key and/or Application Key",
"no_devices": "No devices found in account"
}
}
}

View File

@@ -123,8 +123,9 @@ ICON_MAP = {
'whitebalance_lock': 'mdi:white-balance-auto'
}
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
'overlay', 'torch', 'whitebalance_lock', 'video_recording']
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active',
'motion_detect', 'night_vision', 'overlay',
'torch', 'whitebalance_lock', 'video_recording']
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',

View File

@@ -14,7 +14,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
REQUIREMENTS = ['aioasuswrt==1.1.17']
REQUIREMENTS = ['aioasuswrt==1.1.20']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,19 +15,23 @@ import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
CONF_ENCODING = 'encoding'
CONF_TOPIC = 'topic'
DEFAULT_ENCODING = 'utf-8'
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
})
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
topic = config[CONF_TOPIC]
payload = config.get(CONF_PAYLOAD)
encoding = config[CONF_ENCODING] or None
@callback
def mqtt_automation_listener(msg_topic, msg_payload, qos):
@@ -50,5 +54,5 @@ async def async_trigger(hass, config, action, automation_info):
})
remove = await mqtt.async_subscribe(
hass, topic, mqtt_automation_listener)
hass, topic, mqtt_automation_listener, encoding=encoding)
return remove

View File

@@ -15,8 +15,6 @@ DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
STATE_SMOKE_OFF = 'IDLE_OFF'
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@@ -65,7 +63,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
return True
if self._device.windowState is None:
return None
return self._device.windowState == WindowState.OPEN
return self._device.windowState != WindowState.CLOSED
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
@@ -95,7 +93,9 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
@property
def is_on(self):
"""Return true if smoke is detected."""
return self._device.smokeDetectorAlarmType != STATE_SMOKE_OFF
from homematicip.base.enums import SmokeDetectorAlarmType
return (self._device.smokeDetectorAlarmType
!= SmokeDetectorAlarmType.IDLE_OFF)
class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice):

View File

@@ -8,7 +8,6 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.maxcube import DATA_KEY
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
@@ -40,7 +39,7 @@ class MaxCubeShutter(BinarySensorDevice):
self._sensor_type = 'window'
self._rf_address = rf_address
self._cubehandle = handler
self._state = STATE_UNKNOWN
self._state = None
@property
def should_poll(self):

View File

@@ -22,7 +22,7 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import utcnow
REQUIREMENTS = ['numpy==1.15.4']
REQUIREMENTS = ['numpy==1.16.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
REQUIREMENTS = ['blinkpy==0.11.2']
REQUIREMENTS = ['blinkpy==0.12.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,7 +15,7 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['libpyfoscam==1.0']
REQUIREMENTS = ['pyfoscam==1.2']
CONF_IP = 'ip'
@@ -43,7 +43,7 @@ class FoscamCam(Camera):
def __init__(self, device_info):
"""Initialize a Foscam camera."""
from libpyfoscam import FoscamCamera
from foscam import FoscamCamera
super(FoscamCam, self).__init__()

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
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, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS)
DEFAULT_MIN_TEMP = 7
@@ -208,7 +208,7 @@ class ClimateDevice(Entity):
return self.current_operation
if self.is_on:
return STATE_ON
return STATE_UNKNOWN
return None
@property
def precision(self):

View File

@@ -19,7 +19,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['millheater==0.3.3']
REQUIREMENTS = ['millheater==0.3.4']
_LOGGER = logging.getLogger(__name__)

View File

@@ -18,7 +18,7 @@ from homeassistant.components.climate import (
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE)
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT,
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['nest']
@@ -163,7 +163,7 @@ class NestThermostat(ClimateDevice):
return self._mode
if self._mode == NEST_MODE_HEAT_COOL:
return STATE_AUTO
return STATE_UNKNOWN
return None
@property
def target_temperature(self):

View File

@@ -219,6 +219,11 @@ class RadioThermostat(ClimateDevice):
"""Return true if away mode is on."""
return self._away
@property
def is_on(self):
"""Return true if on."""
return self._tstate != STATE_IDLE
def update(self):
"""Update and validate the data from the thermostat."""
# Radio thermostats are very slow, and sometimes don't respond

View File

@@ -14,6 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = (
'area_registry',
'auth',
'auth_provider_homeassistant',
'automation',

View File

@@ -0,0 +1,126 @@
"""HTTP views to interact with the area registry."""
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.decorators import (
async_response, require_admin)
from homeassistant.core import callback
from homeassistant.helpers.area_registry import async_get_registry
DEPENDENCIES = ['websocket_api']
WS_TYPE_LIST = 'config/area_registry/list'
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_LIST,
})
WS_TYPE_CREATE = 'config/area_registry/create'
SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_CREATE,
vol.Required('name'): str,
})
WS_TYPE_DELETE = 'config/area_registry/delete'
SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_DELETE,
vol.Required('area_id'): str,
})
WS_TYPE_UPDATE = 'config/area_registry/update'
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_UPDATE,
vol.Required('area_id'): str,
vol.Required('name'): str,
})
async def async_setup(hass):
"""Enable the Area Registry views."""
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list_areas, SCHEMA_WS_LIST
)
hass.components.websocket_api.async_register_command(
WS_TYPE_CREATE, websocket_create_area, SCHEMA_WS_CREATE
)
hass.components.websocket_api.async_register_command(
WS_TYPE_DELETE, websocket_delete_area, SCHEMA_WS_DELETE
)
hass.components.websocket_api.async_register_command(
WS_TYPE_UPDATE, websocket_update_area, SCHEMA_WS_UPDATE
)
return True
@async_response
async def websocket_list_areas(hass, connection, msg):
"""Handle list areas command."""
registry = await async_get_registry(hass)
connection.send_message(websocket_api.result_message(
msg['id'], [{
'name': entry.name,
'area_id': entry.id,
} for entry in registry.async_list_areas()]
))
@require_admin
@async_response
async def websocket_create_area(hass, connection, msg):
"""Create area command."""
registry = await async_get_registry(hass)
try:
entry = registry.async_create(msg['name'])
except ValueError as err:
connection.send_message(websocket_api.error_message(
msg['id'], 'invalid_info', str(err)
))
else:
connection.send_message(websocket_api.result_message(
msg['id'], _entry_dict(entry)
))
@require_admin
@async_response
async def websocket_delete_area(hass, connection, msg):
"""Delete area command."""
registry = await async_get_registry(hass)
try:
await registry.async_delete(msg['area_id'])
except KeyError:
connection.send_message(websocket_api.error_message(
msg['id'], 'invalid_info', "Area ID doesn't exist"
))
else:
connection.send_message(websocket_api.result_message(
msg['id'], 'success'
))
@require_admin
@async_response
async def websocket_update_area(hass, connection, msg):
"""Handle update area websocket command."""
registry = await async_get_registry(hass)
try:
entry = registry.async_update(msg['area_id'], msg['name'])
except ValueError as err:
connection.send_message(websocket_api.error_message(
msg['id'], 'invalid_info', str(err)
))
else:
connection.send_message(websocket_api.result_message(
msg['id'], _entry_dict(entry)
))
@callback
def _entry_dict(entry):
"""Convert entry to API format."""
return {
'area_id': entry.id,
'name': entry.name
}

View File

@@ -1,8 +1,11 @@
"""HTTP views to interact with the device registry."""
import voluptuous as vol
from homeassistant.helpers.device_registry import async_get_registry
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.decorators import (
async_response, require_admin)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import async_get_registry
DEPENDENCIES = ['websocket_api']
@@ -11,29 +14,60 @@ SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_LIST,
})
WS_TYPE_UPDATE = 'config/device_registry/update'
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_UPDATE,
vol.Required('device_id'): str,
vol.Optional('area_id'): vol.Any(str, None),
})
async def async_setup(hass):
"""Enable the Entity Registry views."""
"""Enable the Device Registry views."""
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list_devices,
SCHEMA_WS_LIST
)
hass.components.websocket_api.async_register_command(
WS_TYPE_UPDATE, websocket_update_device, SCHEMA_WS_UPDATE
)
return True
@websocket_api.async_response
@async_response
async def websocket_list_devices(hass, connection, msg):
"""Handle list devices command."""
registry = await async_get_registry(hass)
connection.send_message(websocket_api.result_message(
msg['id'], [{
'config_entries': list(entry.config_entries),
'connections': list(entry.connections),
'manufacturer': entry.manufacturer,
'model': entry.model,
'name': entry.name,
'sw_version': entry.sw_version,
'id': entry.id,
'hub_device_id': entry.hub_device_id,
} for entry in registry.devices.values()]
msg['id'], [_entry_dict(entry) for entry in registry.devices.values()]
))
@require_admin
@async_response
async def websocket_update_device(hass, connection, msg):
"""Handle update area websocket command."""
registry = await async_get_registry(hass)
entry = registry.async_update_device(
msg['device_id'], area_id=msg['area_id'])
connection.send_message(websocket_api.result_message(
msg['id'], _entry_dict(entry)
))
@callback
def _entry_dict(entry):
"""Convert entry to API format."""
return {
'config_entries': list(entry.config_entries),
'connections': list(entry.connections),
'manufacturer': entry.manufacturer,
'model': entry.model,
'name': entry.name,
'sw_version': entry.sw_version,
'id': entry.id,
'hub_device_id': entry.hub_device_id,
'area_id': entry.area_id,
}

View File

@@ -5,7 +5,8 @@ from homeassistant.core import callback
from homeassistant.helpers.entity_registry import async_get_registry
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
from homeassistant.components.websocket_api.decorators import async_response
from homeassistant.components.websocket_api.decorators import (
async_response, require_admin)
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['websocket_api']
@@ -30,6 +31,12 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Optional('new_entity_id'): str,
})
WS_TYPE_REMOVE = 'config/entity_registry/remove'
SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_REMOVE,
vol.Required('entity_id'): cv.entity_id
})
async def async_setup(hass):
"""Enable the Entity Registry views."""
@@ -45,6 +52,10 @@ async def async_setup(hass):
WS_TYPE_UPDATE, websocket_update_entity,
SCHEMA_WS_UPDATE
)
hass.components.websocket_api.async_register_command(
WS_TYPE_REMOVE, websocket_remove_entity,
SCHEMA_WS_REMOVE
)
return True
@@ -56,14 +67,7 @@ async def websocket_list_entities(hass, connection, msg):
"""
registry = await async_get_registry(hass)
connection.send_message(websocket_api.result_message(
msg['id'], [{
'config_entry_id': entry.config_entry_id,
'device_id': entry.device_id,
'disabled_by': entry.disabled_by,
'entity_id': entry.entity_id,
'name': entry.name,
'platform': entry.platform,
} for entry in registry.entities.values()]
msg['id'], [_entry_dict(entry) for entry in registry.entities.values()]
))
@@ -86,6 +90,7 @@ async def websocket_get_entity(hass, connection, msg):
))
@require_admin
@async_response
async def websocket_update_entity(hass, connection, msg):
"""Handle update entity websocket command.
@@ -125,10 +130,32 @@ async def websocket_update_entity(hass, connection, msg):
))
@require_admin
@async_response
async def websocket_remove_entity(hass, connection, msg):
"""Handle remove entity websocket command.
Async friendly.
"""
registry = await async_get_registry(hass)
if msg['entity_id'] not in registry.entities:
connection.send_message(websocket_api.error_message(
msg['id'], ERR_NOT_FOUND, 'Entity not found'))
return
registry.async_remove(msg['entity_id'])
connection.send_message(websocket_api.result_message(msg['id']))
@callback
def _entry_dict(entry):
"""Convert entry to API format."""
return {
'config_entry_id': entry.config_entry_id,
'device_id': entry.device_id,
'disabled_by': entry.disabled_by,
'entity_id': entry.entity_id,
'name': entry.name
'name': entry.name,
'platform': entry.platform,
}

View File

@@ -21,7 +21,7 @@ from homeassistant.const import (
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
STATE_CLOSED, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
_LOGGER = logging.getLogger(__name__)
@@ -178,7 +178,7 @@ class CoverDevice(Entity):
closed = self.is_closed
if closed is None:
return STATE_UNKNOWN
return None
return STATE_CLOSED if closed else STATE_OPEN

View File

@@ -14,7 +14,7 @@ from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME,
STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, CONF_COVERS)
STATE_CLOSED, STATE_OPEN, CONF_COVERS)
_LOGGER = logging.getLogger(__name__)
@@ -83,7 +83,7 @@ class GaradgetCover(CoverDevice):
self.obtained_token = False
self._username = args['username']
self._password = args['password']
self._state = STATE_UNKNOWN
self._state = None
self.time_in_state = None
self.signal = None
self.sensor = None
@@ -156,7 +156,7 @@ class GaradgetCover(CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
if self._state is None:
return None
return self._state == STATE_CLOSED
@@ -226,7 +226,7 @@ class GaradgetCover(CoverDevice):
try:
status = self._get_variable('doorStatus')
_LOGGER.debug("Current Status: %s", status['status'])
self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN)
self._state = STATES_MAP.get(status['status'], None)
self.time_in_state = status['time']
self.signal = status['signal']
self.sensor = status['sensor']

View File

@@ -0,0 +1,70 @@
"""
Support for HomematicIP Cloud cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.homematicip_cloud/
"""
import logging
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice)
from homeassistant.components.homematicip_cloud import (
HMIPC_HAPID, HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN)
DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the HomematicIP Cloud cover devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the HomematicIP cover from a config entry."""
from homematicip.aio.device import AsyncFullFlushShutter
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
for device in home.devices:
if isinstance(device, AsyncFullFlushShutter):
devices.append(HomematicipCoverShutter(home, device))
if devices:
async_add_entities(devices)
class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice):
"""Representation of a HomematicIP Cloud cover device."""
@property
def current_cover_position(self):
"""Return current position of cover."""
return int(self._device.shutterLevel * 100)
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
level = position / 100.0
await self._device.set_shutter_level(level)
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._device.shutterLevel is not None:
return self._device.shutterLevel == 0
return None
async def async_open_cover(self, **kwargs):
"""Open the cover."""
await self._device.set_shutter_level(1)
async def async_close_cover(self, **kwargs):
"""Close the cover."""
await self._device.set_shutter_level(0)
async def async_stop_cover(self, **kwargs):
"""Stop the device if in motion."""
await self._device.set_shutter_stop()

View File

@@ -4,8 +4,7 @@ Support for Wink Covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.wink/
"""
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \
ATTR_POSITION
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink']
@@ -54,7 +53,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
"""Return the current position of cover shutter."""
if self.wink.state() is not None:
return int(self.wink.state()*100)
return STATE_UNKNOWN
return None
@property
def is_closed(self):

View File

@@ -0,0 +1,80 @@
"""
Support for Danfoss Air HRV.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/danfoss_air/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.const import CONF_HOST
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['pydanfossair==0.0.6']
_LOGGER = logging.getLogger(__name__)
DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor']
DOMAIN = 'danfoss_air'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Danfoss Air component."""
conf = config[DOMAIN]
hass.data[DOMAIN] = DanfossAir(conf[CONF_HOST])
for platform in DANFOSS_AIR_PLATFORMS:
discovery.load_platform(hass, platform, DOMAIN, {}, config)
return True
class DanfossAir:
"""Handle all communication with Danfoss Air CCM unit."""
def __init__(self, host):
"""Initialize the Danfoss Air CCM connection."""
self._data = {}
from pydanfossair.danfossclient import DanfossClient
self._client = DanfossClient(host)
def get_value(self, item):
"""Get value for sensor."""
return self._data.get(item)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Use the data from Danfoss Air API."""
_LOGGER.debug("Fetching data from Danfoss Air CCM module")
from pydanfossair.commands import ReadCommand
self._data[ReadCommand.exhaustTemperature] \
= self._client.command(ReadCommand.exhaustTemperature)
self._data[ReadCommand.outdoorTemperature] \
= self._client.command(ReadCommand.outdoorTemperature)
self._data[ReadCommand.supplyTemperature] \
= self._client.command(ReadCommand.supplyTemperature)
self._data[ReadCommand.extractTemperature] \
= self._client.command(ReadCommand.extractTemperature)
self._data[ReadCommand.humidity] \
= round(self._client.command(ReadCommand.humidity), 2)
self._data[ReadCommand.filterPercent] \
= round(self._client.command(ReadCommand.filterPercent), 2)
self._data[ReadCommand.bypass] \
= self._client.command(ReadCommand.bypass)
_LOGGER.debug("Done fetching data from Danfoss Air CCM module")

View File

@@ -0,0 +1,56 @@
"""
Support for the for Danfoss Air HRV binary sensor platform.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.danfoss_air/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.danfoss_air import DOMAIN \
as DANFOSS_AIR_DOMAIN
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available Danfoss Air sensors etc."""
from pydanfossair.commands import ReadCommand
data = hass.data[DANFOSS_AIR_DOMAIN]
sensors = [["Danfoss Air Bypass Active", ReadCommand.bypass]]
dev = []
for sensor in sensors:
dev.append(DanfossAirBinarySensor(data, sensor[0], sensor[1]))
add_entities(dev, True)
class DanfossAirBinarySensor(BinarySensorDevice):
"""Representation of a Danfoss Air binary sensor."""
def __init__(self, data, name, sensor_type):
"""Initialize the Danfoss Air binary sensor."""
self._data = data
self._name = name
self._state = None
self._type = sensor_type
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the sensor."""
return self._state
@property
def device_class(self):
"""Type of device class."""
return "opening"
def update(self):
"""Fetch new state data for the sensor."""
self._data.update()
self._state = self._data.get_value(self._type)

View File

@@ -0,0 +1,76 @@
"""
Support for the for Danfoss Air HRV sensor platform.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor.danfoss_air/
"""
from homeassistant.components.danfoss_air import DOMAIN \
as DANFOSS_AIR_DOMAIN
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available Danfoss Air sensors etc."""
from pydanfossair.commands import ReadCommand
data = hass.data[DANFOSS_AIR_DOMAIN]
sensors = [
["Danfoss Air Exhaust Temperature", TEMP_CELSIUS,
ReadCommand.exhaustTemperature],
["Danfoss Air Outdoor Temperature", TEMP_CELSIUS,
ReadCommand.outdoorTemperature],
["Danfoss Air Supply Temperature", TEMP_CELSIUS,
ReadCommand.supplyTemperature],
["Danfoss Air Extract Temperature", TEMP_CELSIUS,
ReadCommand.extractTemperature],
["Danfoss Air Remaining Filter", '%',
ReadCommand.filterPercent],
["Danfoss Air Humidity", '%',
ReadCommand.humidity]
]
dev = []
for sensor in sensors:
dev.append(DanfossAir(data, sensor[0], sensor[1], sensor[2]))
add_entities(dev, True)
class DanfossAir(Entity):
"""Representation of a Sensor."""
def __init__(self, data, name, sensor_unit, sensor_type):
"""Initialize the sensor."""
self._data = data
self._name = name
self._state = None
self._type = sensor_type
self._unit = sensor_unit
@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 self._unit
def update(self):
"""Update the new state of the sensor.
This is done through the DanfossAir object that does the actual
communication with the Air CCM.
"""
self._data.update()
self._state = self._data.get_value(self._type)

View File

@@ -0,0 +1,107 @@
"""
Support for EE Brightbox router.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ee_brightbox/
"""
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['eebrightbox==0.0.4']
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = 'version'
CONF_DEFAULT_IP = '192.168.1.1'
CONF_DEFAULT_USERNAME = 'admin'
CONF_DEFAULT_VERSION = 2
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_VERSION, default=CONF_DEFAULT_VERSION): cv.positive_int,
vol.Required(CONF_HOST, default=CONF_DEFAULT_IP): cv.string,
vol.Required(CONF_USERNAME, default=CONF_DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def get_scanner(hass, config):
"""Return a router scanner instance."""
scanner = EEBrightBoxScanner(config[DOMAIN])
return scanner if scanner.check_config() else None
class EEBrightBoxScanner(DeviceScanner):
"""Scan EE Brightbox router."""
def __init__(self, config):
"""Initialise the scanner."""
self.config = config
self.devices = {}
def check_config(self):
"""Check if provided configuration and credentials are correct."""
from eebrightbox import EEBrightBox, EEBrightBoxException
try:
with EEBrightBox(self.config) as ee_brightbox:
return bool(ee_brightbox.get_devices())
except EEBrightBoxException:
_LOGGER.exception("Failed to connect to the router")
return False
def scan_devices(self):
"""Scan for devices."""
from eebrightbox import EEBrightBox
with EEBrightBox(self.config) as ee_brightbox:
self.devices = {d['mac']: d for d in ee_brightbox.get_devices()}
macs = [d['mac'] for d in self.devices.values() if d['activity_ip']]
_LOGGER.debug('Scan devices %s', macs)
return macs
def get_device_name(self, device):
"""Get the name of a device from hostname."""
if device in self.devices:
return self.devices[device]['hostname'] or None
return None
def get_extra_attributes(self, device):
"""
Get the extra attributes of a device.
Extra attributes include:
- ip
- mac
- port - ethX or wifiX
- last_active
"""
port_map = {
'wl1': 'wifi5Ghz',
'wl0': 'wifi2.4Ghz',
'eth0': 'eth0',
'eth1': 'eth1',
'eth2': 'eth2',
'eth3': 'eth3',
}
if device in self.devices:
return {
'ip': self.devices[device]['ip'],
'mac': self.devices[device]['mac'],
'port': port_map[self.devices[device]['port']],
'last_active': self.devices[device]['time_last_active'],
}
return {}

View File

@@ -1,32 +0,0 @@
"""
Support for the GPSLogger platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.gpslogger/
"""
import logging
from homeassistant.components.gpslogger import TRACKER_UPDATE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['gpslogger']
async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType,
async_see, discovery_info=None):
"""Set up an endpoint for the GPSLogger device tracker."""
async def _set_location(device, gps_location, battery, accuracy, attrs):
"""Fire HA event to set location."""
await async_see(
dev_id=device,
gps=gps_location,
battery=battery,
gps_accuracy=accuracy,
attributes=attrs
)
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
return True

View File

@@ -0,0 +1,100 @@
"""Device tracker for Synology SRM routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.synology_srm/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
CONF_PORT, CONF_SSL, CONF_VERIFY_SSL)
REQUIREMENTS = ['synology-srm==0.0.3']
_LOGGER = logging.getLogger(__name__)
DEFAULT_USERNAME = 'admin'
DEFAULT_PORT = 8001
DEFAULT_SSL = True
DEFAULT_VERIFY_SSL = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
def get_scanner(hass, config):
"""Validate the configuration and return Synology SRM scanner."""
scanner = SynologySrmDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class SynologySrmDeviceScanner(DeviceScanner):
"""This class scans for devices connected to a Synology SRM router."""
def __init__(self, config):
"""Initialize the scanner."""
import synology_srm
self.client = synology_srm.Client(
host=config[CONF_HOST],
port=config[CONF_PORT],
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
https=config[CONF_SSL]
)
if not config[CONF_VERIFY_SSL]:
self.client.http.disable_https_verify()
self.last_results = []
self.success_init = self._update_info()
_LOGGER.info("Synology SRM scanner initialized")
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [device['mac'] for device in self.last_results]
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
filter_named = [result['hostname'] for result in self.last_results if
result['mac'] == device]
if filter_named:
return filter_named[0]
return None
def _update_info(self):
"""Check the router for connected devices."""
_LOGGER.debug("Scanning for connected devices")
devices = self.client.mesh.network_wifidevice()
last_results = []
for device in devices:
last_results.append({
'mac': device['mac'],
'hostname': device['hostname']
})
_LOGGER.debug(
"Found %d device(s) connected to the router",
len(devices)
)
self.last_results = last_results
return True

View File

@@ -0,0 +1,79 @@
"""
Support for Dovado router.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/dovado/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT,
DEVICE_DEFAULT_NAME)
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['dovado==0.4.1']
DOMAIN = 'dovado'
CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT): cv.port,
})
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
def setup(hass, config):
"""Set up the Dovado component."""
import dovado
hass.data[DOMAIN] = DovadoData(
dovado.Dovado(
config[CONF_USERNAME],
config[CONF_PASSWORD],
config.get(CONF_HOST),
config.get(CONF_PORT)
)
)
return True
class DovadoData:
"""Maintains a connection to the router."""
def __init__(self, client):
"""Set up a new Dovado connection."""
self._client = client
self.state = {}
@property
def name(self):
"""Name of the router."""
return self.state.get("product name", DEVICE_DEFAULT_NAME)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update device state."""
try:
self.state = self._client.state or {}
if not self.state:
return False
self.state.update(
connected=self.state.get("modem status") == "CONNECTED")
_LOGGER.debug("Received: %s", self.state)
return True
except OSError as error:
_LOGGER.warning("Could not contact the router: %s", error)
@property
def client(self):
"""Dovado client instance."""
return self._client

View File

@@ -0,0 +1,38 @@
"""
Support for SMS notifications from the Dovado router.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.dovado/
"""
import logging
from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN
from homeassistant.components.notify import BaseNotificationService, \
ATTR_TARGET
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['dovado']
def get_service(hass, config, discovery_info=None):
"""Get the Dovado Router SMS notification service."""
return DovadoSMSNotificationService(hass.data[DOVADO_DOMAIN].client)
class DovadoSMSNotificationService(BaseNotificationService):
"""Implement the notification service for the Dovado SMS component."""
def __init__(self, client):
"""Initialize the service."""
self._client = client
def send_message(self, message, **kwargs):
"""Send SMS to the specified target phone number."""
target = kwargs.get(ATTR_TARGET)
if not target:
_LOGGER.error("One target is required")
return
self._client.send_sms(target, message)

View File

@@ -0,0 +1,116 @@
"""
Support for sensors from the Dovado router.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.dovado/
"""
import logging
import re
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_SENSORS
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['dovado']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
SENSOR_UPLOAD = 'upload'
SENSOR_DOWNLOAD = 'download'
SENSOR_SIGNAL = 'signal'
SENSOR_NETWORK = 'network'
SENSOR_SMS_UNREAD = 'sms'
SENSORS = {
SENSOR_NETWORK: ('signal strength', 'Network', None,
'mdi:access-point-network'),
SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%',
'mdi:signal'),
SENSOR_SMS_UNREAD: ('sms unread', 'SMS unread', '',
'mdi:message-text-outline'),
SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB',
'mdi:cloud-upload'),
SENSOR_DOWNLOAD: ('traffic modem rx', 'Received', 'GB',
'mdi:cloud-download'),
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.All(
cv.ensure_list, [vol.In(SENSORS)]
),
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Dovado sensor platform."""
dovado = hass.data[DOVADO_DOMAIN]
entities = []
for sensor in config[CONF_SENSORS]:
entities.append(DovadoSensor(dovado, sensor))
add_entities(entities)
class DovadoSensor(Entity):
"""Representation of a Dovado sensor."""
def __init__(self, data, sensor):
"""Initialize the sensor."""
self._data = data
self._sensor = sensor
self._state = self._compute_state()
def _compute_state(self):
state = self._data.state.get(SENSORS[self._sensor][0])
if self._sensor == SENSOR_NETWORK:
match = re.search(r"\((.+)\)", state)
return match.group(1) if match else None
if self._sensor == SENSOR_SIGNAL:
try:
return int(state.split()[0])
except ValueError:
return None
if self._sensor == SENSOR_SMS_UNREAD:
return int(state)
if self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
return round(float(state) / 1e6, 1)
return state
def update(self):
"""Update sensor values."""
self._data.update()
self._state = self._compute_state()
@property
def name(self):
"""Return the name of the sensor."""
return "{} {}".format(self._data.name, SENSORS[self._sensor][1])
@property
def state(self):
"""Return the sensor state."""
return self._state
@property
def icon(self):
"""Return the icon for the sensor."""
return SENSORS[self._sensor][3]
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return SENSORS[self._sensor][2]
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {k: v for k, v in self._data.state.items()
if k not in ['date', 'time']}

View File

@@ -0,0 +1,98 @@
"""
Component to control ecoal/esterownik.pl coal/wood boiler controller.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ecoal_boiler/
"""
import logging
import voluptuous as vol
from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
CONF_MONITORED_CONDITIONS, CONF_SENSORS,
CONF_SWITCHES)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
REQUIREMENTS = ['ecoaliface==0.4.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "ecoal_boiler"
DATA_ECOAL_BOILER = 'data_' + DOMAIN
DEFAULT_USERNAME = "admin"
DEFAULT_PASSWORD = "admin"
# Available pump ids with assigned HA names
# Available as switches
AVAILABLE_PUMPS = {
"central_heating_pump": "Central heating pump",
"central_heating_pump2": "Central heating pump2",
"domestic_hot_water_pump": "Domestic hot water pump",
}
# Available temp sensor ids with assigned HA names
# Available as sensors
AVAILABLE_SENSORS = {
"outdoor_temp": 'Outdoor temperature',
"indoor_temp": 'Indoor temperature',
"indoor2_temp": 'Indoor temperature 2',
"domestic_hot_water_temp": 'Domestic hot water temperature',
"target_domestic_hot_water_temp": 'Target hot water temperature',
"feedwater_in_temp": 'Feedwater input temperature',
"feedwater_out_temp": 'Feedwater output temperature',
"target_feedwater_temp": 'Target feedwater temperature',
"fuel_feeder_temp": 'Fuel feeder temperature',
"exhaust_temp": 'Exhaust temperature',
}
SWITCH_SCHEMA = vol.Schema({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_PUMPS)):
vol.All(cv.ensure_list, [vol.In(AVAILABLE_PUMPS)])
})
SENSOR_SCHEMA = vol.Schema({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_SENSORS)):
vol.All(cv.ensure_list, [vol.In(AVAILABLE_SENSORS)])
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_USERNAME,
default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD,
default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA,
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, hass_config):
"""Set up global ECoalController instance same for sensors and switches."""
from ecoaliface.simple import ECoalController
conf = hass_config[DOMAIN]
host = conf[CONF_HOST]
username = conf[CONF_USERNAME]
passwd = conf[CONF_PASSWORD]
# Creating ECoalController instance makes HTTP request to controller.
ecoal_contr = ECoalController(host, username, passwd)
if ecoal_contr.version is None:
# Wrong credentials nor network config
_LOGGER.error("Unable to read controller status from %s@%s"
" (wrong host/credentials)", username, host, )
return False
_LOGGER.debug("Detected controller version: %r @%s",
ecoal_contr.version, host, )
hass.data[DATA_ECOAL_BOILER] = ecoal_contr
# Setup switches
switches = conf[CONF_SWITCHES][CONF_MONITORED_CONDITIONS]
load_platform(hass, 'switch', DOMAIN, switches, hass_config)
# Setup temp sensors
sensors = conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS]
load_platform(hass, 'sensor', DOMAIN, sensors, hass_config)
return True

View File

@@ -22,7 +22,7 @@ from homeassistant.components.http import real_ip
from .hue_api import (
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
HueOneLightChangeView, HueGroupView)
HueOneLightChangeView, HueGroupView, HueAllGroupsStateView)
from .upnp import DescriptionXmlView, UPNPResponderThread
DOMAIN = 'emulated_hue'
@@ -105,6 +105,7 @@ async def async_setup(hass, yaml_config):
HueAllLightsStateView(config).register(app, app.router)
HueOneLightStateView(config).register(app, app.router)
HueOneLightChangeView(config).register(app, app.router)
HueAllGroupsStateView(config).register(app, app.router)
HueGroupView(config).register(app, app.router)
upnp_listener = UPNPResponderThread(

View File

@@ -56,6 +56,28 @@ class HueUsernameView(HomeAssistantView):
return self.json([{'success': {'username': '12345678901234567890'}}])
class HueAllGroupsStateView(HomeAssistantView):
"""Group handler."""
url = '/api/{username}/groups'
name = 'emulated_hue:all_groups:state'
requires_auth = False
def __init__(self, config):
"""Initialize the instance of the view."""
self.config = config
@core.callback
def get(self, request, username):
"""Process a request to make the Brilliant Lightpad work."""
if not is_local(request[KEY_REAL_IP]):
return self.json_message('only local IPs allowed',
HTTP_BAD_REQUEST)
return self.json({
})
class HueGroupView(HomeAssistantView):
"""Group handler to get Logitech Pop working."""

View File

@@ -16,7 +16,7 @@ from .const import (
CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT,
CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN)
REQUIREMENTS = ['emulated_roku==0.1.7']
REQUIREMENTS = ['emulated_roku==0.1.8']
SERVER_CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,

View File

@@ -146,19 +146,19 @@ async def async_setup(hass, config):
@callback
def zones_updated_callback(data):
"""Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
_LOGGER.debug("Envisalink sent a zone update event. Updating zones...")
async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
@callback
def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
_LOGGER.debug("Envisalink sent new alarm info. Updating alarms...")
async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
@callback
def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event")
_LOGGER.debug("The envisalink sent a partition update event")
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
@callback

View File

@@ -12,8 +12,7 @@ import voluptuous as vol
from homeassistant.components import group
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE,
SERVICE_TURN_OFF, ATTR_ENTITY_ID,
STATE_UNKNOWN)
SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
@@ -94,7 +93,7 @@ def is_on(hass, entity_id: str = None) -> bool:
"""Return if the fans are on based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_FANS
state = hass.states.get(entity_id)
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, STATE_UNKNOWN]
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, None]
async def async_setup(hass, config: dict):
@@ -199,7 +198,7 @@ class FanEntity(ToggleEntity):
@property
def is_on(self):
"""Return true if the entity is on."""
return self.speed not in [SPEED_OFF, STATE_UNKNOWN]
return self.speed not in [SPEED_OFF, None]
@property
def speed(self) -> str:

View File

@@ -11,7 +11,6 @@ from homeassistant.components.comfoconnect import (
from homeassistant.components.fan import (
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.dispatcher import (dispatcher_connect)
_LOGGER = logging.getLogger(__name__)
@@ -79,7 +78,7 @@ class ComfoConnectFan(FanEntity):
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
return SPEED_MAPPING[speed]
except KeyError:
return STATE_UNKNOWN
return None
@property
def speed_list(self):

View File

@@ -7,7 +7,7 @@ https://home-assistant.io/components/fan.wink/
import logging
from homeassistant.components.fan import (
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, STATE_UNKNOWN, SUPPORT_DIRECTION,
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION,
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.components.wink import DOMAIN, WinkDevice
@@ -71,7 +71,7 @@ class WinkFanDevice(WinkDevice, FanEntity):
return SPEED_MEDIUM
if SPEED_HIGH == current_wink_speed:
return SPEED_HIGH
return STATE_UNKNOWN
return None
@property
def current_direction(self):

View File

@@ -38,7 +38,7 @@ MODEL_AIRPURIFIER_MA1 = 'zhimi.airpurifier.ma1'
MODEL_AIRPURIFIER_MA2 = 'zhimi.airpurifier.ma2'
MODEL_AIRPURIFIER_SA1 = 'zhimi.airpurifier.sa1'
MODEL_AIRPURIFIER_SA2 = 'zhimi.airpurifier.sa2'
MODEL_AIRPURIFIER_MC1 = 'zhimi.airpurifier.mc1'
MODEL_AIRPURIFIER_2S = 'zhimi.airpurifier.mc1'
MODEL_AIRHUMIDIFIER_V1 = 'zhimi.humidifier.v1'
MODEL_AIRHUMIDIFIER_CA = 'zhimi.humidifier.ca1'
@@ -62,7 +62,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
MODEL_AIRPURIFIER_MA2,
MODEL_AIRPURIFIER_SA1,
MODEL_AIRPURIFIER_SA2,
MODEL_AIRPURIFIER_MC1,
MODEL_AIRPURIFIER_2S,
MODEL_AIRHUMIDIFIER_V1,
MODEL_AIRHUMIDIFIER_CA,
MODEL_AIRFRESH_VA2,
@@ -175,6 +175,15 @@ AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7 = {
ATTR_VOLUME: 'volume',
}
AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S = {
**AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON,
ATTR_BUZZER: 'buzzer',
ATTR_FILTER_RFID_PRODUCT_ID: 'filter_rfid_product_id',
ATTR_FILTER_RFID_TAG: 'filter_rfid_tag',
ATTR_FILTER_TYPE: 'filter_type',
ATTR_ILLUMINANCE: 'illuminance',
}
AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = {
# Common set isn't used here. It's a very basic version of the device.
ATTR_AIR_QUALITY_INDEX: 'aqi',
@@ -249,6 +258,7 @@ AVAILABLE_ATTRIBUTES_AIRFRESH = {
OPERATION_MODES_AIRPURIFIER = ['Auto', 'Silent', 'Favorite', 'Idle']
OPERATION_MODES_AIRPURIFIER_PRO = ['Auto', 'Silent', 'Favorite']
OPERATION_MODES_AIRPURIFIER_PRO_V7 = OPERATION_MODES_AIRPURIFIER_PRO
OPERATION_MODES_AIRPURIFIER_2S = ['Auto', 'Silent', 'Favorite']
OPERATION_MODES_AIRPURIFIER_V3 = ['Auto', 'Silent', 'Favorite', 'Idle',
'Medium', 'High', 'Strong']
OPERATION_MODES_AIRFRESH = ['Auto', 'Silent', 'Interval', 'Low',
@@ -289,6 +299,11 @@ FEATURE_FLAGS_AIRPURIFIER_PRO_V7 = (FEATURE_SET_CHILD_LOCK |
FEATURE_SET_FAVORITE_LEVEL |
FEATURE_SET_VOLUME)
FEATURE_FLAGS_AIRPURIFIER_2S = (FEATURE_SET_BUZZER |
FEATURE_SET_CHILD_LOCK |
FEATURE_SET_LED |
FEATURE_SET_FAVORITE_LEVEL)
FEATURE_FLAGS_AIRPURIFIER_V3 = (FEATURE_SET_BUZZER |
FEATURE_SET_CHILD_LOCK |
FEATURE_SET_LED)
@@ -619,6 +634,10 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
self._available_attributes = \
AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
self._speed_list = OPERATION_MODES_AIRPURIFIER_PRO_V7
elif self._model == MODEL_AIRPURIFIER_2S:
self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S
self._speed_list = OPERATION_MODES_AIRPURIFIER_2S
elif self._model == MODEL_AIRPURIFIER_V3:
self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3

View File

@@ -12,7 +12,8 @@ import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN)
from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN,
CONF_UPDATE_INTERVAL)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -24,8 +25,6 @@ DEFAULT_INTERVAL = timedelta(minutes=10)
TIMEOUT = 10
UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php'
CONF_UPDATE_INTERVAL = 'update_interval'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Exclusive(CONF_URL, DOMAIN): cv.string,

View File

@@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyfritzhome==0.4.0']
SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch']
SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch', 'sensor']
DOMAIN = 'fritzbox'

View File

@@ -24,7 +24,7 @@ from homeassistant.core import callback
from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20190121.1']
REQUIREMENTS = ['home-assistant-frontend==20190201.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',

View File

@@ -22,15 +22,12 @@ DOMAIN = 'geo_location'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_EVENTS = 'All Geolocation Events'
SCAN_INTERVAL = timedelta(seconds=60)
async def async_setup(hass, config):
"""Set up the Geolocation component."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_EVENTS)
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
return True

View File

@@ -10,10 +10,10 @@ import voluptuous as vol
from aiohttp import web
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME, \
ATTR_LATITUDE, ATTR_LONGITUDE, CONF_WEBHOOK_ID, HTTP_OK, ATTR_NAME
from homeassistant.helpers import config_entry_flow
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.util import slugify
@@ -68,13 +68,9 @@ WEBHOOK_SCHEMA = vol.Schema({
async def async_setup(hass, hass_config):
"""Set up the Geofency component."""
config = hass_config[DOMAIN]
mobile_beacons = config[CONF_MOBILE_BEACONS]
config = hass_config.get(DOMAIN, {})
mobile_beacons = config.get(CONF_MOBILE_BEACONS, [])
hass.data[DOMAIN] = [slugify(beacon) for beacon in mobile_beacons]
hass.async_create_task(
async_load_platform(hass, 'device_tracker', DOMAIN, {}, hass_config)
)
return True
@@ -136,12 +132,18 @@ async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
DOMAIN, 'Geofency', entry.data[CONF_WEBHOOK_ID], handle_webhook)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, DEVICE_TRACKER)
)
return True
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
config_entry_flow.register_webhook_flow(

View File

@@ -6,16 +6,21 @@ https://home-assistant.io/components/device_tracker.geofency/
"""
import logging
from homeassistant.components.geofency import TRACKER_UPDATE
from homeassistant.components.device_tracker import DOMAIN as \
DEVICE_TRACKER_DOMAIN
from homeassistant.components.geofency import TRACKER_UPDATE, \
DOMAIN as GEOFENCY_DOMAIN
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['geofency']
DATA_KEY = '{}.{}'.format(GEOFENCY_DOMAIN, DEVICE_TRACKER_DOMAIN)
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the Geofency device tracker."""
async def async_setup_entry(hass, entry, async_see):
"""Configure a dispatcher connection based on a config entry."""
async def _set_location(device, gps, location_name, attributes):
"""Fire HA event to set location."""
await async_see(
@@ -25,5 +30,13 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
attributes=attributes
)
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
hass.data[DATA_KEY] = async_dispatcher_connect(
hass, TRACKER_UPDATE, _set_location
)
return True
async def async_unload_entry(hass, entry):
"""Unload the config entry and remove the dispatcher connection."""
hass.data[DATA_KEY]()
return True

View File

@@ -15,8 +15,8 @@ from homeassistant.components.device_tracker.tile import ATTR_ALTITUDE
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, \
HTTP_OK, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER
_LOGGER = logging.getLogger(__name__)
@@ -57,9 +57,6 @@ WEBHOOK_SCHEMA = vol.Schema({
async def async_setup(hass, hass_config):
"""Set up the GPSLogger component."""
hass.async_create_task(
async_load_platform(hass, 'device_tracker', DOMAIN, {}, hass_config)
)
return True
@@ -103,12 +100,18 @@ async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
DOMAIN, 'GPSLogger', entry.data[CONF_WEBHOOK_ID], handle_webhook)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, DEVICE_TRACKER)
)
return True
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
config_entry_flow.register_webhook_flow(

View File

@@ -0,0 +1,44 @@
"""
Support for the GPSLogger platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.gpslogger/
"""
import logging
from homeassistant.components.device_tracker import DOMAIN as \
DEVICE_TRACKER_DOMAIN
from homeassistant.components.gpslogger import DOMAIN as GPSLOGGER_DOMAIN, \
TRACKER_UPDATE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import HomeAssistantType
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['gpslogger']
DATA_KEY = '{}.{}'.format(GPSLOGGER_DOMAIN, DEVICE_TRACKER_DOMAIN)
async def async_setup_entry(hass: HomeAssistantType, entry, async_see):
"""Configure a dispatcher connection based on a config entry."""
async def _set_location(device, gps_location, battery, accuracy, attrs):
"""Fire HA event to set location."""
await async_see(
dev_id=device,
gps=gps_location,
battery=battery,
gps_accuracy=accuracy,
attributes=attrs
)
hass.data[DATA_KEY] = async_dispatcher_connect(
hass, TRACKER_UPDATE, _set_location
)
return True
async def async_unload_entry(hass: HomeAssistantType, entry):
"""Unload the config entry and remove the dispatcher connection."""
hass.data[DATA_KEY]()
return True

View File

@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER
from homeassistant.components.switch import DOMAIN as SWITCH
from homeassistant.const import (EVENT_HOMEASSISTANT_START, STATE_UNKNOWN,
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, STATE_ON,
STATE_OFF, CONF_DEVICES, CONF_PLATFORM,
STATE_PLAYING, STATE_IDLE,
@@ -324,7 +324,7 @@ class CecDevice(Entity):
"""Initialize the device."""
self._device = device
self._icon = None
self._state = STATE_UNKNOWN
self._state = None
self._logical_address = logical
self.entity_id = "%s.%d" % (DOMAIN, self._logical_address)

View File

@@ -4,8 +4,8 @@ import logging
from pyhap.const import CATEGORY_DOOR_LOCK
from homeassistant.components.lock import (
ATTR_ENTITY_ID, DOMAIN, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN)
from homeassistant.const import ATTR_CODE
ATTR_ENTITY_ID, DOMAIN, STATE_LOCKED, STATE_UNLOCKED)
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN
from . import TYPES
from .accessories import HomeAccessory

View File

@@ -13,7 +13,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import call_later
REQUIREMENTS = ['homekit==0.12.0']
REQUIREMENTS = ['homekit==0.12.2']
DOMAIN = 'homekit_controller'
HOMEKIT_DIR = '.homekit'
@@ -28,7 +28,8 @@ HOMEKIT_ACCESSORY_DISPATCH = {
'garage-door-opener': 'cover',
'window': 'cover',
'window-covering': 'cover',
'lock-mechanism': 'lock'
'lock-mechanism': 'lock',
'motion': 'binary_sensor',
}
HOMEKIT_IGNORE = [
@@ -49,10 +50,6 @@ RETRY_INTERVAL = 60 # seconds
PAIRING_FILE = "pairing.json"
class HomeKitConnectionError(ConnectionError):
"""Raised when unable to connect to target device."""
def get_serial(accessory):
"""Obtain the serial number of a HomeKit device."""
# pylint: disable=import-error
@@ -72,6 +69,11 @@ def get_serial(accessory):
return None
def escape_characteristic_name(char_name):
"""Escape any dash or dots in a characteristics name."""
return char_name.replace('-', '_').replace('.', '_')
class HKDevice():
"""HomeKit device."""
@@ -101,13 +103,14 @@ class HKDevice():
"""Handle setup of a HomeKit accessory."""
# pylint: disable=import-error
from homekit.model.services import ServicesTypes
from homekit.exceptions import AccessoryDisconnectedError
self.pairing.pairing_data['AccessoryIP'] = self.host
self.pairing.pairing_data['AccessoryPort'] = self.port
try:
data = self.pairing.list_accessories_and_characteristics()
except HomeKitConnectionError:
except AccessoryDisconnectedError:
call_later(
self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup())
return
@@ -196,22 +199,80 @@ class HomeKitEntity(Entity):
self._address = "homekit-{}-{}".format(devinfo['serial'], self._iid)
self._features = 0
self._chars = {}
self.setup()
def update(self):
"""Obtain a HomeKit device's state."""
try:
pairing = self._accessory.pairing
data = pairing.list_accessories_and_characteristics()
except HomeKitConnectionError:
return
for accessory in data:
def setup(self):
"""Configure an entity baed on its HomeKit characterstics metadata."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
pairing_data = self._accessory.pairing.pairing_data
get_uuid = CharacteristicsTypes.get_uuid
characteristic_types = [
get_uuid(c) for c in self.get_characteristic_types()
]
self._chars_to_poll = []
self._chars = {}
self._char_names = {}
for accessory in pairing_data.get('accessories', []):
if accessory['aid'] != self._aid:
continue
for service in accessory['services']:
if service['iid'] != self._iid:
continue
self.update_characteristics(service['characteristics'])
break
for char in service['characteristics']:
uuid = CharacteristicsTypes.get_uuid(char['type'])
if uuid not in characteristic_types:
continue
self._setup_characteristic(char)
def _setup_characteristic(self, char):
"""Configure an entity based on a HomeKit characteristics metadata."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
# Build up a list of (aid, iid) tuples to poll on update()
self._chars_to_poll.append((self._aid, char['iid']))
# Build a map of ctype -> iid
short_name = CharacteristicsTypes.get_short(char['type'])
self._chars[short_name] = char['iid']
self._char_names[char['iid']] = short_name
# Callback to allow entity to configure itself based on this
# characteristics metadata (valid values, value ranges, features, etc)
setup_fn_name = escape_characteristic_name(short_name)
setup_fn = getattr(self, '_setup_{}'.format(setup_fn_name), None)
if not setup_fn:
return
# pylint: disable=not-callable
setup_fn(char)
def update(self):
"""Obtain a HomeKit device's state."""
# pylint: disable=import-error
from homekit.exceptions import AccessoryDisconnectedError
pairing = self._accessory.pairing
try:
new_values_dict = pairing.get_characteristics(self._chars_to_poll)
except AccessoryDisconnectedError:
return
for (_, iid), result in new_values_dict.items():
if 'value' not in result:
continue
# Callback to update the entity with this characteristic value
char_name = escape_characteristic_name(self._char_names[iid])
update_fn = getattr(self, '_update_{}'.format(char_name), None)
if not update_fn:
continue
# pylint: disable=not-callable
update_fn(result['value'])
@property
def unique_id(self):
@@ -228,9 +289,13 @@ class HomeKitEntity(Entity):
"""Return True if entity is available."""
return self._accessory.pairing is not None
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
raise NotImplementedError
def update_characteristics(self, characteristics):
"""Synchronise a HomeKit device state with Home Assistant."""
raise NotImplementedError
pass
def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant."""

View File

@@ -54,24 +54,21 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
self._state = None
self._battery_level = None
def update_characteristics(self, characteristics):
"""Synchronise the Alarm Control Panel state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT,
CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET,
CharacteristicsTypes.BATTERY_LEVEL,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "security-system-state.current":
self._chars['security-system-state.current'] = \
characteristic['iid']
self._state = CURRENT_STATE_MAP[characteristic['value']]
elif ctype == "security-system-state.target":
self._chars['security-system-state.target'] = \
characteristic['iid']
elif ctype == "battery-level":
self._chars['battery-level'] = characteristic['iid']
self._battery_level = characteristic['value']
def _update_security_system_state_current(self, value):
self._state = CURRENT_STATE_MAP[value]
def _update_battery_level(self, value):
self._battery_level = value
@property
def icon(self):

View File

@@ -0,0 +1,53 @@
"""
Support for Homekit motion sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homekit_controller/
"""
import logging
from homeassistant.components.homekit_controller import (HomeKitEntity,
KNOWN_ACCESSORIES)
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['homekit_controller']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Homekit motion sensor support."""
if discovery_info is not None:
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
add_entities([HomeKitMotionSensor(accessory, discovery_info)], True)
class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice):
"""Representation of a Homekit sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._on = False
def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.MOTION_DETECTED,
]
def _update_motion_detected(self, value):
self._on = value
@property
def device_class(self):
"""Define this binary_sensor as a motion sensor."""
return 'motion'
@property
def is_on(self):
"""Has motion been detected."""
return self._on

View File

@@ -27,6 +27,8 @@ MODE_HOMEKIT_TO_HASS = {
# Map of hass operation modes to homekit modes
MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()}
DEFAULT_VALID_MODES = list(MODE_HOMEKIT_TO_HASS)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Homekit climate."""
@@ -47,43 +49,54 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
self._current_temp = None
self._target_temp = None
def update_characteristics(self, characteristics):
"""Synchronise device state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.models.characteristics import CharacteristicsTypes
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.HEATING_COOLING_CURRENT,
CharacteristicsTypes.HEATING_COOLING_TARGET,
CharacteristicsTypes.TEMPERATURE_CURRENT,
CharacteristicsTypes.TEMPERATURE_TARGET,
]
for characteristic in characteristics:
ctype = characteristic['type']
if ctype == CharacteristicsTypes.HEATING_COOLING_CURRENT:
self._state = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
if ctype == CharacteristicsTypes.HEATING_COOLING_TARGET:
self._chars['target_mode'] = characteristic['iid']
self._features |= SUPPORT_OPERATION_MODE
self._current_mode = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
self._valid_modes = [MODE_HOMEKIT_TO_HASS.get(
mode) for mode in characteristic['valid-values']]
elif ctype == CharacteristicsTypes.TEMPERATURE_CURRENT:
self._current_temp = characteristic['value']
elif ctype == CharacteristicsTypes.TEMPERATURE_TARGET:
self._chars['target_temp'] = characteristic['iid']
self._features |= SUPPORT_TARGET_TEMPERATURE
self._target_temp = characteristic['value']
def _setup_heating_cooling_target(self, characteristic):
self._features |= SUPPORT_OPERATION_MODE
valid_values = characteristic.get(
'valid-values', DEFAULT_VALID_MODES)
self._valid_modes = [
MODE_HOMEKIT_TO_HASS.get(mode) for mode in valid_values
]
def _setup_temperature_target(self, characteristic):
self._features |= SUPPORT_TARGET_TEMPERATURE
def _update_heating_cooling_current(self, value):
self._state = MODE_HOMEKIT_TO_HASS.get(value)
def _update_heating_cooling_target(self, value):
self._current_mode = MODE_HOMEKIT_TO_HASS.get(value)
def _update_temperature_current(self, value):
self._current_temp = value
def _update_temperature_target(self, value):
self._target_temp = value
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
characteristics = [{'aid': self._aid,
'iid': self._chars['target_temp'],
'iid': self._chars['temperature.target'],
'value': temp}]
self.put_characteristics(characteristics)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
characteristics = [{'aid': self._aid,
'iid': self._chars['target_mode'],
'iid': self._chars['heating-cooling.target'],
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
self.put_characteristics(characteristics)

View File

@@ -62,7 +62,6 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
def __init__(self, accessory, discovery_info):
"""Initialise the Cover."""
super().__init__(accessory, discovery_info)
self._name = None
self._state = None
self._obstruction_detected = None
self.lock_state = None
@@ -72,32 +71,28 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
"""Define this cover as a garage door."""
return 'garage'
def update_characteristics(self, characteristics):
"""Synchronise the Cover state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.DOOR_STATE_CURRENT,
CharacteristicsTypes.DOOR_STATE_TARGET,
CharacteristicsTypes.OBSTRUCTION_DETECTED,
CharacteristicsTypes.NAME,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "door-state.current":
self._chars['door-state.current'] = \
characteristic['iid']
self._state = CURRENT_GARAGE_STATE_MAP[characteristic['value']]
elif ctype == "door-state.target":
self._chars['door-state.target'] = \
characteristic['iid']
elif ctype == "obstruction-detected":
self._chars['obstruction-detected'] = characteristic['iid']
self._obstruction_detected = characteristic['value']
elif ctype == "name":
self._chars['name'] = characteristic['iid']
self._name = characteristic['value']
def _setup_name(self, char):
self._name = char['value']
@property
def name(self):
"""Return the name of the cover."""
return self._name
def _update_door_state_current(self, value):
self._state = CURRENT_GARAGE_STATE_MAP[value]
def _update_obstruction_detected(self, value):
self._obstruction_detected = value
def _update_name(self, value):
self._name = value
@property
def available(self):
@@ -156,7 +151,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
def __init__(self, accessory, discovery_info):
"""Initialise the Cover."""
super().__init__(accessory, discovery_info)
self._name = None
self._state = None
self._position = None
self._tilt_position = None
@@ -169,57 +163,46 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
"""Return True if entity is available."""
return self._state is not None
def update_characteristics(self, characteristics):
"""Synchronise the Cover state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.POSITION_STATE,
CharacteristicsTypes.POSITION_CURRENT,
CharacteristicsTypes.POSITION_TARGET,
CharacteristicsTypes.POSITION_HOLD,
CharacteristicsTypes.VERTICAL_TILT_CURRENT,
CharacteristicsTypes.VERTICAL_TILT_TARGET,
CharacteristicsTypes.HORIZONTAL_TILT_CURRENT,
CharacteristicsTypes.HORIZONTAL_TILT_TARGET,
CharacteristicsTypes.OBSTRUCTION_DETECTED,
CharacteristicsTypes.NAME,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "position.state":
self._chars['position.state'] = \
characteristic['iid']
if 'value' in characteristic:
self._state = \
CURRENT_WINDOW_STATE_MAP[characteristic['value']]
elif ctype == "position.current":
self._chars['position.current'] = \
characteristic['iid']
self._position = characteristic['value']
elif ctype == "position.target":
self._chars['position.target'] = \
characteristic['iid']
elif ctype == "position.hold":
self._chars['position.hold'] = characteristic['iid']
if 'value' in characteristic:
self._hold = characteristic['value']
elif ctype == "vertical-tilt.current":
self._chars['vertical-tilt.current'] = characteristic['iid']
if characteristic['value'] is not None:
self._tilt_position = characteristic['value']
elif ctype == "horizontal-tilt.current":
self._chars['horizontal-tilt.current'] = characteristic['iid']
if characteristic['value'] is not None:
self._tilt_position = characteristic['value']
elif ctype == "vertical-tilt.target":
self._chars['vertical-tilt.target'] = \
characteristic['iid']
elif ctype == "horizontal-tilt.target":
self._chars['vertical-tilt.target'] = \
characteristic['iid']
elif ctype == "obstruction-detected":
self._chars['obstruction-detected'] = characteristic['iid']
self._obstruction_detected = characteristic['value']
elif ctype == "name":
self._chars['name'] = characteristic['iid']
if 'value' in characteristic:
self._name = characteristic['value']
def _setup_name(self, char):
self._name = char['value']
@property
def name(self):
"""Return the name of the cover."""
return self._name
def _update_position_state(self, value):
self._state = CURRENT_WINDOW_STATE_MAP[value]
def _update_position_current(self, value):
self._position = value
def _update_position_hold(self, value):
self._hold = value
def _update_vertical_tilt_current(self, value):
self._tilt_position = value
def _update_horizontal_tilt_current(self, value):
self._tilt_position = value
def _update_obstruction_detected(self, value):
self._obstruction_detected = value
def _update_name(self, value):
self._hold = value
@property
def supported_features(self):

View File

@@ -36,33 +36,44 @@ class HomeKitLight(HomeKitEntity, Light):
self._hue = None
self._saturation = None
def update_characteristics(self, characteristics):
"""Synchronise light state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.ON,
CharacteristicsTypes.BRIGHTNESS,
CharacteristicsTypes.COLOR_TEMPERATURE,
CharacteristicsTypes.HUE,
CharacteristicsTypes.SATURATION,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "on":
self._chars['on'] = characteristic['iid']
self._on = characteristic['value']
elif ctype == 'brightness':
self._chars['brightness'] = characteristic['iid']
self._features |= SUPPORT_BRIGHTNESS
self._brightness = characteristic['value']
elif ctype == 'color-temperature':
self._chars['color_temperature'] = characteristic['iid']
self._features |= SUPPORT_COLOR_TEMP
self._color_temperature = characteristic['value']
elif ctype == "hue":
self._chars['hue'] = characteristic['iid']
self._features |= SUPPORT_COLOR
self._hue = characteristic['value']
elif ctype == "saturation":
self._chars['saturation'] = characteristic['iid']
self._features |= SUPPORT_COLOR
self._saturation = characteristic['value']
def _setup_brightness(self, char):
self._features |= SUPPORT_BRIGHTNESS
def _setup_color_temperature(self, char):
self._features |= SUPPORT_COLOR_TEMP
def _setup_hue(self, char):
self._features |= SUPPORT_COLOR
def _setup_saturation(self, char):
self._features |= SUPPORT_COLOR
def _update_on(self, value):
self._on = value
def _update_brightness(self, value):
self._brightness = value
def _update_color_temperature(self, value):
self._color_temperature = value
def _update_hue(self, value):
self._hue = value
def _update_saturation(self, value):
self._saturation = value
@property
def is_on(self):

View File

@@ -51,24 +51,21 @@ class HomeKitLock(HomeKitEntity, LockDevice):
self._name = discovery_info['model']
self._battery_level = None
def update_characteristics(self, characteristics):
"""Synchronise the Lock state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE,
CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE,
CharacteristicsTypes.BATTERY_LEVEL,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "lock-mechanism.current-state":
self._chars['lock-mechanism.current-state'] = \
characteristic['iid']
self._state = CURRENT_STATE_MAP[characteristic['value']]
elif ctype == "lock-mechanism.target-state":
self._chars['lock-mechanism.target-state'] = \
characteristic['iid']
elif ctype == "battery-level":
self._chars['battery-level'] = characteristic['iid']
self._battery_level = characteristic['value']
def _update_lock_mechanism_current_state(self, value):
self._state = CURRENT_STATE_MAP[value]
def _update_battery_level(self, value):
self._battery_level = value
@property
def name(self):

View File

@@ -33,20 +33,20 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice):
self._on = None
self._outlet_in_use = None
def update_characteristics(self, characteristics):
"""Synchronise the switch state with Home Assistant."""
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.ON,
CharacteristicsTypes.OUTLET_IN_USE,
]
for characteristic in characteristics:
ctype = characteristic['type']
ctype = CharacteristicsTypes.get_short(ctype)
if ctype == "on":
self._chars['on'] = characteristic['iid']
self._on = characteristic['value']
elif ctype == "outlet-in-use":
self._chars['outlet-in-use'] = characteristic['iid']
self._outlet_in_use = characteristic['value']
def _update_on(self, value):
self._on = value
def _update_outlet_in_use(self, value):
self._outlet_in_use = value
@property
def is_on(self):

View File

@@ -19,7 +19,7 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyhomematic==0.1.54']
REQUIREMENTS = ['pyhomematic==0.1.55']
_LOGGER = logging.getLogger(__name__)
@@ -65,7 +65,7 @@ HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren',
'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic',
'IPKeySwitchPowermeter', 'IPGarage'],
'IPKeySwitchPowermeter', 'IPGarage', 'IPKeySwitch', 'IPMultiIO'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer', 'IPDimmer',
'ColorEffectLight'],
DISCOVER_SENSORS: [
@@ -79,7 +79,7 @@ HM_DEVICE_TYPES = {
'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor',
'IPKeySwitchPowermeter', 'IPThermostatWall230V', 'IPWeatherSensorPlus',
'IPWeatherSensorBasic', 'IPBrightnessSensor', 'IPGarage',
'UniversalSensor', 'MotionIPV2'],
'UniversalSensor', 'MotionIPV2', 'IPMultiIO'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
@@ -89,7 +89,8 @@ HM_DEVICE_TYPES = {
'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor',
'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain',
'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor',
'SmartwareMotion', 'IPWeatherSensorPlus', 'MotionIPV2'],
'SmartwareMotion', 'IPWeatherSensorPlus', 'MotionIPV2', 'WaterIP',
'IPMultiIO'],
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'],
DISCOVER_LOCKS: ['KeyMatic']
}

View File

@@ -19,7 +19,7 @@ from .const import (
from .device import HomematicipGenericDevice # noqa: F401
from .hap import HomematicipAuth, HomematicipHAP # noqa: F401
REQUIREMENTS = ['homematicip==0.10.3']
REQUIREMENTS = ['homematicip==0.10.4']
_LOGGER = logging.getLogger(__name__)

View File

@@ -9,6 +9,7 @@ COMPONENTS = [
'alarm_control_panel',
'binary_sensor',
'climate',
'cover',
'light',
'sensor',
'switch',

View File

@@ -99,6 +99,7 @@ class ApiConfig:
self.port = port
self.api_password = api_password
host = host.rstrip('/')
if host.startswith(("http://", "https://")):
self.base_url = host
elif use_ssl:

View File

@@ -104,7 +104,7 @@ async def process_wrong_login(request):
request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1
if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] >
if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] >=
request.app[KEY_LOGIN_THRESHOLD]):
new_ban = IpBan(remote_addr)
request.app[KEY_BANNED_IPS].append(new_ban)

View File

@@ -19,7 +19,7 @@ from .bridge import HueBridge
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
REQUIREMENTS = ['aiohue==1.8.0']
REQUIREMENTS = ['aiohue==1.9.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -41,6 +41,7 @@ SUPPORT_HUE = {
}
ATTR_IS_HUE_GROUP = 'is_hue_group'
GAMUT_TYPE_UNAVAILABLE = 'None'
# Minimum Hue Bridge API version to support groups
# 1.4.0 introduced extended group info
# 1.12 introduced the state object for groups
@@ -221,7 +222,7 @@ class HueLight(Light):
if is_group:
self.is_osram = False
self.is_philips = False
self.gamut_typ = 'None'
self.gamut_typ = GAMUT_TYPE_UNAVAILABLE
self.gamut = None
else:
self.is_osram = light.manufacturername == 'OSRAM'
@@ -229,6 +230,21 @@ class HueLight(Light):
self.gamut_typ = self.light.colorgamuttype
self.gamut = self.light.colorgamut
_LOGGER.debug("Color gamut of %s: %s", self.name, str(self.gamut))
if self.light.swupdatestate == "readytoinstall":
err = (
"Please check for software updates of the bridge "
"and/or the bulb: %s, in the Philips Hue App."
)
_LOGGER.warning(err, self.name)
if self.gamut:
if not color.check_valid_gamut(self.gamut):
err = (
"Color gamut of %s: %s, not valid, "
"setting gamut to None."
)
_LOGGER.warning(err, self.name, str(self.gamut))
self.gamut_typ = GAMUT_TYPE_UNAVAILABLE
self.gamut = None
@property
def unique_id(self):

View File

@@ -13,7 +13,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import split_entity_id, callback
from homeassistant.const import STATE_UNKNOWN, CONF_REGION
from homeassistant.const import CONF_REGION
from homeassistant.components.image_processing import (
PLATFORM_SCHEMA, ImageProcessingEntity, CONF_CONFIDENCE, CONF_SOURCE,
CONF_ENTITY_ID, CONF_NAME, ATTR_ENTITY_ID, ATTR_CONFIDENCE)
@@ -82,7 +82,7 @@ class ImageProcessingAlprEntity(ImageProcessingEntity):
def state(self):
"""Return the state of the entity."""
confidence = 0
plate = STATE_UNKNOWN
plate = None
# search high plate
for i_pl, i_co in self.plates.items():

View File

@@ -16,7 +16,7 @@ from homeassistant.components.image_processing import (
from homeassistant.core import split_entity_id
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['numpy==1.15.4']
REQUIREMENTS = ['numpy==1.16.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,69 @@
"""
Support for the QR image processing.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/image_processing.qr/
"""
from homeassistant.core import split_entity_id
from homeassistant.components.image_processing import (
ImageProcessingEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME)
REQUIREMENTS = ['pyzbar==0.1.7', 'pillow==5.4.1']
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the demo image processing platform."""
# pylint: disable=unused-argument
entities = []
for camera in config[CONF_SOURCE]:
entities.append(QrEntity(
camera[CONF_ENTITY_ID], camera.get(CONF_NAME)
))
add_entities(entities)
class QrEntity(ImageProcessingEntity):
"""QR image processing entity."""
def __init__(self, camera_entity, name):
"""Initialize QR image processing entity."""
super().__init__()
self._camera = camera_entity
if name:
self._name = name
else:
self._name = "QR {0}".format(
split_entity_id(camera_entity)[1])
self._state = None
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def name(self):
"""Return the name of the entity."""
return self._name
def process_image(self, image):
"""Process image."""
import io
from pyzbar import pyzbar
from PIL import Image
stream = io.BytesIO(image)
img = Image.open(stream)
barcodes = pyzbar.decode(img)
if barcodes:
self._state = barcodes[0].data.decode("utf-8")
else:
self._state = None

View File

@@ -20,7 +20,7 @@ from homeassistant.core import split_entity_id
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['numpy==1.15.4', 'pillow==5.4.1', 'protobuf==3.6.1']
REQUIREMENTS = ['numpy==1.16.0', 'pillow==5.4.1', 'protobuf==3.6.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -17,7 +17,7 @@ from homeassistant.helpers import discovery, config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, Dict
REQUIREMENTS = ['PyISY==1.1.0']
REQUIREMENTS = ['PyISY==1.1.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.const import (
CONF_ADDRESS, CONF_HOST, CONF_LIGHTS, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME)
CONF_SWITCHES, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.entity import Entity
@@ -32,8 +32,10 @@ CONF_TRANSITION = 'transition'
CONF_DIMMABLE = 'dimmable'
CONF_CONNECTIONS = 'connections'
DIM_MODES = ['steps50', 'steps200']
OUTPUT_PORTS = ['output1', 'output2', 'output3', 'output4']
DIM_MODES = ['STEPS50', 'STEPS200']
OUTPUT_PORTS = ['OUTPUT1', 'OUTPUT2', 'OUTPUT3', 'OUTPUT4']
RELAY_PORTS = ['RELAY1', 'RELAY2', 'RELAY3', 'RELAY4',
'RELAY5', 'RELAY6', 'RELAY7', 'RELAY8']
# Regex for address validation
PATTERN_ADDRESS = re.compile('^((?P<conn_id>\\w+)\\.)?s?(?P<seg_id>\\d+)'
@@ -85,21 +87,29 @@ def is_address(value):
LIGHTS_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): is_address,
vol.Required(CONF_OUTPUT): vol.All(vol.In(OUTPUT_PORTS), vol.Upper),
vol.Required(CONF_OUTPUT): vol.All(vol.Upper,
vol.In(OUTPUT_PORTS + RELAY_PORTS)),
vol.Optional(CONF_DIMMABLE, default=False): vol.Coerce(bool),
vol.Optional(CONF_TRANSITION, default=0):
vol.All(vol.Coerce(float), vol.Range(min=0., max=486.),
lambda value: value * 1000),
})
SWITCHES_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): is_address,
vol.Required(CONF_OUTPUT): vol.All(vol.Upper,
vol.In(OUTPUT_PORTS + RELAY_PORTS))
})
CONNECTION_SCHEMA = vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SK_NUM_TRIES, default=3): cv.positive_int,
vol.Optional(CONF_DIM_MODE, default='steps50'): vol.All(vol.In(DIM_MODES),
vol.Upper),
vol.Optional(CONF_DIM_MODE, default='steps50'): vol.All(vol.Upper,
vol.In(DIM_MODES)),
vol.Optional(CONF_NAME): cv.string
})
@@ -107,7 +117,8 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_CONNECTIONS): vol.All(
cv.ensure_list, has_unique_connection_names, [CONNECTION_SCHEMA]),
vol.Required(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHTS_SCHEMA])
vol.Optional(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHTS_SCHEMA]),
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCHES_SCHEMA])
})
}, extra=vol.ALLOW_EXTRA)
@@ -165,6 +176,10 @@ async def async_setup(hass, config):
async_load_platform(hass, 'light', DOMAIN,
config[DOMAIN][CONF_LIGHTS], config))
hass.async_create_task(
async_load_platform(hass, 'switch', DOMAIN,
config[DOMAIN][CONF_SWITCHES], config))
return True
@@ -180,11 +195,11 @@ class LcnDevice(Entity):
self._name = config[CONF_NAME]
@property
def should_poll(self) -> bool:
def should_poll(self):
"""Lcn device entity pushes its state to HA."""
return False
async def async_added_to_hass(self) -> None:
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
self.address_connection.register_for_inputs(
self.input_received)

View File

@@ -3,6 +3,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
from homeassistant.const import CONF_PORT
from homeassistant.helpers import config_entry_flow
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
@@ -15,6 +16,7 @@ CONF_BROADCAST = 'broadcast'
INTERFACE_SCHEMA = vol.Schema({
vol.Optional(CONF_SERVER): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_BROADCAST): cv.string,
})

View File

@@ -0,0 +1,177 @@
"""
Support for EverLights lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.everlights/
"""
import logging
from datetime import timedelta
from typing import Tuple
import voluptuous as vol
from homeassistant.const import CONF_HOSTS
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_EFFECT,
SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, SUPPORT_COLOR,
Light, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['pyeverlights==0.1.0']
_LOGGER = logging.getLogger(__name__)
SUPPORT_EVERLIGHTS = (SUPPORT_EFFECT | SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
SCAN_INTERVAL = timedelta(minutes=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]),
})
NAME_FORMAT = "EverLights {} Zone {}"
def color_rgb_to_int(red: int, green: int, blue: int) -> int:
"""Return a RGB color as an integer."""
return red*256*256+green*256+blue
def color_int_to_rgb(value: int) -> Tuple[int, int, int]:
"""Return an RGB tuple from an integer."""
return (value >> 16, (value >> 8) & 0xff, value & 0xff)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the EverLights lights from configuration.yaml."""
import pyeverlights
lights = []
for ipaddr in config[CONF_HOSTS]:
api = pyeverlights.EverLights(ipaddr,
async_get_clientsession(hass))
try:
status = await api.get_status()
effects = await api.get_all_patterns()
except pyeverlights.ConnectionError:
raise PlatformNotReady
else:
lights.append(EverLightsLight(api, pyeverlights.ZONE_1,
status, effects))
lights.append(EverLightsLight(api, pyeverlights.ZONE_2,
status, effects))
async_add_entities(lights)
class EverLightsLight(Light):
"""Representation of a Flux light."""
def __init__(self, api, channel, status, effects):
"""Initialize the light."""
self._api = api
self._channel = channel
self._status = status
self._effects = effects
self._mac = status['mac']
self._error_reported = False
self._hs_color = [255, 255]
self._brightness = 255
self._effect = None
self._available = True
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return '{}-{}'.format(self._mac, self._channel)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available
@property
def name(self):
"""Return the name of the device."""
return NAME_FORMAT.format(self._mac, self._channel)
@property
def is_on(self):
"""Return true if device is on."""
return self._status['ch{}Active'.format(self._channel)] == 1
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def hs_color(self):
"""Return the color property."""
return self._hs_color
@property
def effect(self):
"""Return the effect property."""
return self._effect
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_EVERLIGHTS
@property
def effect_list(self):
"""Return the list of supported effects."""
return self._effects
async def async_turn_on(self, **kwargs):
"""Turn the light on."""
hs_color = kwargs.get(ATTR_HS_COLOR, self._hs_color)
brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness)
effect = kwargs.get(ATTR_EFFECT)
if effect is not None:
colors = await self._api.set_pattern_by_id(self._channel, effect)
rgb = color_int_to_rgb(colors[0])
hsv = color_util.color_RGB_to_hsv(*rgb)
hs_color = hsv[:2]
brightness = hsv[2] / 100 * 255
else:
rgb = color_util.color_hsv_to_RGB(*hs_color, brightness/255*100)
colors = [color_rgb_to_int(*rgb)]
await self._api.set_pattern(self._channel, colors)
self._hs_color = hs_color
self._brightness = brightness
self._effect = effect
async def async_turn_off(self, **kwargs):
"""Turn the light off."""
await self._api.clear_pattern(self._channel)
async def async_update(self):
"""Synchronize state with control box."""
import pyeverlights
try:
self._status = await self._api.get_status()
except pyeverlights.ConnectionError:
if self._available:
_LOGGER.warning("EverLights control box connection lost.")
self._available = False
else:
if not self._available:
_LOGGER.warning("EverLights control box connection restored.")
self._available = True

View File

@@ -177,11 +177,6 @@ class Hyperion(Light):
def turn_off(self, **kwargs):
"""Disconnect all remotes."""
self.json_request({'command': 'clearall'})
self.json_request({
'command': 'color',
'priority': self._priority,
'color': [0, 0, 0]
})
def update(self):
"""Get the lights status."""

View File

@@ -7,7 +7,7 @@ https://home-assistant.io/components/light.lcn/
from homeassistant.components.lcn import (
CONF_CONNECTIONS, CONF_DIMMABLE, CONF_OUTPUT, CONF_TRANSITION, DATA_LCN,
LcnDevice, get_connection)
OUTPUT_PORTS, LcnDevice, get_connection)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION,
Light)
@@ -19,6 +19,9 @@ DEPENDENCIES = ['lcn']
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
"""Set up the LCN light platform."""
if discovery_info is None:
return
import pypck
devices = []
@@ -29,7 +32,13 @@ async def async_setup_platform(hass, hass_config, async_add_entities,
connection = get_connection(connections, connection_id)
address_connection = connection.get_address_conn(addr)
devices.append(LcnOutputLight(config, address_connection))
if config[CONF_OUTPUT] in OUTPUT_PORTS:
device = LcnOutputLight(config, address_connection)
else: # in RELAY_PORTS
device = LcnRelayLight(config, address_connection)
devices.append(device)
async_add_entities(devices)
@@ -50,7 +59,7 @@ class LcnOutputLight(LcnDevice, Light):
self._is_on = None
self._is_dimming_to_zero = False
async def async_added_to_hass(self) -> None:
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
self.hass.async_create_task(
@@ -119,3 +128,55 @@ class LcnOutputLight(LcnDevice, Light):
if not self._is_dimming_to_zero:
self._is_on = self.brightness > 0
self.async_schedule_update_ha_state()
class LcnRelayLight(LcnDevice, Light):
"""Representation of a LCN light for relay ports."""
def __init__(self, config, address_connection):
"""Initialize the LCN light."""
super().__init__(config, address_connection)
self.output = self.pypck.lcn_defs.RelayPort[config[CONF_OUTPUT]]
self._is_on = None
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
self.hass.async_create_task(
self.address_connection.activate_status_request_handler(
self.output))
@property
def is_on(self):
"""Return True if entity is on."""
return self._is_on
async def async_turn_on(self, **kwargs):
"""Turn the entity on."""
self._is_on = True
states = [self.pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
states[self.output.value] = self.pypck.lcn_defs.RelayStateModifier.ON
self.address_connection.control_relays(states)
await self.async_update_ha_state()
async def async_turn_off(self, **kwargs):
"""Turn the entity off."""
self._is_on = False
states = [self.pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
states[self.output.value] = self.pypck.lcn_defs.RelayStateModifier.OFF
self.address_connection.control_relays(states)
await self.async_update_ha_state()
def input_received(self, input_obj):
"""Set light state when LCN input object (command) is received."""
if not isinstance(input_obj, self.pypck.inputs.ModStatusRelays):
return
self._is_on = input_obj.get_state(self.output.value)
self.async_schedule_update_ha_state()

View File

@@ -22,7 +22,8 @@ from homeassistant.components.light import (
SUPPORT_TRANSITION, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, Light,
preprocess_turn_on_alternatives)
from homeassistant.components.lifx import (
DOMAIN as LIFX_DOMAIN, DATA_LIFX_MANAGER, CONF_SERVER, CONF_BROADCAST)
DOMAIN as LIFX_DOMAIN, DATA_LIFX_MANAGER, CONF_SERVER, CONF_PORT,
CONF_BROADCAST)
from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
@@ -230,6 +231,9 @@ class LIFXManager:
listen_ip = interface.get(CONF_SERVER)
if listen_ip:
kwargs['listen_ip'] = listen_ip
listen_port = interface.get(CONF_PORT)
if listen_port:
kwargs['listen_port'] = listen_port
lifx_discovery.start(**kwargs)
self.discoveries.append(lifx_discovery)
@@ -710,3 +714,7 @@ class LIFXStrip(LIFXColor):
if resp:
zone += 8
top = resp.count
# We only await multizone responses so don't ask for just one
if zone == top-1:
zone -= 1

View File

@@ -192,3 +192,16 @@ yeelight_set_mode:
mode:
description: Operation mode. Valid values are 'last', 'normal', 'rgb', 'hsv', 'color_flow', 'moonlight'.
example: 'moonlight'
yeelight_start_flow:
description: Start a custom flow, using transitions from https://yeelight.readthedocs.io/en/stable/yeelight.html#flow-objects
fields:
entity_id:
description: Name of the light entity.
example: 'light.yeelight'
count:
description: The number of times to run this flow (0 to run forever).
example: 0
transitions:
description: Array of transitions, for desired effect. Examples https://yeelight.readthedocs.io/en/stable/flow.html
example: '[{ "TemperatureTransition": [1900, 1000, 80] }, { "TemperatureTransition": [1900, 1000, 10] }]'

View File

@@ -39,15 +39,48 @@ CONF_MODEL = 'model'
CONF_TRANSITION = 'transition'
CONF_SAVE_ON_CHANGE = 'save_on_change'
CONF_MODE_MUSIC = 'use_music_mode'
CONF_CUSTOM_EFFECTS = 'custom_effects'
CONF_FLOW_PARAMS = 'flow_params'
DATA_KEY = 'light.yeelight'
ATTR_MODE = 'mode'
ATTR_COUNT = 'count'
ATTR_TRANSITIONS = 'transitions'
YEELIGHT_RGB_TRANSITION = 'RGBTransition'
YEELIGHT_HSV_TRANSACTION = 'HSVTransition'
YEELIGHT_TEMPERATURE_TRANSACTION = 'TemperatureTransition'
YEELIGHT_SLEEP_TRANSACTION = 'SleepTransition'
YEELIGHT_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
})
YEELIGHT_FLOW_TRANSITION_SCHEMA = {
vol.Optional(ATTR_COUNT, default=0): cv.positive_int,
vol.Required(ATTR_TRANSITIONS): [{
vol.Exclusive(YEELIGHT_RGB_TRANSITION, CONF_TRANSITION):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Exclusive(YEELIGHT_HSV_TRANSACTION, CONF_TRANSITION):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Exclusive(YEELIGHT_TEMPERATURE_TRANSACTION, CONF_TRANSITION):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Exclusive(YEELIGHT_SLEEP_TRANSACTION, CONF_TRANSITION):
vol.All(cv.ensure_list, [cv.positive_int]),
}]
}
DEVICE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TRANSITION, default=DEFAULT_TRANSITION): cv.positive_int,
vol.Optional(CONF_MODE_MUSIC, default=False): cv.boolean,
vol.Optional(CONF_SAVE_ON_CHANGE, default=False): cv.boolean,
vol.Optional(CONF_MODEL): cv.string,
vol.Optional(CONF_CUSTOM_EFFECTS): [{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_FLOW_PARAMS): YEELIGHT_FLOW_TRANSITION_SCHEMA
}]
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@@ -103,11 +136,7 @@ YEELIGHT_EFFECT_LIST = [
EFFECT_STOP]
SERVICE_SET_MODE = 'yeelight_set_mode'
ATTR_MODE = 'mode'
YEELIGHT_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SERVICE_START_FLOW = 'yeelight_start_flow'
def _cmd(func):
@@ -123,6 +152,19 @@ def _cmd(func):
return _wrap
def _parse_custom_effects(effects_config):
effects = {}
for config in effects_config:
params = config[CONF_FLOW_PARAMS]
transitions = YeelightLight.transitions_config_parser(
params[ATTR_TRANSITIONS])
effects[config[CONF_NAME]] = \
{ATTR_COUNT: params[ATTR_COUNT], ATTR_TRANSITIONS: transitions}
return effects
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Yeelight bulbs."""
from yeelight.enums import PowerMode
@@ -152,7 +194,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.debug("Adding configured %s", name)
device = {'name': name, 'ipaddr': ipaddr}
light = YeelightLight(device, device_config)
if CONF_CUSTOM_EFFECTS in config:
custom_effects = \
_parse_custom_effects(config[CONF_CUSTOM_EFFECTS])
else:
custom_effects = None
light = YeelightLight(device, device_config,
custom_effects=custom_effects)
lights.append(light)
hass.data[DATA_KEY][name] = light
@@ -163,15 +213,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
params = {key: value for key, value in service.data.items()
if key != ATTR_ENTITY_ID}
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
target_devices = [dev for dev in hass.data[DATA_KEY].values()
if dev.entity_id in entity_ids]
else:
target_devices = hass.data[DATA_KEY].values()
target_devices = [dev for dev in hass.data[DATA_KEY].values()
if dev.entity_id in entity_ids]
for target_device in target_devices:
if service.service == SERVICE_SET_MODE:
target_device.set_mode(**params)
elif service.service == SERVICE_START_FLOW:
target_device.start_flow(**params)
service_schema_set_mode = YEELIGHT_SERVICE_SCHEMA.extend({
vol.Required(ATTR_MODE):
@@ -181,11 +230,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
DOMAIN, SERVICE_SET_MODE, service_handler,
schema=service_schema_set_mode)
service_schema_start_flow = YEELIGHT_SERVICE_SCHEMA.extend(
YEELIGHT_FLOW_TRANSITION_SCHEMA
)
hass.services.register(
DOMAIN, SERVICE_START_FLOW, service_handler,
schema=service_schema_start_flow)
class YeelightLight(Light):
"""Representation of a Yeelight light."""
def __init__(self, device, config):
def __init__(self, device, config, custom_effects=None):
"""Initialize the Yeelight light."""
self.config = config
self._name = device['name']
@@ -204,6 +260,11 @@ class YeelightLight(Light):
self._min_mireds = None
self._max_mireds = None
if custom_effects:
self._custom_effects = custom_effects
else:
self._custom_effects = {}
@property
def available(self) -> bool:
"""Return if bulb is available."""
@@ -217,7 +278,7 @@ class YeelightLight(Light):
@property
def effect_list(self):
"""Return the list of supported effects."""
return YEELIGHT_EFFECT_LIST
return YEELIGHT_EFFECT_LIST + self.custom_effects_names
@property
def color_temp(self) -> int:
@@ -249,6 +310,16 @@ class YeelightLight(Light):
"""Return maximum supported color temperature."""
return self._max_mireds
@property
def custom_effects(self):
"""Return dict with custom effects."""
return self._custom_effects
@property
def custom_effects_names(self):
"""Return list with custom effects names."""
return list(self.custom_effects.keys())
def _get_hs_from_properties(self):
rgb = self._properties.get('rgb', None)
color_mode = self._properties.get('color_mode', None)
@@ -435,15 +506,17 @@ class YeelightLight(Light):
EFFECT_SLOWDOWN: slowdown,
}
if effect in effects_map:
if effect in self.custom_effects_names:
flow = Flow(**self.custom_effects[effect])
elif effect in effects_map:
flow = Flow(count=0, transitions=effects_map[effect]())
if effect == EFFECT_FAST_RANDOM_LOOP:
elif effect == EFFECT_FAST_RANDOM_LOOP:
flow = Flow(count=0, transitions=randomloop(duration=250))
if effect == EFFECT_WHATSAPP:
elif effect == EFFECT_WHATSAPP:
flow = Flow(count=2, transitions=pulse(37, 211, 102))
if effect == EFFECT_FACEBOOK:
elif effect == EFFECT_FACEBOOK:
flow = Flow(count=2, transitions=pulse(59, 89, 152))
if effect == EFFECT_TWITTER:
elif effect == EFFECT_TWITTER:
flow = Flow(count=2, transitions=pulse(0, 172, 237))
try:
@@ -518,3 +591,28 @@ class YeelightLight(Light):
self.async_schedule_update_ha_state(True)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set the power mode: %s", ex)
@staticmethod
def transitions_config_parser(transitions):
"""Parse transitions config into initialized objects."""
import yeelight
transition_objects = []
for transition_config in transitions:
transition, params = list(transition_config.items())[0]
transition_objects.append(getattr(yeelight, transition)(*params))
return transition_objects
def start_flow(self, transitions, count=0):
"""Start flow."""
import yeelight
try:
flow = yeelight.Flow(
count=count,
transitions=self.transitions_config_parser(transitions))
self._bulb.start_flow(flow)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set effect: %s", ex)

View File

@@ -12,11 +12,10 @@ from aiohttp import web
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import \
DOMAIN as DEVICE_TRACKER_DOMAIN
DOMAIN as DEVICE_TRACKER
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, ATTR_LATITUDE, \
ATTR_LONGITUDE, STATE_NOT_HOME, CONF_WEBHOOK_ID, ATTR_ID, HTTP_OK
from homeassistant.helpers import config_entry_flow
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
_LOGGER = logging.getLogger(__name__)
@@ -57,9 +56,6 @@ WEBHOOK_SCHEMA = vol.All(
async def async_setup(hass, hass_config):
"""Set up the Locative component."""
hass.async_create_task(
async_load_platform(hass, 'device_tracker', DOMAIN, {}, hass_config)
)
return True
@@ -93,7 +89,7 @@ async def handle_webhook(hass, webhook_id, request):
if direction == 'exit':
current_state = hass.states.get(
'{}.{}'.format(DEVICE_TRACKER_DOMAIN, device))
'{}.{}'.format(DEVICE_TRACKER, device))
if current_state is None or current_state.state == location_name:
location_name = STATE_NOT_HOME
@@ -140,12 +136,18 @@ async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
DOMAIN, 'Locative', entry.data[CONF_WEBHOOK_ID], handle_webhook)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, DEVICE_TRACKER)
)
return True
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
config_entry_flow.register_webhook_flow(

View File

@@ -6,6 +6,9 @@ https://home-assistant.io/components/device_tracker.locative/
"""
import logging
from homeassistant.components.device_tracker import \
DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.locative import DOMAIN as LOCATIVE_DOMAIN
from homeassistant.components.locative import TRACKER_UPDATE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -13,9 +16,11 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['locative']
DATA_KEY = '{}.{}'.format(LOCATIVE_DOMAIN, DEVICE_TRACKER_DOMAIN)
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an endpoint for the Locative device tracker."""
async def async_setup_entry(hass, entry, async_see):
"""Configure a dispatcher connection based on a config entry."""
async def _set_location(device, gps_location, location_name):
"""Fire HA event to set location."""
await async_see(
@@ -24,5 +29,13 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
location_name=location_name
)
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
hass.data[DATA_KEY] = async_dispatcher_connect(
hass, TRACKER_UPDATE, _set_location
)
return True
async def async_unload_entry(hass, entry):
"""Unload the config entry and remove the dispatcher connection."""
hass.data[DATA_KEY]()
return True

View File

@@ -17,7 +17,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
from homeassistant.components import group
ATTR_CHANGED_BY = 'changed_by'
@@ -150,5 +150,5 @@ class LockDevice(Entity):
"""Return the state."""
locked = self.is_locked
if locked is None:
return STATE_UNKNOWN
return None
return STATE_LOCKED if locked else STATE_UNLOCKED

View File

@@ -12,7 +12,7 @@ from homeassistant.components.verisure import (
CONF_LOCKS, CONF_DEFAULT_LOCK_CODE, CONF_CODE_DIGITS)
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
ATTR_CODE, STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED)
ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED)
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ class VerisureDoorlock(LockDevice):
def __init__(self, device_label):
"""Initialize the Verisure lock."""
self._device_label = device_label
self._state = STATE_UNKNOWN
self._state = None
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
self._change_timestamp = 0
@@ -80,7 +80,7 @@ class VerisureDoorlock(LockDevice):
"$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState",
self._device_label)
if status == 'UNLOCKED':
self._state = STATE_UNLOCKED
self._state = None
elif status == 'LOCKED':
self._state = STATE_LOCKED
elif status != 'PENDING':
@@ -96,7 +96,7 @@ class VerisureDoorlock(LockDevice):
def unlock(self, **kwargs):
"""Send unlock command."""
if self._state == STATE_UNLOCKED:
if self._state is None:
return
code = kwargs.get(ATTR_CODE, self._default_lock_code)

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