Compare commits

...

215 Commits

Author SHA1 Message Date
Paulus Schoutsen
1e28c752e1 Merge pull request #16988 from home-assistant/rc
0.79.1
2018-09-30 13:31:29 +02:00
Paulus Schoutsen
677714ecab Bumped version to 0.79.1 2018-09-30 10:22:49 +02:00
Rohan Kapoor
e2aadc3227 Bump zm-py to 0.0.4 (#16979) 2018-09-30 10:22:42 +02:00
Greg Laabs
c45b240026 Fix ISY blocking bug (#16978)
This fix results in `is_on` returning False if the state is unknown (which was a bug in 0.79).
2018-09-30 10:22:42 +02:00
Jason Hu
32d652884b Override unique_id of NestActivityZoneSensor (#16961) 2018-09-30 10:22:41 +02:00
Anders Melchiorsen
3528f8e647 Fix exception during history_stats startup (#16932)
* Fix exception during history_stats startup

* Do not track changes during startup

* Ignore args
2018-09-30 10:22:41 +02:00
Jason Hu
3e6cf8f59f Optimize Ring Sensors platform setup (#16886) 2018-09-30 10:22:41 +02:00
Paulus Schoutsen
c93879074b Merge pull request #16940 from home-assistant/rc
0.79.0
2018-09-28 16:51:25 +02:00
Paulus Schoutsen
f476d781ec Version bump to 0.79.0 2018-09-28 15:28:24 +02:00
Paulus Schoutsen
7bfe0e1c00 Bumped version to 0.79.0b3 2018-09-27 23:10:42 +02:00
Paulus Schoutsen
fb39641eef Handle exception handling websocket command (#16927)
* Handle exception handling websocket command

* lint

* Lint2
2018-09-27 23:10:36 +02:00
Paulus Schoutsen
b7e03f6973 Prevent discovered Tradfri while already configured (#16891)
* Prevent discovered Tradfri while already configured

* Lint
2018-09-27 23:10:35 +02:00
Charles Garwood
7597e30efb Add unique_id to Nest Sensors (#16869)
* Add unique_id

* Add device_info

* Fix typo

* Update __init__.py
2018-09-27 23:10:35 +02:00
Paulus Schoutsen
51dbc988f9 Update translations 2018-09-27 23:08:04 +02:00
Paulus Schoutsen
71333a15f7 Bump frontend to 20180927.0 2018-09-27 23:08:04 +02:00
Paulus Schoutsen
7b68f344e3 Bumped version to 0.79.0b2 2018-09-26 11:21:22 +02:00
Paulus Schoutsen
824e59499f Make ring sync again (#16866) 2018-09-26 11:21:10 +02:00
Paulus Schoutsen
ff9377d1d9 Fix MQTT discovery (#16864)
* Fix MQTT discovery

* Update __init__.py
2018-09-26 11:21:09 +02:00
Charles Garwood
cf0d0fb33e Device Registry Support for iOS Sensors (#16862)
* Add device_info property to iOS sensors for device registry

* Remove unused logger import

* Fix spacing

* lint

* Lint
2018-09-26 11:21:09 +02:00
Paulus Schoutsen
3aaf619fc3 Update translations 2018-09-26 11:20:07 +02:00
Paulus Schoutsen
e375b63902 Update frontend to 20180926.0 2018-09-26 11:20:00 +02:00
Paulus Schoutsen
61a2d09342 Bumped version to 0.79.0b1 2018-09-25 15:39:42 +02:00
Paulus Schoutsen
345c886dec Add unique ID and device info to Nest camera (#16846)
* Add unique ID and device info to Nest camera

* Remove sw version
2018-09-25 15:39:36 +02:00
Paulus Schoutsen
b9043ef7a7 Allow MQTT discovery (#16842) 2018-09-25 15:39:36 +02:00
Paulus Schoutsen
356040d506 Support old tradfri config format (#16841) 2018-09-25 15:39:35 +02:00
Rohan Kapoor
0431e38aa2 Bump zm-py to 0.0.3 (#16835) 2018-09-25 15:39:34 +02:00
Paulus Schoutsen
4c32ad3b48 Don't warn but info when on dev mode (#16831) 2018-09-25 15:39:33 +02:00
Paulus Schoutsen
7d68ec1110 Merge branch 'dev' into rc 2018-09-24 12:14:43 +02:00
Paulus Schoutsen
c352b6fa59 Version bump to 0.79.0b0 2018-09-24 12:13:52 +02:00
Paulus Schoutsen
0bd94d8b56 Remove unused translation key 2018-09-24 12:02:26 +02:00
Paulus Schoutsen
3e2a9afff0 Another update translations 2018-09-24 12:01:34 +02:00
Paulus Schoutsen
d4b239d1d4 Update translations 2018-09-24 12:01:01 +02:00
Paulus Schoutsen
b2a9e203f2 Update frontend to 20180924.0 2018-09-24 11:58:03 +02:00
Greg Laabs
dc1534c6d1 Fix some unhandled exceptions due to missing null checks (#16812)
Fixed a couple cases that would produce errors when the ISY node status was None or `-inf`.
2018-09-24 11:43:00 +02:00
Robin Clarke
589554ad16 Allow soundtouch to play https content too (#16713) 2018-09-24 11:16:28 +02:00
Ville Skyttä
33d6c99f19 Add Python 3.7 classifier (#16645) 2018-09-24 11:11:24 +02:00
Daniel Høyer Iversen
1f74adae2a Broadlink service name (#16345)
* Broadlink. slugify service name

* style
2018-09-24 11:10:33 +02:00
Jason Hu
7a77951bb4 Add Notify MFA module (#16314)
* Add Notify MFA

* Fix unit test

* Address review comment, change storage implementation

* Add retry limit to mfa module

* Fix loading

* Fix invalaid login log processing

* Typing

* Change default message template

* Change one-time password to 8 digit

* Refactoring to not save secret

* Bug fixing

* Change async_initialize method name to aysnc_initialize_login_mfa_step

* Address some simple fix code review comment
2018-09-24 11:06:50 +02:00
Thomas Lovén
ad47ece5c6 Allow split component definitions in packages (#16177)
* Allow split component definitions in packages

Two different configuration styles are described in
https://www.home-assistant.io/docs/configuration/devices/#style-2-list-each-device-separately

But only one is allowed in packages according to
https://www.home-assistant.io/docs/configuration/packages/

This change allows "Style 2" configuration in packages.

* Added test for split component definition in packages
2018-09-24 10:17:24 +02:00
emontnemery
5ee4718e24 Remove discovered MQTT Switch device when discovery topic is cleared (#16605)
* Remove discovered device when discovery topic is cleared

* Move entity removal away from mqtt discovery

* Move discovery update to mixin class

* Add testcase

* Review comments
2018-09-24 10:11:49 +02:00
Martin Berg
a5cb4e6c2b Use pyspcwebgw for SPC component (#16214)
* Use pyspcwebgw library.

* Support alarm triggering.

* Update requirements.

* Add pyspcwebgw to test reqs.

* Also update script.

* Use dispatcher.

* Address review feedback.
2018-09-24 10:10:10 +02:00
Baptiste Lecocq
4fd2f773ad Add linky sensor (#16468)
* Add linky component

* Update requirements_all.txt

* Add timeout to pylinky requests

* Make linky platform fail fast
2018-09-24 08:09:34 +02:00
Jason Hu
564ad7e22a Rework chromecast fix (#16804)
* Revert changes of #16471, and fix the platform setup issue

* Fix unit test

* Fix

* Fix comment

* Fix test

* Address review comment

* Address review comment
2018-09-23 23:35:07 +02:00
mvn23
329d9dfc06 Improve opentherm_gw state detection (#16809)
Only show the platform in STATE_HEAT when the boiler is actually heating.
2018-09-23 22:16:24 +02:00
Anders Melchiorsen
2258c56d34 Handle netgear_lte connection errors (#16806) 2018-09-23 18:58:09 +02:00
Daniel Shokouhi
e90abf1007 Set botvac state when offline (#16805) 2018-09-23 12:35:18 +02:00
Paul Biester
eaee55175b Add configurable host for bbox routers (#16778)
* Add configurable host for bbox routers

Add configurable host for bbox router running on non-default IP addresses.

* Fix unused import

Fix unused import which also resolves "line too long"

* Fix wrong import order

* Update validation
2018-09-23 10:37:53 +02:00
huangyupeng
539b86e079 Add Tuya cover state (#16709)
* add cover open and close state

* add tuya cover state

* fix pylint

* fix pylint problem
2018-09-23 10:36:33 +02:00
tadly
127395ae8d Upgrade zeroconf to 0.21.3 (#16789) 2018-09-23 09:50:59 +02:00
Greg Laabs
eca1f050cd Bump sucks (Ecovacs) lib to 0.9.3 (#16803)
* Bump sucks (Ecovacs) lib to 0.9.3

Changed code that was in place as a workaround pre-0.9.2. This version bump fixes a few issues.

* Update requirements_all
2018-09-23 09:43:01 +02:00
Rohan Kapoor
0d681b0ba6 Bump zm-py up to 0.0.2 (#16800) 2018-09-23 09:42:11 +02:00
Daniel Shokouhi
94d38a1c6b Bump pybotvac to 0.0.10 (#16799) 2018-09-23 09:41:45 +02:00
Daniel Høyer Iversen
cfd1aec741 Update Tibber lib (#16795) 2018-09-22 18:25:17 +02:00
Daniel Shokouhi
eec6722cf4 Fix return to base logic for neato (#16776) 2018-09-22 11:34:46 +02:00
Paulus Schoutsen
fce275d29c Merge pull request #16787 from home-assistant/rc
0.78.3
2018-09-22 11:20:29 +02:00
Paulus Schoutsen
5eda5f2f7b Bumped version to 0.78.3 2018-09-22 11:18:54 +02:00
edif30
5ab580ab34 Bump gtts-token to 1.1.2 (#16775)
* bump gtts-token to 1.1.2

* bump gtts-token to 1.1.2

* bump gtts-token to 1.1.2
2018-09-22 11:18:47 +02:00
Paulus Schoutsen
5613816476 Fix Windows loop (#16737)
* Fix Windows loop

* Fix windows Ctrl+C

* Move windows restart handler out of setup_and_run_hass
2018-09-22 09:54:37 +02:00
Gerard
e9c7fe184d Upgrade bimmer_connected to 0.5.2 (#16780) 2018-09-22 08:52:57 +02:00
Fabian Affolter
41bb4760f7 Upgrade restrictedpython to 4.0b5 (#16779) 2018-09-21 23:21:00 +02:00
edif30
ee3f17d5c7 Bump gtts-token to 1.1.2 (#16775)
* bump gtts-token to 1.1.2

* bump gtts-token to 1.1.2

* bump gtts-token to 1.1.2
2018-09-21 21:22:27 +02:00
Malte Franken
18d37ff0fd GeoJSON platform (#16610)
* initial version of geojson platform

* unit tests for geo json platform added; smaller bugfixes and code cleanups

* fixing pylint issues

* moved all code accessing the external feed into separate library; refactored platform and tests to work with that new library

* fixing lint

* small refactorings
2018-09-21 21:15:57 +02:00
Robert Svensson
7fe0d8b2f4 deCONZ cover support (#16759)
deCONZ cover platform for Keen vents
2018-09-21 19:59:20 +02:00
Paulus Schoutsen
8b42d0c471 Add confirmation to Cast/Sonos/iOS config entries (#16769)
* Add confirmation to Cast/Sonos/iOS config entries

* Remove redundant code
2018-09-21 16:34:37 +02:00
Giuseppe
213171769d Refactored units and icons for the Dyson sensors (#14550)
* Refactored units and icons for the Dyson sensors

* Adapted unit tests to the new device names and unit of measurements

* Use None as empty unit of measurement

* Unrelated overall improvements following code review

* Adapted tests to new constructors as per previous commit

* Make sure the sensors have their `hass` attribute set in the test environment
2018-09-21 15:55:07 +02:00
Malte Franken
d5813cf167 Make rest sensor and binary sensor more efficient (#14484)
* create binary sensor even if initial update fails

* fixed broken test assertion

* fixed broken test assertion

* avoid fetching resource twice - manually in the setup_platform and then through add_devices

* raising PlatformNotReady instead of creating the sensor if the initial rest call fails; throttling the update to avoid fetching the same resource again immediately after setting up sensor

* rolled back throttling of the rest update call; can still avoid updating the binary sensor's rest resoure twice; fixed tests

* typo
2018-09-21 15:54:50 +02:00
Paulus Schoutsen
3e59ffb33a Add tradfri device info (#16768) 2018-09-21 14:47:52 +02:00
Maciej Bieniek
a0a54dfd5b Add unique_id to mqtt camera (#16569)
* Add unique_id to mqtt camera

* Remove whitespaces

* Add test for unique_id

* Add blank line
2018-09-21 13:09:54 +02:00
cdce8p
3bfe9e757e Add Carbon Monoxide HomeKit Sensor (#16664) 2018-09-21 12:51:02 +02:00
Alexei Chetroi
9fdf123a2f Zha switch schedule update state (#16621)
* switch.zha: Schedule state update.

if turning switch On or Off operation was successful, then schedule
state update

* switch.zha: Update debug logging.
2018-09-21 12:11:23 +02:00
Evan Bruhn
aeaf694552 Add Logi Circle component, camera and sensor platform (#16540)
* Added Logi Circle platform, camera and sensor

* Integrated with Logo Circle API’s feature detection to exclude sensors not supported by device. Added services for recording livestream and taking a snapshot from the livestream.

* Migrated livestream snapshot and recording functionality out of home assistant components and into the logi circle API wrapper. Added services.yaml entries for logi services.

* Added new Logi sensor types, updated to latest version of `logi_circle` and tidy up in preparation for pull request.

- Renamed `logi_set_mode` to `logi_set_config`.
- Live stream recording and snapshot methods now respect whitelisted path configuration.
- Added `streaming_mode` and `speaker_volume` sensors.
- Moved model-specific turn on/off logic to `logi_circle` library.

* Renamed `logi` component domain to `logi_circle`.

* Updates based on PR feedback

* Added timeout of 15s to logi_circle initial setup requests (login and grabbing cameras).
* Added unique ID (uses MAC address for camera platform, MAC address + sensor type for sensor platform).
* Added battery level and battery charging attributes to camera.
* Removed static attributes from device_state_attributes.
* Replaced STATE_UNKNOWN with None, replaced ‘on’ & ‘off’ with STATE_ON and STATE_OFF.
* Removed redundant SCAN_INTERVAL in sensor, removed redundant hass param from async_setup_platform in camera and sensor.
* Style tweaks.

* Replaced `asyncio.wait_for` with `async_timeout` to be consistent with other components.
2018-09-21 12:00:15 +02:00
Rohan Kapoor
3d1c8ee467 Implement support for complex templates in script delays (#16442)
* Implement support for complex templates in script delays

* Swap out dict instead of collections.Mapping
2018-09-21 11:57:01 +02:00
PhracturedBlue
98b92c78c0 Add Call Data Log platform. Mailboxes no longer require media (#16579)
* Add multiple mailbox support

* Fix extraneous debugging

* Add cdr support

* liniting errors

* Mailbox log messages should mostly be debug.  Fix race condition with initializing CDR

* async decorators to async

* Lint fixes

* Typo

* remove unneeded parameter

* Fix variable names

* Fix async calls from worker thread.  Other minor cleanups

* more variable renames
2018-09-21 11:55:12 +02:00
Paulus Schoutsen
2ac16b12c1 Merge pull request #16770 from home-assistant/rc
0.78.2
2018-09-21 11:40:04 +02:00
Paulus Schoutsen
df67093441 Fix faulty color temp crashing google (#16758)
* Fix faulty color temp crashing google

* Rename

* Print warning for incorrect color temp
2018-09-21 10:51:46 +02:00
Anders Melchiorsen
c475a876ce Upgrade pysonos to 0.0.2 (#16761) 2018-09-21 09:21:56 +02:00
Robert Svensson
90c18d1c15 deCONZ add via_hub attribute for device registry (#16760)
* deCONZ add via_hub attribute for device registry

* A shorter way to get bridgeid
2018-09-21 09:21:44 +02:00
Anders Melchiorsen
78b6439ee6 Use pysonos for Sonos media player (#16753) 2018-09-20 23:50:11 +02:00
Paulus Schoutsen
092c146eae Add option to disable specific integrations (#16757)
* Add option to disable specific integrations

* Lint
2018-09-20 23:46:51 +02:00
Paulus Schoutsen
44a98fb77a Bumped version to 0.78.2 2018-09-20 20:26:25 +02:00
Fabian Affolter
03d93bea34 Upgrade zeroconf to 0.21.2 (#16730) 2018-09-20 20:25:39 +02:00
Fabian Affolter
9dbac9b033 Upgrade zeroconf to 0.21.1 (#16687) 2018-09-20 20:25:38 +02:00
tadly
9e86f0498b Upgrade zeroconf to 0.21.0 (#16647) 2018-09-20 20:25:38 +02:00
Zoé Bőle
03de658d4d Changed save_on_change to default to False (#16744) 2018-09-20 20:24:01 +02:00
Alexei Chetroi
3ea8c25e1f light.zha: Catch exceptions for all commands. (#16752)
Catch exceptions for all operations which may fail because of device
reachibility
More verbose debug logging on operations
2018-09-20 20:23:09 +02:00
Pascal Vizeli
c6ccbed828 Small cleanup for slack (#16743) 2018-09-20 15:14:43 +02:00
Paulus Schoutsen
e58836f99f Add subscription info endpoint (#16727)
* Add subscription info endpoint

* Lint

* Lint

* Make decorator

* Lint
2018-09-20 14:53:13 +02:00
Paulus Schoutsen
874225dd67 Merge branch 'master' into dev 2018-09-20 13:57:35 +02:00
Paulus Schoutsen
0d0d5c8c2c Merge pull request #16742 from home-assistant/rc
0.78.1
2018-09-20 13:55:09 +02:00
Paulus Schoutsen
fc6cc22b6d Bump frontend to 20180920.0 2018-09-20 13:09:43 +02:00
Fabian Affolter
39ea9a8c90 Upgrade shodan to 1.10.2 (#16736) 2018-09-20 11:34:37 +02:00
Paulus Schoutsen
c7d5f7698e Bumped version to 0.78.1 2018-09-20 11:32:26 +02:00
Jason Hu
9bbd61cbe4 Upgrade netdisco to 2.1.0 (#16735) 2018-09-20 11:32:16 +02:00
Jason Hu
7a7a164cb8 Handle chromecast CONNECTION_STATUS_DISCONNECTED event (#16732) 2018-09-20 11:32:15 +02:00
Andreas Oberritter
8379567636 SnmpSensor: Fix async_update (#16679) (#16716)
Bugfix provided by awarecan.
2018-09-20 11:32:14 +02:00
Daniel Høyer Iversen
93af3c57ff Avoid calling yr update every second for a minute ones every hour (#16731)
* Avoid calling yr update every second for a minute one every hour

* style
2018-09-20 11:31:05 +02:00
Jason Hu
d1acb0326c Upgrade netdisco to 2.1.0 (#16735) 2018-09-20 11:11:39 +02:00
Fabian Affolter
35005474f8 Upgrade keyring to 15.1.0 (#16734) 2018-09-20 11:11:08 +02:00
Jason Hu
3a45481b5b Handle chromecast CONNECTION_STATUS_DISCONNECTED event (#16732) 2018-09-20 10:48:45 +02:00
Daniel Høyer Iversen
aa7635398a Met.no weather platform (#16582)
* Add met.no weather component

* requirements .coveragerx

* use lib

* style

* style

* renam function to _fetch_data

* Only update once per hour

* style
2018-09-20 10:32:14 +02:00
Fabian Affolter
d3658c4af9 Upgrade zeroconf to 0.21.2 (#16730) 2018-09-20 09:38:35 +02:00
Fabian Affolter
dfe38b4d5a Upgrade youtube_dl to 2018.09.18 (#16729) 2018-09-20 09:37:44 +02:00
Vikram Gorla
fcb84d951e On-demand update of swiss public transport sensor (#16723)
* Sensor values to be updated only when required (only after the train has crossed).
Looking at the documentation on https://transport.opendata.ch/docs.html, delay information is available only on stationboard, so no need to query often if there is no "real"time info.

* Bumping up version of python_opendata_transport to 0.1.4 in order to catch client errors like throttling rejection (HTTP 429)

* pleasing the hound

* bumping python_opendata_transport to 0.1.4
2018-09-20 08:45:16 +02:00
Nikolay Kasyanov
27eede724c Add unique_id to mqtt_json light (#16721)
Applies changes from #16303 to mqtt_json component.
Fixes #16600.
2018-09-20 08:15:17 +02:00
kunago
258beb9cd3 Upgrading librouteros version (#16718) 2018-09-20 07:52:06 +02:00
Charles Garwood
da882672bd Save disabled_by in entity registry (#16699)
* Save disabled_by in entity registry

* Add trailing comma
2018-09-20 00:52:10 +02:00
geekofweek
60dfd68083 MyQ Open State Fix (#16681)
* MyQ Open State Fix

Fixes issues with MyQ reporting an open state

* Update myq.py

* MyQ STATE_OPEN Fix

remove un-needed code
2018-09-19 22:03:47 +02:00
Andreas Oberritter
6e4a6cc069 SnmpSensor: Fix async_update (#16679) (#16716)
Bugfix provided by awarecan.
2018-09-19 22:01:54 +02:00
Paulus Schoutsen
a1c524d372 Config flow tradfri (#16665)
* Fix comments

* Add config flow tests

* Fix Tradfri light tests

* Lint

* Remove import group from config flow

* fix stale comments
2018-09-19 21:21:43 +02:00
Ville Skyttä
3160fa5de8 Make pylint report non-LF linefeeds per the style guidelines (#16601) 2018-09-19 15:51:57 +02:00
Ville Skyttä
d4b7057a3d Use posargs in tox lint env (#16646)
Allows linting specified files or dirs only.
2018-09-19 15:48:04 +02:00
Ville Skyttä
227a1b919b More isort preparations (#16633)
* Adjust config closer to currently established style conventions

* Adjust some imports for better outcome with their comments
2018-09-19 15:40:36 +02:00
Matthias Urlichs
0121e3cb04 Remove usage of "run_until_complete" (#16617)
* De-run_forever()-ization

* Use asyncio.run (or our own implementation on Python <3.7)
* hass.start is only used by tests
* setup_and_run_hass() is now async
* Add "main" async hass.run method
* move SIGINT handling to helpers/signal.py
  * add flag to .run to disable hass's signal handlers
* Teach async_start and async_stop to not step on each other
  (more than necessary)

* shorten over-long lines

* restore missing "import asyncio"

* move run_asyncio to homeassistant.util.async_

* LOGGER: warn => warning

* Add "force" flag to async_stop

only useful for testing

* Add 'attrs==18.2.0' to requirements_all.txt

Required for keeping requirements_test_all.txt in sync, where it is in
turn required to prevent auto-downgrading "attrs" during "pip install"

* Fixes for mypy

* Fix "mock_signal" fixture

* Revert mistaken edit

* Flake8 fixes

* mypy fixes

* pylint fix

* Revert adding attrs== to requirements_test*.txt

solved by using "pip -c"

* Rename "run" to "async_run", as per calling conventions
2018-09-19 15:40:02 +02:00
Paulus Schoutsen
da108f1999 bump frontend to 20180919.0 2018-09-19 15:17:05 +02:00
Pascal Vizeli
d376049a3f Use one regex for Hass.io URL check (#16710) 2018-09-19 12:57:55 +02:00
Ville Skyttä
7f462ba0ec Upgrade mypy to 0.630 (#16674) 2018-09-19 08:58:58 +02:00
Fabien Piuzzi
b7ef4dddb4 Netdata configuration change: Allows multiple elements per group (#16656)
Allows multiple Netdata elements per group
2018-09-19 00:42:09 +02:00
Adam Dullage
56b0d2e99f Added support for Starling Bank (#16522)
* Added support for Starling Bank

* Fixing Lint Issues

* Resolving Change Requests

* HTTP Error Handling & Sandbox URL Option

* Update starlingbank Requirement to v1.2

* Minor changes
2018-09-18 15:55:10 +02:00
Maikel Punie
8e7f783da8 Added velbus counter sensors, updated to py-velbus 2.0.20 (#16683) 2018-09-18 15:51:22 +02:00
Fabian Affolter
1913d07c39 Upgrade zeroconf to 0.21.1 (#16687) 2018-09-18 15:49:54 +02:00
Glenn Waters
2a85ed7236 Streamline log messages (#16243) 2018-09-18 15:06:52 +02:00
Fabian Affolter
cba3a5b055 Upgrade paho-mqtt to 1.4.0 (#16688) 2018-09-18 14:59:39 +02:00
Fabian Affolter
d2246d5a4f Fix test 2018-09-18 12:15:55 +02:00
Greg Laabs
72419a1afe Fix Ecovacs vacuums showing "None" for name (#16654)
Was previously checking for a missing key, when should have been checking for the value being None
2018-09-18 08:30:20 +02:00
Fabian Affolter
a7325ebe1f Suppress traceback and log error (#16669) 2018-09-18 07:55:13 +02:00
Aaron Bach
27d50d388f Fixes an AirVisual bug where response data is missing (#16673) 2018-09-17 14:44:18 -06:00
Tsvi Mostovicz
25712f16b3 Jewish calendar sensor (#16393)
* Initial commit for  jewish calendar sensor

* Make check for logging errors into it's own function

* Can't use f-strings as we need to support python3.5

* Implement basic functionality: printing of date

* Update requirements_all.txt

* Allow user to specify date for sensor

* Add hdate to test requirements

* Update to match pull request

* Support date output in hebrew

* Limit languages to english and hebrew

* Add name back to sensor

* Change icon to be calendar-today

* Add multiple sensors

* Fix tests

* Make Hound happy, remove unused imported class

* hdate expects datetime.date not datetime.datetime

* Return sensor name

* Times should be returned as time object, not datetime

* Add myself to codeowners for jewish calendar component

* Return actual reading, not index

* Add more tests. Currently failing.

Will need to update hdate API and version before continuing.

* Fix weekly portion test

* Make all tests pass

* Make travis happy and add a test so it doesnt happen again

* Remove defaults in __init__ method

* Change sensor state variable to local variable in update() method

* Minor changes
2018-09-17 22:43:31 +02:00
Paulus Schoutsen
9e59fc5d05 Merge pull request #16666 from home-assistant/rc
0.78.0
2018-09-17 19:03:37 +02:00
Paulus Schoutsen
366e270e94 Bump frontend to 20180916.0 2018-09-17 18:33:41 +02:00
Paulus Schoutsen
4b30cbbf3b Update translations 2018-09-17 14:18:20 +02:00
Paulus Schoutsen
41ac2a3c73 Bump frontend to 20180917.0 2018-09-17 14:18:04 +02:00
Paulus Schoutsen
b8257866f5 Clean up device update, add via-hub (#16659)
* Clean up device update, add via-hub

* Test loading/saving data

* Lint

* Add to Hue"

* Lint + tests
2018-09-17 13:39:30 +02:00
Paulus Schoutsen
849a93e0a6 Update translations 2018-09-17 10:48:22 +02:00
Paulus Schoutsen
f918d62571 version bump to 0.78.0 2018-09-17 10:48:09 +02:00
damarco
1c251009fe Add zha device entity (#14579)
* Add endpoint entity

* Fix lint error

* Add nwk address as device state attribute

* Change to ZhaDeviceEntity

* Show last_seen only if offline

* Remove obsolete _discover_endpoint_info()
2018-09-17 10:42:21 +02:00
Paulus Schoutsen
201fd4afee Add config entries to connection class (#16618) 2018-09-17 10:12:46 +02:00
Anders Melchiorsen
3e0c6c176a Rework timer delays (#16650)
* Calibrate timer for each tick

* Return of timer out of sync detection
2018-09-17 10:10:50 +02:00
Alexei Chetroi
44fdfdf695 Log raw result of configure_reporting() command. (#16655) 2018-09-17 07:45:44 +02:00
Fabian Affolter
fea1c921fc Fix link to docs (#16652) 2018-09-17 07:45:01 +02:00
Fabian Affolter
5e3e441aa0 Upgrade holidays to 0.9.7 (#16651) 2018-09-17 07:44:23 +02:00
Daniel Perna
a1e6e04a5e Update pyhomematic to 0.1.49 (#16649) 2018-09-16 22:56:01 +02:00
tadly
2002497d09 Upgrade zeroconf to 0.21.0 (#16647) 2018-09-16 22:53:25 +02:00
Daniel Høyer Iversen
baeb791d84 Upgrade Switchmate lib (#16637)
* new version switchmate lib, fix bug, and enable more logging in lib

* new version switchmate lib, fix bug, and enable more logging in lib
2018-09-16 13:58:26 +02:00
Abílio Costa
9c9df793b4 New EDP re:dy component (#16426)
* add new EDP re:dy platform

* lint

* move api code to pypi module; fix lint

* fix lint; remove unused import

* pass aiohttp client session and hass loop to platform

* update requirements_all.txt

* fix docstring lint

* normalize quotes

* use async setup_platform

* improve entities update mechanism

* doc lint

* send update topic only after loading platforms

* lint whitespaces

* mute used-before-assignment pylint false error
2018-09-16 01:17:47 +02:00
Mattias Welponer
05922ac56a Add new devices to HomematicIP Cloud (#16636)
* Add support for outdoor temperature sensor and cleanup

* Add support for rotary handle and water sensor

* Fix comment
2018-09-15 21:28:49 +02:00
Jason Hu
34deaf8849 Add valid_window=1 to TOTP verify (#16625) 2018-09-15 13:28:25 +02:00
Ville Skyttä
cc38981a38 Update developer doc links to developers.home-assistant.io (#16622) 2018-09-15 13:27:37 +02:00
Paulus Schoutsen
18ce5092b4 Bumped version to 0.78.0b3 2018-09-15 12:46:02 +02:00
Paulus Schoutsen
7f7372198a Update translations 2018-09-15 12:45:54 +02:00
Pascal Vizeli
abe61c5529 Rewrite bluetooth le (#16592)
* Rewrite bluetooth le

* Update requirements_all.txt

* Update gen_requirements_all.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py
2018-09-15 12:45:23 +02:00
Jason Hu
b231fa2616 Fix broken bluetooth tracker (#16589) 2018-09-15 12:45:23 +02:00
Jason Hu
336289d7e7 Add retry limit for chromecast connection (#16471) 2018-09-15 12:45:22 +02:00
Rohan Kapoor
19514ea500 Clean up MjpegCamera by removing unnused hass object in __init__ (#16628) 2018-09-15 11:26:29 +02:00
Sören Oldag
00918af94d Upgrade pwmled to 1.3.0 (#16624) 2018-09-15 10:08:52 +02:00
Ville Skyttä
e054e4da1b Small huawei_lte improvements (#16626)
* Add bunch of RouterData tests

* Avoid raising AttributeError from RouterData.__getitem__

* Use new style string formatting

* Use {key: value} instead of dict(key=value)
2018-09-15 10:42:36 +03:00
Rohan Kapoor
0c945d81c8 Add @rohankapoorcom to CODEOWNERS for the zoneminder platform (#16627) 2018-09-15 09:25:21 +02:00
Rohan Kapoor
1ca09ea36f Extracting zoneminder to a new library (#16527)
* Migrating out the zoneminder platform (and camera.zoneminder) to a new library

* Clean up the global variable ZM usage

* Modify camera.zoneminder to use the new Monitor class implementation

* Refactor camera.zoneminder after latest refactor in zm-py

* Implementing changes to switch.zoneminder to use zm-py native methods

* Complete migrating over sensor.zoneminder to the zm-py library

* Tweaking ZoneMinder components from code review

* Linting fixes for the zoneminder components

* Directly assign value when turning on/off in switch.zoneminder
2018-09-15 08:44:48 +02:00
Nate Clark
8ce2d701c2 fix bug where momentary switch with activation low does not reset (#16603) 2018-09-14 13:31:41 -06:00
christopheBfr
9c1a539f90 Adding support for RTDSContactSensor and RTDSMotionSensor in Tahoma … (RTS Alarms sensors and contacts for Somfy Protexiom alarms) (#16609)
* Adding support for RTDSContactSensor and RTDSContactSensor in Tahoma component

* Removing extra blank lines
2018-09-14 13:31:08 -06:00
Alexei Chetroi
0d0bda9658 Switch components.sensor.zha to await syntax. (#16619) 2018-09-14 13:29:10 -06:00
Pascal Vizeli
7705666061 Rewrite bluetooth le (#16592)
* Rewrite bluetooth le

* Update requirements_all.txt

* Update gen_requirements_all.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py

* Update bluetooth_le_tracker.py
2018-09-14 13:49:20 +02:00
Anders Melchiorsen
e82e75baf3 Improve precision of timer ticks (#16598) 2018-09-14 12:28:09 +02:00
Fabian Affolter
481f6e09fa Upgrade python-twitch-client to 0.6.0 (#16602) 2018-09-14 12:08:33 +02:00
Paulus Schoutsen
72e746d240 MQTT config entry (#16594)
* MQTT config entry

* Solely rely on config entry

* Improve wawrning

* Lint

* Lint2
2018-09-14 11:57:31 +02:00
Paulus Schoutsen
05c0717167 Add websocket list APIs for the registries (#16597)
* Add websocket list APIs for the registries

* Remove identifiers

* Fix tests

* Use ordered dict
2018-09-14 11:57:18 +02:00
Ville Skyttä
67b5b5bc85 Add myself to CODEOWNERS for upcloud (#16599) 2018-09-14 08:42:23 +02:00
Harvtronix
d076251b18 Changing z-wave brightness calculation to respect 0x01 and 0x02 byte values (#16420)
* Changing z-wave brightness calculation to respect 0x01 and 0x02 byte
values

* adding additional line breaks to satisfy houndci

* - Update comment style for linter
- Add additional unit test to increase code coverage

* Update zwave.py
2018-09-13 10:38:07 -04:00
Ville Skyttä
e59ba28fe6 Add Huawei LTE router platform, device tracker, and sensor (#16498)
* Add Huawei LTE router platform, device tracker, and sensor

* Add myself to CODEOWNERS for huawei_lte
2018-09-13 10:01:28 +02:00
Sergiy Maysak
f63dba5521 Multiple tag managers for Wireless Sensor Tags. (#16353)
* Added support for multiple tag managers. Fixed typo for signal strength.

* Corrected broken merge.

* Fixed flake8/pylint error.

* Improved docstring.
2018-09-13 09:48:17 +02:00
William Scanlon
f43d9ba680 Support for the Quirky Nimbus (#16520)
* Support for quriky nimbu

* Fixed lint

* fixed some typos
2018-09-13 09:35:40 +02:00
Daniel Høyer Iversen
aec134c47a xiaomi lib 0.10.0 (#16591) 2018-09-13 09:26:46 +02:00
Daniel Høyer Iversen
901dd9acca Update tibber lib version (#16590) 2018-09-13 09:23:33 +02:00
Alexei Chetroi
7a52bbdf24 Allow only_cache parameter in zha.safe_read() (#16553)
* Allow only_cache parameter in zha.safe_read()

* Use cache_only for binary_sensor.zha initial update.

* Use cache_only for fan.zha initial update.

* Use cache_only for sensor.zha initial update.

* Use cache_only for switch.zha initial update.

* Use cache_only for light.zha initial update.

* Refactor cached only read in zha platform.
2018-09-13 09:22:50 +02:00
Alexei Chetroi
f2203e52ef Add configure_reporting() method to zha component (#16487)
* Add zha.configure_reporting() method.

Binds a cluster and configures reporting for the specified attribute.

* git add homeassistant/components/binary_sensor/zha.py

* Refactor sensor.zha to use new 'configure_reporting() method.

* Zha configure reporting - switch (#1)

* use configure_reporting for zha switch

* lint fixes

* Rename variables/classes to properly reflect the content
2018-09-13 09:11:47 +02:00
Jason Hu
1586d3000c Fix broken bluetooth tracker (#16589) 2018-09-13 07:52:31 +02:00
Daniel Perna
d0aeb90c22 Update pyhomematic to 0.1.48 (#16588) 2018-09-13 01:04:15 +02:00
Daniel Høyer Iversen
cb542a90eb Switchmate (#16395)
* Switchmate

* switchmate

* swithcmate

* switchmate

* switchmate

* switchmate

* Make switchmate a bit more robust

* style

* style

* style

* Use external lib for switchmate

* add_entities

* Update requirements_all.txt

* unnecessary string format
2018-09-12 21:10:04 +02:00
Paulus Schoutsen
3824582e68 Add config entry to iOS (#16580)
* Add config entry to iOS

* Add translation
2018-09-12 20:17:52 +02:00
Nate Clark
2682d26f79 Konnected component feature updates (#16479)
* add  config option to invert state of binary_sensor

* Add API endpoint to get state of a Konnected switch
2018-09-12 13:54:38 +02:00
Jason Hu
308b7fb385 Add retry limit for chromecast connection (#16471) 2018-09-12 13:42:54 +02:00
Paulus Schoutsen
a88cda44d9 Bumped version to 0.78.0b2 2018-09-12 13:30:14 +02:00
Zoé Bőle
08100a485a Increasing python-websockets' version number (#16578)
* increasing python-websockets' version number so now it works with python 3.7

* required version for websockets increased to work with Python 3.7

* script/gen_requirements_all.py is done
2018-09-12 13:30:04 +02:00
Jason Hu
4c1b816bb8 Track refresh token last usage information (#16408)
* Extend refresh_token to support last_used_at and last_used_by

* Address code review comment

* Remove unused code

* Add it to websocket response

* Fix typing
2018-09-12 13:30:04 +02:00
Jason Hu
1983361373 Return if refresh token is current used one in WS API (#16575) 2018-09-12 13:29:38 +02:00
Marcel Hoppe
4efe86327d Hangouts help "page" and little bugfix (#16464)
* add 'default_conversations' parameter

* remove the empty message segment at the end of every message

* add 'HangoutsHelp' intent

* add hangouts/intents.py
2018-09-12 13:27:21 +02:00
Jason Hu
ff78a5b04b Track refresh token last usage information (#16408)
* Extend refresh_token to support last_used_at and last_used_by

* Address code review comment

* Remove unused code

* Add it to websocket response

* Fix typing
2018-09-12 13:24:16 +02:00
Paulus Schoutsen
68c21530ca Update frontend to 20180912.0 2018-09-12 13:24:02 +02:00
Paulus Schoutsen
453cbb7c60 Update frontend to 20180912.0 2018-09-12 13:23:50 +02:00
Zoé Bőle
77026a2242 Increasing python-websockets' version number (#16578)
* increasing python-websockets' version number so now it works with python 3.7

* required version for websockets increased to work with Python 3.7

* script/gen_requirements_all.py is done
2018-09-12 12:44:14 +02:00
Alexei Chetroi
501f2b0a93 Update fan.zha platform. (#16551)
* Update fan.zha platform.

switch from asyncio to async def()
catch DeliveryError exceptions
keep previous state, if reading 'fan_mode' attribute fails

* fan.zha: Use None for unknown state.

if we fail to read 'fan_mode' attribute, use None for state
2018-09-12 12:43:06 +02:00
Alexei Chetroi
117ea9e553 Refactor zha/async_device_initialized(). (#16485)
Leverage endpoint.model and endpoint.manufacturer properties
2018-09-12 11:39:23 +02:00
Ville Skyttä
beed82ab12 Upgrade pytest to 3.8.0 and pytest-timeout to 1.3.2 (#16489) 2018-09-12 11:33:26 +02:00
Daniel Perna
601f2df5a7 Notifications for Android TV: Add fontsize and sending images (#16565)
* Add fontsize and image functionality

* woof
2018-09-12 11:22:21 +02:00
Jason Hu
34d369ba26 Return if refresh token is current used one in WS API (#16575) 2018-09-12 09:49:44 +02:00
Daniel Høyer Iversen
e2465da7c2 yr: use async syntax (#16563) 2018-09-12 04:52:01 +02:00
William Scanlon
6bd120ff1d Bump pyeconet (#16571)
* Updated pyeconet to the newest version to deal with econet API change
2018-09-11 21:45:51 -04:00
Aaron Bach
188f5de5ae Fixes an OpenUV bug with the scan interval (#16570) 2018-09-11 17:13:16 -06:00
Paulus Schoutsen
06af76404f Fix invalid state (#16558)
* Fix invalid state

* Make slightly more efficient in unsubscribing

* Use uuid4"
2018-09-11 21:40:35 +02:00
Paulus Schoutsen
629c4a0bf5 Update frontend to 20180911.0 2018-09-11 21:37:38 +02:00
Paulus Schoutsen
3f06b4eb9b Update translations 2018-09-11 21:36:54 +02:00
Paulus Schoutsen
0db13a99aa Add websocket commands for refresh tokens (#16559)
* Add websocket commands for refresh tokens

* Comment
2018-09-11 09:08:03 -07:00
Paulus Schoutsen
4e3faf6108 Fix typo (#16556) 2018-09-11 12:55:05 +02:00
Jason Hu
9583947012 Long-lived access token (#16453)
* Allow create refresh_token with specific access_token_expiration

* Add token_type, client_name and client_icon

* Add unit test

* Add websocket API to create long-lived access token

* Allow URL use as client_id for long-lived access token

* Remove mutate_refresh_token method

* Use client name as id for long_lived_access_token type refresh token

* Minor change

* Do not allow duplicate client name

* Update docstring

* Remove unnecessary `list`
2018-09-11 12:05:15 +02:00
Jerad Meisner
50fb59477a Store notifications in component. Add ws endpoint for fetching. (#16503)
* Store notifications in component. Add ws endpoint for fetching.

* Comments
2018-09-11 11:39:30 +02:00
Diogo Gomes
20f6cb7cc7 Replace api_password in Camera.Push (#16339)
* Use access_token and user provided token instead of api_password

* address comments by @awarecan

* new tests

* add extra checks and test

* lint

* add comment
2018-09-11 11:30:20 +02:00
Zellux Wang
6b08e6e769 Fix arlo intilization when no base station available (#16529)
* Fix arlo intilization when no base station

* Fix pylint for empty camera check

* Fix typo

* Minor change to trigger CI again
2018-09-11 11:25:38 +02:00
Ville Skyttä
ee696643cd Isort preparations (#16555)
* Don't treat typing as an "in-between" module for import order

That was a < 3.5 era thing.

* Tighten scope of some pylint unused-import disables

To avoid isort moving a top level one around, undesirably broadening its
scope.
2018-09-11 11:21:48 +02:00
cgtobi
a059cc860a Update PyRMVtransport version (#16547)
* Update PyRMVtransport version

* Update requirements.
2018-09-11 05:55:02 +02:00
Paulus Schoutsen
cfe5db4350 Fail fetch auth providers if onboarding required (#16454) 2018-09-10 23:51:40 +02:00
Tom Harris
dcd7b9a529 Fix insteon Hub v1 support (#16472)
* Fix support for Hub version 1 (i.e. pre-2014 Hub model 2242)

* Bump insteonplm to 0.14.1

* Code review changes

* Clean up and better document set_default_port

* Simplify set_default_port based on code review

* Remove Callable type import

* Simplify port setup
2018-09-10 16:54:17 +02:00
mrosseel
f858938ada Make the Qnap sensor more resilient if server is not reachable (#16445)
* add CONF_ALLOW_UNREACHABLE option, retry connection if allowed

* fix linting errors

* Removing the config option, just using PlatformNotReady
2018-09-10 16:19:17 +02:00
vikramgorla
e96635b5c1 bugfix - incorrect camera type and missing sensors when multiple netatmo cameras (#16490)
fixed get_camera_type as it was originally not consuming any input, was looping with all cameras and the first camera type was retutned, modified to call cameraType using provided camera name.
2018-09-10 16:13:05 +02:00
Fabian Affolter
d2d715faa8 Upgrade wakeonlan to 1.1.6 (#16512) 2018-09-10 16:07:31 +02:00
Franck Nijhof
7a5e828f6b Updates documentation repo URL in PR template (#16537) 2018-09-10 14:28:21 +02:00
Paulus Schoutsen
99f7b7f42d Version bump to 0.79.0dev0 2018-09-10 13:46:07 +02:00
455 changed files with 10940 additions and 2946 deletions

View File

@@ -42,6 +42,7 @@ omit =
homeassistant/components/asterisk_mbox.py
homeassistant/components/*/asterisk_mbox.py
homeassistant/components/*/asterisk_cdr.py
homeassistant/components/august.py
homeassistant/components/*/august.py
@@ -92,6 +93,9 @@ omit =
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/edp_redy.py
homeassistant/components/*/edp_redy.py
homeassistant/components/egardia.py
homeassistant/components/*/egardia.py
@@ -123,6 +127,7 @@ omit =
homeassistant/components/hangouts/const.py
homeassistant/components/hangouts/hangouts_bot.py
homeassistant/components/hangouts/hangups_utils.py
homeassistant/components/hangouts/intents.py
homeassistant/components/*/hangouts.py
homeassistant/components/hdmi_cec.py
@@ -140,6 +145,9 @@ omit =
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/huawei_lte.py
homeassistant/components/*/huawei_lte.py
homeassistant/components/hydrawise.py
homeassistant/components/*/hydrawise.py
@@ -183,6 +191,9 @@ omit =
homeassistant/components/linode.py
homeassistant/components/*/linode.py
homeassistant/components/logi_circle.py
homeassistant/components/*/logi_circle.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
@@ -684,6 +695,7 @@ omit =
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linky.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
@@ -740,6 +752,7 @@ omit =
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
homeassistant/components/sensor/starlingbank.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py
@@ -813,6 +826,7 @@ omit =
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/darksky.py
homeassistant/components/weather/met.py
homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py

View File

@@ -3,7 +3,7 @@
**Related issue (if applicable):** fixes #<home-assistant issue number goes here>
**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>
**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io#<home-assistant.io PR number goes here>
## Example entry for `configuration.yaml` (if applicable):
```yaml
@@ -15,7 +15,7 @@
- [ ] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass**
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
If the code communicates with devices, web services, or third-party tools:
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).

View File

@@ -72,6 +72,7 @@ homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/jewish_calendar.py @tsvi
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
homeassistant/components/sensor/pollen.py @bachya
@@ -97,6 +98,8 @@ homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/hive.py @Rendili @KJonline
homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/huawei_lte.py @scop
homeassistant/components/*/huawei_lte.py @scop
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/konnected.py @heythisisnate
@@ -117,9 +120,13 @@ homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tradfri.py @ggravlingen
homeassistant/components/upcloud.py @scop
homeassistant/components/*/upcloud.py @scop
homeassistant/components/velux.py @Julius2342
homeassistant/components/*/velux.py @Julius2342
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
homeassistant/components/zoneminder.py @rohankapoorcom
homeassistant/components/*/zoneminder.py @rohankapoorcom
homeassistant/scripts/check_config.py @kellerza

View File

@@ -10,5 +10,5 @@ The process is straight-forward.
- Ensure tests work.
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
Still interested? Then you should take a peek at the [developer documentation](https://home-assistant.io/developers/) to get more details.
Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details.

View File

@@ -21,8 +21,8 @@ Featured integrations
|screenshot-components|
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://home-assistant.io/developers/architecture/>`__ and the `section on creating your own
components <https://home-assistant.io/developers/creating_components/>`__.
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://developers.home-assistant.io/docs/en/architecture_index.html>`__ and the `section on creating your own
components <https://developers.home-assistant.io/docs/en/creating_component_index.html>`__.
If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.

View File

@@ -19,4 +19,4 @@ Indices and tables
* :ref:`modindex`
* :ref:`search`
.. _Home Assistant developers: https://home-assistant.io/developers/
.. _Home Assistant developers: https://developers.home-assistant.io/

View File

@@ -7,7 +7,6 @@ import platform
import subprocess
import sys
import threading
from typing import List, Dict, Any # noqa pylint: disable=unused-import
@@ -20,15 +19,19 @@ from homeassistant.const import (
)
def attempt_use_uvloop() -> None:
def set_loop() -> None:
"""Attempt to use uvloop."""
import asyncio
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
pass
if sys.platform == 'win32':
asyncio.set_event_loop(asyncio.ProactorEventLoop())
else:
try:
import uvloop
except ImportError:
pass
else:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
def validate_python() -> None:
@@ -240,51 +243,39 @@ def cmdline() -> List[str]:
return [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,
args: argparse.Namespace) -> int:
async def setup_and_run_hass(config_dir: str,
args: argparse.Namespace) -> int:
"""Set up HASS and run."""
from homeassistant import bootstrap
from homeassistant import bootstrap, core
# Run a simple daemon runner process on Windows to handle restarts
if os.name == 'nt' and '--runner' not in sys.argv:
nt_args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(nt_args)
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)
hass = core.HomeAssistant()
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
} # type: Dict[str, Any]
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, verbose=args.verbose,
bootstrap.async_from_config_dict(
config, hass, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days,
log_file=args.log_file, log_no_color=args.log_no_color)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, verbose=args.verbose, skip_pip=args.skip_pip,
await bootstrap.async_from_config_file(
config_file, hass, verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days, log_file=args.log_file,
log_no_color=args.log_no_color)
if hass is None:
return -1
if args.open_ui:
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async_ import run_callback_threadsafe
def open_browser(_: Any) -> None:
"""Open the web interface in a browser."""
if hass.config.api is not None: # type: ignore
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url) # type: ignore
webbrowser.open(hass.config.api.base_url)
run_callback_threadsafe(
hass.loop,
@@ -292,7 +283,7 @@ def setup_and_run_hass(config_dir: str,
EVENT_HOMEASSISTANT_START, open_browser
)
return hass.start()
return await hass.async_run()
def try_to_restart() -> None:
@@ -347,7 +338,20 @@ def main() -> int:
monkey_patch.disable_c_asyncio()
monkey_patch.patch_weakref_tasks()
attempt_use_uvloop()
set_loop()
# Run a simple daemon runner process on Windows to handle restarts
if os.name == 'nt' and '--runner' not in sys.argv:
nt_args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(nt_args)
sys.exit(0)
except KeyboardInterrupt:
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)
args = get_arguments()
@@ -366,11 +370,12 @@ def main() -> int:
if args.pid_file:
write_pid(args.pid_file)
exit_code = setup_and_run_hass(config_dir, args)
from homeassistant.util.async_ import asyncio_run
exit_code = asyncio_run(setup_and_run_hass(config_dir, args))
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()
return exit_code
return exit_code # type: ignore # mypy cannot yet infer it
if __name__ == "__main__":

View File

@@ -309,8 +309,11 @@ class AuthManager:
@callback
def async_create_access_token(self,
refresh_token: models.RefreshToken) -> str:
refresh_token: models.RefreshToken,
remote_ip: Optional[str] = None) -> str:
"""Create a new access token."""
self._store.async_log_refresh_token_usage(refresh_token, remote_ip)
# pylint: disable=no-self-use
now = dt_util.utcnow()
return jwt.encode({

View File

@@ -195,6 +195,15 @@ class AuthStore:
return found
@callback
def async_log_refresh_token_usage(
self, refresh_token: models.RefreshToken,
remote_ip: Optional[str] = None) -> None:
"""Update refresh token last used information."""
refresh_token.last_used_at = dt_util.utcnow()
refresh_token.last_used_ip = remote_ip
self._async_schedule_save()
async def _async_load(self) -> None:
"""Load the users."""
data = await self._store.async_load()
@@ -233,12 +242,21 @@ class AuthStore:
'Ignoring refresh token %(id)s with invalid created_at '
'%(created_at)s for user_id %(user_id)s', rt_dict)
continue
token_type = rt_dict.get('token_type')
if token_type is None:
if rt_dict['client_id'] is None:
token_type = models.TOKEN_TYPE_SYSTEM
else:
token_type = models.TOKEN_TYPE_NORMAL
# old refresh_token don't have last_used_at (pre-0.78)
last_used_at_str = rt_dict.get('last_used_at')
if last_used_at_str:
last_used_at = dt_util.parse_datetime(last_used_at_str)
else:
last_used_at = None
token = models.RefreshToken(
id=rt_dict['id'],
user=users[rt_dict['user_id']],
@@ -251,7 +269,9 @@ class AuthStore:
access_token_expiration=timedelta(
seconds=rt_dict['access_token_expiration']),
token=rt_dict['token'],
jwt_key=rt_dict['jwt_key']
jwt_key=rt_dict['jwt_key'],
last_used_at=last_used_at,
last_used_ip=rt_dict.get('last_used_ip'),
)
users[rt_dict['user_id']].refresh_tokens[token.id] = token
@@ -306,6 +326,10 @@ class AuthStore:
refresh_token.access_token_expiration.total_seconds(),
'token': refresh_token.token,
'jwt_key': refresh_token.jwt_key,
'last_used_at':
refresh_token.last_used_at.isoformat()
if refresh_token.last_used_at else None,
'last_used_ip': refresh_token.last_used_ip,
}
for user in self._users.values()
for refresh_token in user.refresh_tokens.values()

View File

@@ -2,3 +2,4 @@
from datetime import timedelta
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
MFA_SESSION_EXPIRATION = timedelta(minutes=5)

View File

@@ -1,5 +1,4 @@
"""Plugable auth modules for Home Assistant."""
from datetime import timedelta
import importlib
import logging
import types
@@ -23,8 +22,6 @@ MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({
vol.Optional(CONF_ID): str,
}, extra=vol.ALLOW_EXTRA)
SESSION_EXPIRATION = timedelta(minutes=5)
DATA_REQS = 'mfa_auth_module_reqs_processed'
_LOGGER = logging.getLogger(__name__)
@@ -34,6 +31,7 @@ class MultiFactorAuthModule:
"""Multi-factor Auth Module of validation function."""
DEFAULT_TITLE = 'Unnamed auth module'
MAX_RETRY_TIME = 3
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
"""Initialize an auth module."""
@@ -84,7 +82,7 @@ class MultiFactorAuthModule:
"""Return whether user is setup."""
raise NotImplementedError
async def async_validation(
async def async_validate(
self, user_id: str, user_input: Dict[str, Any]) -> bool:
"""Return True if validation passed."""
raise NotImplementedError

View File

@@ -77,7 +77,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
return True
return False
async def async_validation(
async def async_validate(
self, user_id: str, user_input: Dict[str, Any]) -> bool:
"""Return True if validation passed."""
for data in self._data:

View File

@@ -0,0 +1,325 @@
"""HMAC-based One-time Password auth module.
Sending HOTP through notify service
"""
import logging
from collections import OrderedDict
from typing import Any, Dict, Optional, Tuple, List # noqa: F401
import attr
import voluptuous as vol
from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \
MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow
REQUIREMENTS = ['pyotp==2.2.6']
CONF_MESSAGE = 'message'
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({
vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_MESSAGE,
default='{} is your Home Assistant login code'): str
}, extra=vol.PREVENT_EXTRA)
STORAGE_VERSION = 1
STORAGE_KEY = 'auth_module.notify'
STORAGE_USERS = 'users'
STORAGE_USER_ID = 'user_id'
INPUT_FIELD_CODE = 'code'
_LOGGER = logging.getLogger(__name__)
def _generate_secret() -> str:
"""Generate a secret."""
import pyotp
return str(pyotp.random_base32())
def _generate_random() -> int:
"""Generate a 8 digit number."""
import pyotp
return int(pyotp.random_base32(length=8, chars=list('1234567890')))
def _generate_otp(secret: str, count: int) -> str:
"""Generate one time password."""
import pyotp
return str(pyotp.HOTP(secret).at(count))
def _verify_otp(secret: str, otp: str, count: int) -> bool:
"""Verify one time password."""
import pyotp
return bool(pyotp.HOTP(secret).verify(otp, count))
@attr.s(slots=True)
class NotifySetting:
"""Store notify setting for one user."""
secret = attr.ib(type=str, factory=_generate_secret) # not persistent
counter = attr.ib(type=int, factory=_generate_random) # not persistent
notify_service = attr.ib(type=Optional[str], default=None)
target = attr.ib(type=Optional[str], default=None)
_UsersDict = Dict[str, NotifySetting]
@MULTI_FACTOR_AUTH_MODULES.register('notify')
class NotifyAuthModule(MultiFactorAuthModule):
"""Auth module send hmac-based one time password by notify service."""
DEFAULT_TITLE = 'Notify One-Time Password'
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
"""Initialize the user data store."""
super().__init__(hass, config)
self._user_settings = None # type: Optional[_UsersDict]
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY)
self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, [])
self._message_template = config[CONF_MESSAGE]
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({INPUT_FIELD_CODE: str})
async def _async_load(self) -> None:
"""Load stored data."""
data = await self._user_store.async_load()
if data is None:
data = {STORAGE_USERS: {}}
self._user_settings = {
user_id: NotifySetting(**setting)
for user_id, setting in data.get(STORAGE_USERS, {}).items()
}
async def _async_save(self) -> None:
"""Save data."""
if self._user_settings is None:
return
await self._user_store.async_save({STORAGE_USERS: {
user_id: attr.asdict(
notify_setting, filter=attr.filters.exclude(
attr.fields(NotifySetting).secret,
attr.fields(NotifySetting).counter,
))
for user_id, notify_setting
in self._user_settings.items()
}})
@callback
def aync_get_available_notify_services(self) -> List[str]:
"""Return list of notify services."""
unordered_services = set()
for service in self.hass.services.async_services().get('notify', {}):
if service not in self._exclude:
unordered_services.add(service)
if self._include:
unordered_services &= set(self._include)
return sorted(unordered_services)
async def async_setup_flow(self, user_id: str) -> SetupFlow:
"""Return a data entry flow handler for setup module.
Mfa module should extend SetupFlow
"""
return NotifySetupFlow(
self, self.input_schema, user_id,
self.aync_get_available_notify_services())
async def async_setup_user(self, user_id: str, setup_data: Any) -> Any:
"""Set up auth module for user."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
self._user_settings[user_id] = NotifySetting(
notify_service=setup_data.get('notify_service'),
target=setup_data.get('target'),
)
await self._async_save()
async def async_depose_user(self, user_id: str) -> None:
"""Depose auth module for user."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
if self._user_settings.pop(user_id, None):
await self._async_save()
async def async_is_user_setup(self, user_id: str) -> bool:
"""Return whether user is setup."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
return user_id in self._user_settings
async def async_validate(
self, user_id: str, user_input: Dict[str, Any]) -> bool:
"""Return True if validation passed."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
notify_setting = self._user_settings.get(user_id, None)
if notify_setting is None:
return False
# user_input has been validate in caller
return await self.hass.async_add_executor_job(
_verify_otp, notify_setting.secret,
user_input.get(INPUT_FIELD_CODE, ''),
notify_setting.counter)
async def async_initialize_login_mfa_step(self, user_id: str) -> None:
"""Generate code and notify user."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
notify_setting = self._user_settings.get(user_id, None)
if notify_setting is None:
raise ValueError('Cannot find user_id')
def generate_secret_and_one_time_password() -> str:
"""Generate and send one time password."""
assert notify_setting
# secret and counter are not persistent
notify_setting.secret = _generate_secret()
notify_setting.counter = _generate_random()
return _generate_otp(
notify_setting.secret, notify_setting.counter)
code = await self.hass.async_add_executor_job(
generate_secret_and_one_time_password)
await self.async_notify_user(user_id, code)
async def async_notify_user(self, user_id: str, code: str) -> None:
"""Send code by user's notify service."""
if self._user_settings is None:
await self._async_load()
assert self._user_settings is not None
notify_setting = self._user_settings.get(user_id, None)
if notify_setting is None:
_LOGGER.error('Cannot find user %s', user_id)
return
await self.async_notify( # type: ignore
code, notify_setting.notify_service, notify_setting.target)
async def async_notify(self, code: str, notify_service: str,
target: Optional[str] = None) -> None:
"""Send code by notify service."""
data = {'message': self._message_template.format(code)}
if target:
data['target'] = [target]
await self.hass.services.async_call('notify', notify_service, data)
class NotifySetupFlow(SetupFlow):
"""Handler for the setup flow."""
def __init__(self, auth_module: NotifyAuthModule,
setup_schema: vol.Schema,
user_id: str,
available_notify_services: List[str]) -> None:
"""Initialize the setup flow."""
super().__init__(auth_module, setup_schema, user_id)
# to fix typing complaint
self._auth_module = auth_module # type: NotifyAuthModule
self._available_notify_services = available_notify_services
self._secret = None # type: Optional[str]
self._count = None # type: Optional[int]
self._notify_service = None # type: Optional[str]
self._target = None # type: Optional[str]
async def async_step_init(
self, user_input: Optional[Dict[str, str]] = None) \
-> Dict[str, Any]:
"""Let user select available notify services."""
errors = {} # type: Dict[str, str]
hass = self._auth_module.hass
if user_input:
self._notify_service = user_input['notify_service']
self._target = user_input.get('target')
self._secret = await hass.async_add_executor_job(_generate_secret)
self._count = await hass.async_add_executor_job(_generate_random)
return await self.async_step_setup()
if not self._available_notify_services:
return self.async_abort(reason='no_available_service')
schema = OrderedDict() # type: Dict[str, Any]
schema['notify_service'] = vol.In(self._available_notify_services)
schema['target'] = vol.Optional(str)
return self.async_show_form(
step_id='init',
data_schema=vol.Schema(schema),
errors=errors
)
async def async_step_setup(
self, user_input: Optional[Dict[str, str]] = None) \
-> Dict[str, Any]:
"""Verify user can recevie one-time password."""
errors = {} # type: Dict[str, str]
hass = self._auth_module.hass
if user_input:
verified = await hass.async_add_executor_job(
_verify_otp, self._secret, user_input['code'], self._count)
if verified:
await self._auth_module.async_setup_user(
self._user_id, {
'notify_service': self._notify_service,
'target': self._target,
})
return self.async_create_entry(
title=self._auth_module.name,
data={}
)
errors['base'] = 'invalid_code'
# generate code every time, no retry logic
assert self._secret and self._count
code = await hass.async_add_executor_job(
_generate_otp, self._secret, self._count)
assert self._notify_service
await self._auth_module.async_notify(
code, self._notify_service, self._target)
return self.async_show_form(
step_id='setup',
data_schema=self._setup_schema,
description_placeholders={'notify_service': self._notify_service},
errors=errors,
)

View File

@@ -60,6 +60,7 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Auth module validate time-based one time password."""
DEFAULT_TITLE = 'Time-based One Time Password'
MAX_RETRY_TIME = 5
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
"""Initialize the user data store."""
@@ -130,7 +131,7 @@ class TotpAuthModule(MultiFactorAuthModule):
return user_id in self._users # type: ignore
async def async_validation(
async def async_validate(
self, user_id: str, user_input: Dict[str, Any]) -> bool:
"""Return True if validation passed."""
if self._users is None:
@@ -149,10 +150,10 @@ class TotpAuthModule(MultiFactorAuthModule):
if ota_secret is None:
# even we cannot find user, we still do verify
# to make timing the same as if user was found.
pyotp.TOTP(DUMMY_SECRET).verify(code)
pyotp.TOTP(DUMMY_SECRET).verify(code, valid_window=1)
return False
return bool(pyotp.TOTP(ota_secret).verify(code))
return bool(pyotp.TOTP(ota_secret).verify(code, valid_window=1))
class TotpSetupFlow(SetupFlow):

View File

@@ -55,13 +55,16 @@ class RefreshToken:
jwt_key = attr.ib(type=str,
default=attr.Factory(lambda: generate_secret(64)))
last_used_at = attr.ib(type=Optional[datetime], default=None)
last_used_ip = attr.ib(type=Optional[str], default=None)
@attr.s(slots=True)
class Credentials:
"""Credentials for a user on an auth provider."""
auth_provider_type = attr.ib(type=str)
auth_provider_id = attr.ib(type=str) # type: Optional[str]
auth_provider_id = attr.ib(type=Optional[str])
# Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict)

View File

@@ -15,8 +15,8 @@ from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry
from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, User, UserMeta # noqa: F401
from ..mfa_modules import SESSION_EXPIRATION
_LOGGER = logging.getLogger(__name__)
DATA_REQS = 'auth_prov_reqs_processed'
@@ -171,6 +171,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
self._auth_manager = auth_provider.hass.auth # type: ignore
self.available_mfa_modules = {} # type: Dict[str, str]
self.created_at = dt_util.utcnow()
self.invalid_mfa_times = 0
self.user = None # type: Optional[User]
async def async_step_init(
@@ -212,6 +213,8 @@ class LoginFlow(data_entry_flow.FlowHandler):
self, user_input: Optional[Dict[str, str]] = None) \
-> Dict[str, Any]:
"""Handle the step of mfa validation."""
assert self.user
errors = {}
auth_module = self._auth_manager.get_auth_mfa_module(
@@ -221,25 +224,34 @@ class LoginFlow(data_entry_flow.FlowHandler):
# will show invalid_auth_module error
return await self.async_step_select_mfa_module(user_input={})
if user_input is None and hasattr(auth_module,
'async_initialize_login_mfa_step'):
await auth_module.async_initialize_login_mfa_step(self.user.id)
if user_input is not None:
expires = self.created_at + SESSION_EXPIRATION
expires = self.created_at + MFA_SESSION_EXPIRATION
if dt_util.utcnow() > expires:
return self.async_abort(
reason='login_expired'
)
result = await auth_module.async_validation(
self.user.id, user_input) # type: ignore
result = await auth_module.async_validate(
self.user.id, user_input)
if not result:
errors['base'] = 'invalid_code'
self.invalid_mfa_times += 1
if self.invalid_mfa_times >= auth_module.MAX_RETRY_TIME > 0:
return self.async_abort(
reason='too_many_retry'
)
if not errors:
return await self.async_finish(self.user)
description_placeholders = {
'mfa_module_name': auth_module.name,
'mfa_module_id': auth_module.id
} # type: Dict[str, str]
'mfa_module_id': auth_module.id,
} # type: Dict[str, Optional[str]]
return self.async_show_form(
step_id='mfa',

View File

@@ -5,7 +5,6 @@ import os
import sys
from time import time
from collections import OrderedDict
from typing import Any, Optional, Dict
import voluptuous as vol
@@ -19,7 +18,6 @@ from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, is_virtual_env
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.signal import async_register_signal_handling
_LOGGER = logging.getLogger(__name__)
@@ -160,7 +158,6 @@ async def async_from_config_dict(config: Dict[str, Any],
stop = time()
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
async_register_signal_handling(hass)
return hass

View File

@@ -4,71 +4,65 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.spc/
"""
import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback
from homeassistant.components.spc import (
ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway)
ATTR_DISCOVER_AREAS, DATA_API, SIGNAL_UPDATE_ALARM)
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_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
_LOGGER = logging.getLogger(__name__)
SPC_AREA_MODE_TO_STATE = {
'0': STATE_ALARM_DISARMED,
'1': STATE_ALARM_ARMED_HOME,
'3': STATE_ALARM_ARMED_AWAY,
}
def _get_alarm_state(spc_mode):
def _get_alarm_state(area):
"""Get the alarm state."""
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
from pyspcwebgw.const import AreaMode
if area.verified_alarm:
return STATE_ALARM_TRIGGERED
mode_to_state = {
AreaMode.UNSET: STATE_ALARM_DISARMED,
AreaMode.PART_SET_A: STATE_ALARM_ARMED_HOME,
AreaMode.PART_SET_B: STATE_ALARM_ARMED_NIGHT,
AreaMode.FULL_SET: STATE_ALARM_ARMED_AWAY,
}
return mode_to_state.get(area.mode)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the SPC alarm control panel platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_AREAS] is None):
return
api = hass.data[DATA_API]
devices = [SpcAlarm(api, area)
for area in discovery_info[ATTR_DISCOVER_AREAS]]
async_add_entities(devices)
async_add_entities([SpcAlarm(area=area, api=hass.data[DATA_API])
for area in discovery_info[ATTR_DISCOVER_AREAS]])
class SpcAlarm(alarm.AlarmControlPanel):
"""Representation of the SPC alarm panel."""
def __init__(self, api, area):
def __init__(self, area, api):
"""Initialize the SPC alarm panel."""
self._area_id = area['id']
self._name = area['name']
self._state = _get_alarm_state(area['mode'])
if self._state == STATE_ALARM_DISARMED:
self._changed_by = area.get('last_unset_user_name', 'unknown')
else:
self._changed_by = area.get('last_set_user_name', 'unknown')
self._area = area
self._api = api
@asyncio.coroutine
def async_added_to_hass(self):
async def async_added_to_hass(self):
"""Call for adding new entities."""
self.hass.data[DATA_REGISTRY].register_alarm_device(
self._area_id, self)
async_dispatcher_connect(self.hass,
SIGNAL_UPDATE_ALARM.format(self._area.id),
self._update_callback)
@asyncio.coroutine
def async_update_from_spc(self, state, extra):
"""Update the alarm panel with a new state."""
self._state = state
self._changed_by = extra.get('changed_by', 'unknown')
self.async_schedule_update_ha_state()
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state(True)
@property
def should_poll(self):
@@ -78,32 +72,34 @@ class SpcAlarm(alarm.AlarmControlPanel):
@property
def name(self):
"""Return the name of the device."""
return self._name
return self._area.name
@property
def changed_by(self):
"""Return the user the last change was triggered by."""
return self._changed_by
return self._area.last_changed_by
@property
def state(self):
"""Return the state of the device."""
return self._state
return _get_alarm_state(self._area)
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_UNSET)
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.UNSET)
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_PART_SET)
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_A)
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
async def async_alarm_arm_night(self, code=None):
"""Send arm home command."""
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_B)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_SET)
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.FULL_SET)

View File

@@ -1529,3 +1529,8 @@ async def async_api_reportstate(hass, config, request, context, entity):
name='StateReport',
context={'properties': properties}
)
def turned_off_response(message):
"""Return a device turned off response."""
return api_error(message[API_DIRECTIVE], error_type='BRIDGE_UNREACHABLE')

View File

@@ -6,7 +6,6 @@ https://home-assistant.io/components/apple_tv/
"""
import asyncio
import logging
from typing import Sequence, TypeVar, Union
import voluptuous as vol

View File

@@ -13,7 +13,7 @@ from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
async_dispatcher_send, dispatcher_connect)
REQUIREMENTS = ['asterisk_mbox==0.5.0']
@@ -21,8 +21,11 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'asterisk_mbox'
SIGNAL_DISCOVER_PLATFORM = "asterisk_mbox.discover_platform"
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
SIGNAL_CDR_UPDATE = 'asterisk_mbox.message_updated'
SIGNAL_CDR_REQUEST = 'asterisk_mbox.message_request'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -41,9 +44,7 @@ def setup(hass, config):
port = conf.get(CONF_PORT)
password = conf.get(CONF_PASSWORD)
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
hass.data[DOMAIN] = AsteriskData(hass, host, port, password, config)
return True
@@ -51,31 +52,71 @@ def setup(hass, config):
class AsteriskData:
"""Store Asterisk mailbox data."""
def __init__(self, hass, host, port, password):
def __init__(self, hass, host, port, password, config):
"""Init the Asterisk data object."""
from asterisk_mbox import Client as asteriskClient
self.hass = hass
self.client = asteriskClient(host, port, password, self.handle_data)
self.messages = []
self.config = config
self.messages = None
self.cdr = None
async_dispatcher_connect(
dispatcher_connect(
self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages)
dispatcher_connect(
self.hass, SIGNAL_CDR_REQUEST, self._request_cdr)
dispatcher_connect(
self.hass, SIGNAL_DISCOVER_PLATFORM, self._discover_platform)
# Only connect after signal connection to ensure we don't miss any
self.client = asteriskClient(host, port, password, self.handle_data)
@callback
def _discover_platform(self, component):
_LOGGER.debug("Adding mailbox %s", component)
self.hass.async_create_task(discovery.async_load_platform(
self.hass, "mailbox", component, {}, self.config))
@callback
def handle_data(self, command, msg):
"""Handle changes to the mailbox."""
from asterisk_mbox.commands import CMD_MESSAGE_LIST
from asterisk_mbox.commands import (CMD_MESSAGE_LIST,
CMD_MESSAGE_CDR_AVAILABLE,
CMD_MESSAGE_CDR)
if command == CMD_MESSAGE_LIST:
_LOGGER.debug("AsteriskVM sent updated message list")
_LOGGER.debug("AsteriskVM sent updated message list: Len %d",
len(msg))
old_messages = self.messages
self.messages = sorted(
msg, key=lambda item: item['info']['origtime'], reverse=True)
async_dispatcher_send(
self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
if not isinstance(old_messages, list):
async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM,
DOMAIN)
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
self.messages)
elif command == CMD_MESSAGE_CDR:
_LOGGER.debug("AsteriskVM sent updated CDR list: Len %d",
len(msg.get('entries', [])))
self.cdr = msg['entries']
async_dispatcher_send(self.hass, SIGNAL_CDR_UPDATE, self.cdr)
elif command == CMD_MESSAGE_CDR_AVAILABLE:
if not isinstance(self.cdr, list):
_LOGGER.debug("AsteriskVM adding CDR platform")
self.cdr = []
async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM,
"asterisk_cdr")
async_dispatcher_send(self.hass, SIGNAL_CDR_REQUEST)
else:
_LOGGER.debug("AsteriskVM sent unknown message '%d' len: %d",
command, len(msg))
@callback
def _request_messages(self):
"""Handle changes to the mailbox."""
_LOGGER.debug("Requesting message list")
self.client.messages()
@callback
def _request_cdr(self):
"""Handle changes to the CDR."""
_LOGGER.debug("Requesting CDR list")
self.client.get_cdr()

View File

@@ -1,8 +1,27 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "No hi ha serveis de notificaci\u00f3 disponibles."
},
"error": {
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho."
},
"step": {
"init": {
"description": "Seleccioneu un dels serveis de notificaci\u00f3:",
"title": "Configureu una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
},
"setup": {
"description": "**notify.{notify_service}** ha enviat una contrasenya d'un sol \u00fas. Introdu\u00efu-la a continuaci\u00f3:",
"title": "Verifiqueu la configuraci\u00f3"
}
},
"title": "Contrasenya d'un sol \u00fas del servei de notificacions"
},
"totp": {
"error": {
"invalid_code": "Codi no v\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
},
"step": {
"init": {

View File

@@ -1,8 +1,26 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Keine Benachrichtigungsdienste verf\u00fcgbar."
},
"error": {
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut."
},
"step": {
"init": {
"description": "Bitte w\u00e4hle einen der Benachrichtigungsdienste:",
"title": "Einmal Passwort f\u00fcr Notify einrichten"
},
"setup": {
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
"title": "\u00dcberpr\u00fcfe das Setup"
}
}
},
"totp": {
"error": {
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn Sie diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn du diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
},
"step": {
"init": {

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "No notification services available."
},
"error": {
"invalid_code": "Invalid code, please try again."
},
"step": {
"init": {
"description": "Please select one of the notification services:",
"title": "Set up one-time password delivered by notify component"
},
"setup": {
"description": "A one-time password has been sent via **notify.{notify_service}**. Please enter it below:",
"title": "Verify setup"
}
},
"title": "Notify One-Time Password"
},
"totp": {
"error": {
"invalid_code": "Invalid code, please try again. If you get this error consistently, please make sure the clock of your Home Assistant system is accurate."

View File

@@ -1,8 +1,15 @@
{
"mfa_setup": {
"notify": {
"step": {
"setup": {
"description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :"
}
}
},
"totp": {
"error": {
"invalid_code": "Code invalide. S'il vous pla\u00eet essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
"invalid_code": "Code invalide. Veuillez essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
},
"step": {
"init": {

View File

@@ -0,0 +1,35 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\u05d0\u05d9\u05df \u05e9\u05d9\u05e8\u05d5\u05ea\u05d9 notify \u05d6\u05de\u05d9\u05e0\u05d9\u05dd."
},
"error": {
"invalid_code": "\u05e7\u05d5\u05d3 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9, \u05d0\u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1."
},
"step": {
"init": {
"description": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05d0\u05d7\u05d3 \u05de\u05e9\u05e8\u05d5\u05ea\u05d9 notify",
"title": "\u05d4\u05d2\u05d3\u05e8 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea \u05d4\u05e0\u05e9\u05dc\u05d7\u05ea \u05e2\u05dc \u05d9\u05d3\u05d9 \u05e8\u05db\u05d9\u05d1 notify"
},
"setup": {
"description": "\u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea \u05e0\u05e9\u05dc\u05d7\u05d4 \u05e2\u05dc \u05d9\u05d3\u05d9 **{notify_service}**. \u05d4\u05d6\u05df \u05d0\u05d5\u05ea\u05d4 \u05dc\u05de\u05d8\u05d4:",
"title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4"
}
},
"title": "\u05dc\u05d4\u05d5\u05d3\u05d9\u05e2 \u200b\u200b\u05e2\u05dc \u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea"
},
"totp": {
"error": {
"invalid_code": "\u05e7\u05d5\u05d3 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9, \u05d0\u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1. \u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05e7\u05d1\u05dc \u05d0\u05ea \u05d4\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d4\u05d6\u05d5 \u05d1\u05d0\u05d5\u05e4\u05df \u05e2\u05e7\u05d1\u05d9, \u05d5\u05d3\u05d0 \u05e9\u05d4\u05e9\u05e2\u05d5\u05df \u05e9\u05dc \u05de\u05e2\u05e8\u05db\u05ea \u05d4 - Home Assistant \u05e9\u05dc\u05da \u05de\u05d3\u05d5\u05d9\u05e7."
},
"step": {
"init": {
"description": "\u05db\u05d3\u05d9 \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05d9\u05de\u05d5\u05ea \u05d3\u05d5 \u05e9\u05dc\u05d1\u05d9 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05e1\u05d9\u05e1\u05de\u05d0\u05d5\u05ea \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05d5\u05ea \u05de\u05d1\u05d5\u05e1\u05e1\u05d5\u05ea \u05d6\u05de\u05df, \u05e1\u05e8\u05d5\u05e7 \u05d0\u05ea \u05e7\u05d5\u05d3 QR \u05e2\u05dd \u05d9\u05d9\u05e9\u05d5\u05dd \u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05e9\u05dc\u05da. \u05d0\u05dd \u05d0\u05d9\u05df \u05dc\u05da \u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d6\u05d4, \u05d0\u05e0\u05d5 \u05de\u05de\u05dc\u05d9\u05e6\u05d9\u05dd \u05e2\u05dc [Google Authenticator] (https://support.google.com/accounts/answer/1066447) \u05d0\u05d5 [Authy] (https://authy.com/). \n\n {qr_code} \n \n \u05dc\u05d0\u05d7\u05e8 \u05e1\u05e8\u05d9\u05e7\u05ea \u05d4\u05e7\u05d5\u05d3, \u05d4\u05d6\u05df \u05d0\u05ea \u05d4\u05e7\u05d5\u05d3 \u05d1\u05df \u05e9\u05e9 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05de\u05d4\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4 \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05d0\u05de\u05ea \u05d0\u05ea \u05d4\u05d4\u05d2\u05d3\u05e8\u05d4. \u05d0\u05dd \u05d0\u05ea\u05d4 \u05e0\u05ea\u05e7\u05dc \u05d1\u05d1\u05e2\u05d9\u05d5\u05ea \u05d1\u05e1\u05e8\u05d9\u05e7\u05ea \u05e7\u05d5\u05d3 QR, \u05d1\u05e6\u05e2 \u05d4\u05d2\u05d3\u05e8\u05d4 \u05d9\u05d3\u05e0\u05d9\u05ea \u05e2\u05dd \u05e7\u05d5\u05d3 **`{code}`**.",
"title": "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05d9\u05de\u05d5\u05ea \u05d3\u05d5 \u05e9\u05dc\u05d1\u05d9 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Kode salah, coba lagi. Jika Anda mendapatkan kesalahan ini secara konsisten, pastikan jam pada sistem Home Assistant anda akurat."
},
"step": {
"init": {
"description": "Untuk mengaktifkan otentikasi dua faktor menggunakan password satu kali berbasis waktu, pindai kode QR dengan aplikasi otentikasi Anda. Jika Anda tidak memilikinya, kami menyarankan [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \n Setelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi pengaturan. Jika Anda mengalami masalah saat memindai kode QR, lakukan pengaturan manual dengan kode ** ` {code} ` **.",
"title": "Siapkan otentikasi dua faktor menggunakan TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc54c\ub9bc \uc11c\ube44\uc2a4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4."
},
"error": {
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694."
},
"step": {
"init": {
"description": "\uc54c\ub9bc \uc11c\ube44\uc2a4 \uc911 \ud558\ub098\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694:",
"title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815"
},
"setup": {
"description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574 \uc8fc\uc138\uc694:",
"title": "\uc124\uc815 \ud655\uc778"
}
},
"title": "\uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc54c\ub9bc"
},
"totp": {
"error": {
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Keen Notifikatioun's D\u00e9ngscht disponibel."
},
"error": {
"invalid_code": "Ong\u00ebltege Code, prob\u00e9iert w.e.g. nach emol."
},
"step": {
"init": {
"description": "Wielt w.e.g. een Notifikatioun's D\u00e9ngscht aus:",
"title": "Eemolegt Passwuert ariichte wat vun engem Notifikatioun's Komponente versch\u00e9ckt g\u00ebtt"
},
"setup": {
"description": "Een eemolegt Passwuert ass vun **notify.{notify_service}** gesch\u00e9ckt ginn. Gitt et w.e.g hei \u00ebnnen dr\u00ebnner an:",
"title": "Astellungen iwwerpr\u00e9iwen"
}
},
"title": "Eemolegt Passwuert Notifikatioun"
},
"totp": {
"error": {
"invalid_code": "Ong\u00ebltege Login, prob\u00e9iert w.e.g. nach emol. Falls d\u00ebse Feeler Message \u00ebmmer er\u00ebm optr\u00ebtt dann iwwerpr\u00e9ift op d'Z\u00e4it vum Home Assistant System richteg ass."

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Dersom du heile tida f\u00e5r denne feilen, m\u00e5 du s\u00f8rge for at klokka p\u00e5 Home Assistant-systemet ditt er n\u00f8yaktig."
},
"step": {
"init": {
"description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte eingangspassord, skann QR-koden med autentiseringsappen din. Dersom du ikkje har ein, vil vi r\u00e5de deg til \u00e5 bruke anten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har skanna koda, skriv du inn den sekssifra koda fr\u00e5 appen din for \u00e5 stadfeste oppsettet. Dersom du har problemer med \u00e5 skanne QR-koda, gjer du eit manuelt oppsett med kode ** ` {code} ` **.",
"title": "Konfigurer to-faktor-autentisering ved hjelp av TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Ingen varslingstjenester er tilgjengelig."
},
"error": {
"invalid_code": "Ugyldig kode, vennligst pr\u00f8v igjen."
},
"step": {
"init": {
"description": "Vennligst velg en av varslingstjenestene:",
"title": "Sett opp engangspassord levert av varsel komponent"
},
"setup": {
"description": "Et engangspassord har blitt sendt via **notify.{notify_service}**. Vennligst skriv det inn nedenfor:",
"title": "Bekreft oppsettet"
}
},
"title": "Varsle engangspassord"
},
"totp": {
"error": {
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Hvis du f\u00e5r denne feilen konsekvent, m\u00e5 du s\u00f8rge for at klokken p\u00e5 Home Assistant systemet er riktig."

View File

@@ -1,5 +1,23 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Brak dost\u0119pnych us\u0142ug powiadamiania."
},
"error": {
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie."
},
"step": {
"init": {
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:",
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
},
"setup": {
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez ** powiadom. {notify_service} **. Wpisz je poni\u017cej:",
"title": "Sprawd\u017a konfiguracj\u0119"
}
}
},
"totp": {
"error": {
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie. Je\u015bli b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, upewnij si\u0119, \u017ce czas zegara systemu Home Assistant jest prawid\u0142owy."

View File

@@ -0,0 +1,15 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente. Se voc\u00ea obtiver este erro de forma consistente, certifique-se de que o rel\u00f3gio do sistema Home Assistant esteja correto."
},
"step": {
"init": {
"title": "Configure a autentica\u00e7\u00e3o de dois fatores usando o TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0441\u043b\u0443\u0436\u0431 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439."
},
"error": {
"invalid_code": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443."
},
"step": {
"init": {
"description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0441\u043b\u0443\u0436\u0431 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439:",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439"
},
"setup": {
"description": "\u041e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0447\u0435\u0440\u0435\u0437 **notify.{notify_service}**. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0435\u0433\u043e \u043d\u0438\u0436\u0435:",
"title": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443"
}
},
"title": "\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439"
},
"totp": {
"error": {
"invalid_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u0415\u0441\u043b\u0438 \u0432\u044b \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0447\u0430\u0441\u044b \u0432 \u0432\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 Home Assistant \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f."

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Ni na voljo storitev obve\u0161\u010danja."
},
"error": {
"invalid_code": "Neveljavna koda, poskusite znova."
},
"step": {
"init": {
"description": "Prosimo, izberite eno od storitev obve\u0161\u010danja:",
"title": "Nastavite enkratno geslo, ki ga dostavite z obvestilno komponento"
},
"setup": {
"description": "Enkratno geslo je poslal **notify.{notify_service} **. Vnesite ga spodaj:",
"title": "Preverite nastavitev"
}
},
"title": "Obvesti Enkratno Geslo"
},
"totp": {
"error": {
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."

View File

@@ -1,5 +1,19 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Inga tillg\u00e4ngliga meddelande tj\u00e4nster."
},
"error": {
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
},
"step": {
"setup": {
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:",
"title": "Verifiera installationen"
}
}
},
"totp": {
"error": {
"invalid_code": "Ogiltig kod, f\u00f6rs\u00f6k igen. Om du flera g\u00e5nger i rad f\u00e5r detta fel, se till att klockan i din Home Assistant \u00e4r korrekt inst\u00e4lld."

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\u6ca1\u6709\u53ef\u7528\u7684\u901a\u77e5\u670d\u52a1\u3002"
},
"error": {
"invalid_code": "\u4ee3\u7801\u65e0\u6548\uff0c\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002"
},
"step": {
"init": {
"description": "\u8bf7\u4ece\u4e0b\u9762\u9009\u62e9\u4e00\u4e2a\u901a\u77e5\u670d\u52a1\uff1a",
"title": "\u8bbe\u7f6e\u7531\u901a\u77e5\u7ec4\u4ef6\u4f20\u9012\u7684\u4e00\u6b21\u6027\u5bc6\u7801"
},
"setup": {
"description": "\u4e00\u6b21\u6027\u5bc6\u7801\u5df2\u7531 **notify.{notify_service}** \u53d1\u9001\u3002\u8bf7\u5728\u4e0b\u9762\u8f93\u5165\uff1a",
"title": "\u9a8c\u8bc1\u8bbe\u7f6e"
}
},
"title": "\u4e00\u6b21\u6027\u5bc6\u7801\u901a\u77e5"
},
"totp": {
"error": {
"invalid_code": "\u53e3\u4ee4\u65e0\u6548\uff0c\u8bf7\u91cd\u65b0\u8f93\u5165\u3002\u5982\u679c\u9519\u8bef\u53cd\u590d\u51fa\u73b0\uff0c\u8bf7\u786e\u4fdd Home Assistant \u7cfb\u7edf\u7684\u65f6\u95f4\u51c6\u786e\u65e0\u8bef\u3002"

View File

@@ -1,5 +1,24 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\u6c92\u6709\u53ef\u7528\u7684\u901a\u77e5\u670d\u52d9\u3002"
},
"error": {
"invalid_code": "\u9a57\u8b49\u78bc\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002"
},
"step": {
"init": {
"description": "\u8acb\u9078\u64c7\u4e00\u9805\u901a\u77e5\u670d\u52d9\uff1a",
"title": "\u8a2d\u5b9a\u4e00\u6b21\u6027\u5bc6\u78bc\u50b3\u9001\u7d44\u4ef6"
},
"setup": {
"description": "\u4e00\u6b21\u6027\u5bc6\u78bc\u5df2\u900f\u904e **notify.{notify_service}** \u50b3\u9001\u3002\u8acb\u65bc\u4e0b\u65b9\u8f38\u5165\uff1a",
"title": "\u9a57\u8b49\u8a2d\u5b9a"
}
},
"title": "\u901a\u77e5\u4e00\u6b21\u6027\u5bc6\u78bc"
},
"totp": {
"error": {
"invalid_code": "\u9a57\u8b49\u78bc\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002\u5047\u5982\u932f\u8aa4\u6301\u7e8c\u767c\u751f\uff0c\u8acb\u5148\u78ba\u5b9a\u60a8\u7684 Home Assistant \u7cfb\u7d71\u4e0a\u7684\u6642\u9593\u8a2d\u5b9a\u6b63\u78ba\u5f8c\uff0c\u518d\u8a66\u4e00\u6b21\u3002"

View File

@@ -129,6 +129,7 @@ import voluptuous as vol
from homeassistant.auth.models import User, Credentials, \
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
from homeassistant.components import websocket_api
from homeassistant.components.http import KEY_REAL_IP
from homeassistant.components.http.ban import log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.http.view import HomeAssistantView
@@ -236,10 +237,12 @@ class TokenView(HomeAssistantView):
return await self._async_handle_revoke_token(hass, data)
if grant_type == 'authorization_code':
return await self._async_handle_auth_code(hass, data)
return await self._async_handle_auth_code(
hass, data, str(request[KEY_REAL_IP]))
if grant_type == 'refresh_token':
return await self._async_handle_refresh_token(hass, data)
return await self._async_handle_refresh_token(
hass, data, str(request[KEY_REAL_IP]))
return self.json({
'error': 'unsupported_grant_type',
@@ -264,7 +267,7 @@ class TokenView(HomeAssistantView):
await hass.auth.async_remove_refresh_token(refresh_token)
return web.Response(status=200)
async def _async_handle_auth_code(self, hass, data):
async def _async_handle_auth_code(self, hass, data, remote_addr):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is None or not indieauth.verify_client_id(client_id):
@@ -300,7 +303,8 @@ class TokenView(HomeAssistantView):
refresh_token = await hass.auth.async_create_refresh_token(user,
client_id)
access_token = hass.auth.async_create_access_token(refresh_token)
access_token = hass.auth.async_create_access_token(
refresh_token, remote_addr)
return self.json({
'access_token': access_token,
@@ -310,7 +314,7 @@ class TokenView(HomeAssistantView):
int(refresh_token.access_token_expiration.total_seconds()),
})
async def _async_handle_refresh_token(self, hass, data):
async def _async_handle_refresh_token(self, hass, data, remote_addr):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is not None and not indieauth.verify_client_id(client_id):
@@ -338,7 +342,8 @@ class TokenView(HomeAssistantView):
'error': 'invalid_request',
}, status_code=400)
access_token = hass.auth.async_create_access_token(refresh_token)
access_token = hass.auth.async_create_access_token(
refresh_token, remote_addr)
return self.json({
'access_token': access_token,
@@ -475,6 +480,7 @@ def websocket_create_long_lived_access_token(
def websocket_refresh_tokens(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
"""Return metadata of users refresh tokens."""
current_id = connection.request.get('refresh_token_id')
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{
'id': refresh.id,
'client_id': refresh.client_id,
@@ -482,6 +488,9 @@ def websocket_refresh_tokens(
'client_icon': refresh.client_icon,
'type': refresh.token_type,
'created_at': refresh.created_at,
'is_current': refresh.id == current_id,
'last_used_at': refresh.last_used_at,
'last_used_ip': refresh.last_used_ip,
} for refresh in connection.user.refresh_tokens.values()]))

View File

@@ -226,8 +226,9 @@ class LoginFlowResourceView(HomeAssistantView):
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
# @log_invalid_auth does not work here since it returns HTTP 200
# need manually log failed login attempts
if result['errors'] is not None and \
result['errors'].get('base') == 'invalid_auth':
if (result.get('errors') is not None and
result['errors'].get('base') in ['invalid_auth',
'invalid_code']):
await process_wrong_login(request)
return self.json(_prepare_result_json(result))

View File

@@ -11,6 +11,25 @@
"error": {
"invalid_code": "Invalid code, please try again. If you get this error consistently, please make sure the clock of your Home Assistant system is accurate."
}
},
"notify": {
"title": "Notify One-Time Password",
"step": {
"init": {
"title": "Set up one-time password delivered by notify component",
"description": "Please select one of notify service:"
},
"setup": {
"title": "Verify setup",
"description": "A one-time password have sent by **notify.{notify_service}**. Please input it in below:"
}
},
"abort": {
"no_available_service": "No available notify services."
},
"error": {
"invalid_code": "Invalid code, please try again."
}
}
}
}

View File

@@ -127,6 +127,7 @@ class DeconzBinarySensor(BinarySensorDevice):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},
@@ -134,4 +135,5 @@ class DeconzBinarySensor(BinarySensorDevice):
'model': self._sensor.modelid,
'name': self._sensor.name,
'sw_version': self._sensor.swversion,
'via_hub': (DECONZ_DOMAIN, bridgeid),
}

View File

@@ -27,17 +27,20 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the HomematicIP Cloud binary sensor from a config entry."""
from homematicip.aio.device import (
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector)
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector,
AsyncWaterSensor, AsyncRotaryHandleSensor)
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
for device in home.devices:
if isinstance(device, AsyncShutterContact):
if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, AsyncMotionDetectorIndoor):
devices.append(HomematicipMotionDetector(home, device))
elif isinstance(device, AsyncSmokeDetector):
devices.append(HomematicipSmokeDetector(home, device))
elif isinstance(device, AsyncWaterSensor):
devices.append(HomematicipWaterDetector(home, device))
if devices:
async_add_entities(devices)
@@ -91,3 +94,17 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
def is_on(self):
"""Return true if smoke is detected."""
return self._device.smokeDetectorAlarmType != STATE_SMOKE_OFF
class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice):
"""Representation of a HomematicIP Cloud water detector."""
@property
def device_class(self):
"""Return the class of this sensor."""
return 'moisture'
@property
def is_on(self):
"""Return true if moisture or waterlevel is detected."""
return self._device.moistureDetected or self._device.waterlevelDetected

View File

@@ -147,6 +147,11 @@ class NestActivityZoneSensor(NestBinarySensor):
self.zone = zone
self._name = "{} {} activity".format(self._name, self.zone.name)
@property
def unique_id(self):
"""Return unique id based on camera serial and zone id."""
return "{}-{}".format(self.device.serial, self.zone.zone_id)
@property
def device_class(self):
"""Return the device class of the binary sensor."""

View File

@@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
_LOGGER = logging.getLogger(__name__)
@@ -66,13 +67,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:
_LOGGER.error("Unable to fetch REST data from %s", resource)
return False
raise PlatformNotReady
# No need to update the sensor now because it will determine its state
# based in the rest resource that has just been retrieved.
add_entities([RestBinarySensor(
hass, rest, name, device_class, value_template)], True)
hass, rest, name, device_class, value_template)])
class RestBinarySensor(BinarySensorDevice):

View File

@@ -44,14 +44,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
ring = hass.data[DATA_RING]
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
for device in ring.doorbells:
for device in ring.doorbells: # ring.doorbells is doing I/O
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
sensors.append(RingBinarySensor(hass,
device,
sensor_type))
for device in ring.stickup_cams:
for device in ring.stickup_cams: # ring.stickup_cams is doing I/O
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]:
sensors.append(RingBinarySensor(hass,
device,

View File

@@ -4,87 +4,66 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.spc/
"""
import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.spc import ATTR_DISCOVER_DEVICES, DATA_REGISTRY
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback
from homeassistant.components.spc import (
ATTR_DISCOVER_DEVICES, SIGNAL_UPDATE_SENSOR)
_LOGGER = logging.getLogger(__name__)
SPC_TYPE_TO_DEVICE_CLASS = {
'0': 'motion',
'1': 'opening',
'3': 'smoke',
}
SPC_INPUT_TO_SENSOR_STATE = {
'0': STATE_OFF,
'1': STATE_ON,
}
def _get_device_class(zone_type):
from pyspcwebgw.const import ZoneType
return {
ZoneType.ALARM: 'motion',
ZoneType.ENTRY_EXIT: 'opening',
ZoneType.FIRE: 'smoke',
}.get(zone_type)
def _get_device_class(spc_type):
"""Get the device class."""
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
def _get_sensor_state(spc_input):
"""Get the sensor state."""
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
def _create_sensor(hass, zone):
"""Create a SPC sensor."""
return SpcBinarySensor(
zone_id=zone['id'], name=zone['zone_name'],
state=_get_sensor_state(zone['input']),
device_class=_get_device_class(zone['type']),
spc_registry=hass.data[DATA_REGISTRY])
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the SPC binary sensor."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return
async_add_entities(
_create_sensor(hass, zone)
for zone in discovery_info[ATTR_DISCOVER_DEVICES]
if _get_device_class(zone['type']))
async_add_entities(SpcBinarySensor(zone)
for zone in discovery_info[ATTR_DISCOVER_DEVICES]
if _get_device_class(zone.type))
class SpcBinarySensor(BinarySensorDevice):
"""Representation of a sensor based on a SPC zone."""
def __init__(self, zone_id, name, state, device_class, spc_registry):
def __init__(self, zone):
"""Initialize the sensor device."""
self._zone_id = zone_id
self._name = name
self._state = state
self._device_class = device_class
self._zone = zone
spc_registry.register_sensor_device(zone_id, self)
async def async_added_to_hass(self):
"""Call for adding new entities."""
async_dispatcher_connect(self.hass,
SIGNAL_UPDATE_SENSOR.format(self._zone.id),
self._update_callback)
@asyncio.coroutine
def async_update_from_spc(self, state, extra):
"""Update the state of the device."""
self._state = state
yield from self.async_update_ha_state()
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state(True)
@property
def name(self):
"""Return the name of the device."""
return self._name
return self._zone.name
@property
def is_on(self):
"""Whether the device is switched on."""
return self._state == STATE_ON
from pyspcwebgw.const import ZoneInput
return self._zone.input == ZoneInput.OPEN
@property
def hidden(self) -> bool:
@@ -100,4 +79,4 @@ class SpcBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the device class."""
return self._device_class
return _get_device_class(self._zone.type)

View File

@@ -14,9 +14,6 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.wirelesstag import (
DOMAIN as WIRELESSTAG_DOMAIN,
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
WIRELESSTAG_TYPE_ALSPRO,
WIRELESSTAG_TYPE_WEMO_DEVICE,
SIGNAL_BINARY_EVENT_UPDATE,
WirelessTagBaseSensor)
from homeassistant.const import (
@@ -30,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
# On means in range, Off means out of range
SENSOR_PRESENCE = 'presence'
# On means motion detected, Off means cear
# On means motion detected, Off means clear
SENSOR_MOTION = 'motion'
# On means open, Off means closed
@@ -55,49 +52,21 @@ SENSOR_LIGHT = 'light'
SENSOR_MOISTURE = 'moisture'
# On means tag battery is low, Off means normal
SENSOR_BATTERY = 'low_battery'
SENSOR_BATTERY = 'battery'
# Sensor types: Name, device_class, push notification type representing 'on',
# attr to check
SENSOR_TYPES = {
SENSOR_PRESENCE: ['Presence', 'presence', 'is_in_range', {
"on": "oor",
"off": "back_in_range"
}, 2],
SENSOR_MOTION: ['Motion', 'motion', 'is_moved', {
"on": "motion_detected",
}, 5],
SENSOR_DOOR: ['Door', 'door', 'is_door_open', {
"on": "door_opened",
"off": "door_closed"
}, 5],
SENSOR_COLD: ['Cold', 'cold', 'is_cold', {
"on": "temp_toolow",
"off": "temp_normal"
}, 4],
SENSOR_HEAT: ['Heat', 'heat', 'is_heat', {
"on": "temp_toohigh",
"off": "temp_normal"
}, 4],
SENSOR_DRY: ['Too dry', 'dry', 'is_too_dry', {
"on": "too_dry",
"off": "cap_normal"
}, 2],
SENSOR_WET: ['Too wet', 'wet', 'is_too_humid', {
"on": "too_humid",
"off": "cap_normal"
}, 2],
SENSOR_LIGHT: ['Light', 'light', 'is_light_on', {
"on": "too_bright",
"off": "light_normal"
}, 1],
SENSOR_MOISTURE: ['Leak', 'moisture', 'is_leaking', {
"on": "water_detected",
"off": "water_dried",
}, 1],
SENSOR_BATTERY: ['Low Battery', 'battery', 'is_battery_low', {
"on": "low_battery"
}, 3]
SENSOR_PRESENCE: 'Presence',
SENSOR_MOTION: 'Motion',
SENSOR_DOOR: 'Door',
SENSOR_COLD: 'Cold',
SENSOR_HEAT: 'Heat',
SENSOR_DRY: 'Too dry',
SENSOR_WET: 'Too wet',
SENSOR_LIGHT: 'Light',
SENSOR_MOISTURE: 'Leak',
SENSOR_BATTERY: 'Low Battery'
}
@@ -114,7 +83,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
sensors = []
tags = platform.tags
for tag in tags.values():
allowed_sensor_types = WirelessTagBinarySensor.allowed_sensors(tag)
allowed_sensor_types = tag.supported_binary_events_types
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type in allowed_sensor_types:
sensors.append(WirelessTagBinarySensor(platform, tag,
@@ -127,59 +96,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
"""A binary sensor implementation for WirelessTags."""
@classmethod
def allowed_sensors(cls, tag):
"""Return list of allowed sensor types for specific tag type."""
sensors_map = {
# 13-bit tag - allows everything but not light and moisture
WIRELESSTAG_TYPE_13BIT: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET],
# Moister/water sensor - temperature and moisture only
WIRELESSTAG_TYPE_WATER: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_MOISTURE],
# ALS Pro: allows everything, but not moisture
WIRELESSTAG_TYPE_ALSPRO: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET,
SENSOR_LIGHT],
# Wemo are power switches.
WIRELESSTAG_TYPE_WEMO_DEVICE: [SENSOR_PRESENCE]
}
# allow everything if tag type is unknown
# (i just dont have full catalog of them :))
tag_type = tag.tag_type
fullset = SENSOR_TYPES.keys()
return sensors_map[tag_type] if tag_type in sensors_map else fullset
def __init__(self, api, tag, sensor_type):
"""Initialize a binary sensor for a Wireless Sensor Tags."""
super().__init__(api, tag)
self._sensor_type = sensor_type
self._name = '{0} {1}'.format(self._tag.name,
SENSOR_TYPES[self._sensor_type][0])
self._device_class = SENSOR_TYPES[self._sensor_type][1]
self._tag_attr = SENSOR_TYPES[self._sensor_type][2]
self.binary_spec = SENSOR_TYPES[self._sensor_type][3]
self.tag_id_index_template = SENSOR_TYPES[self._sensor_type][4]
self.event.human_readable_name)
async def async_added_to_hass(self):
"""Register callbacks."""
tag_id = self.tag_id
event_type = self.device_class
mac = self.tag_manager_mac
async_dispatcher_connect(
self.hass,
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type),
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type, mac),
self._on_binary_event_callback)
@property
@@ -190,7 +121,12 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
return self._sensor_type
@property
def event(self):
"""Binary event of tag."""
return self._tag.event[self._sensor_type]
@property
def principal_value(self):
@@ -198,9 +134,7 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
Subclasses need override based on type of sensor.
"""
return (
STATE_ON if getattr(self._tag, self._tag_attr, False)
else STATE_OFF)
return STATE_ON if self.event.is_state_on else STATE_OFF
def updated_state_value(self):
"""Use raw princial value."""
@@ -208,7 +142,7 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
@callback
def _on_binary_event_callback(self, event):
"""Update state from arrive push notification."""
"""Update state from arrived push notification."""
# state should be 'on' or 'off'
self._state = event.data.get('state')
self.async_schedule_update_ha_state()

View File

@@ -15,35 +15,38 @@ from homeassistant.const import CONF_NAME, WEEKDAYS
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.9.7']
REQUIREMENTS = ['holidays==0.9.6']
_LOGGER = logging.getLogger(__name__)
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
ALL_COUNTRIES = ['Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT',
'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech',
'CZ', 'Denmark', 'DK', 'England', 'EuropeanCentralBank',
'ECB', 'TAR', 'Finland', 'FI', 'France', 'FRA', 'Germany',
'DE', 'Hungary', 'HU', 'India', 'IND', 'Ireland',
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',
'Sweden', 'SE', 'Switzerland', 'CH', 'UnitedKingdom', 'UK',
'UnitedStates', 'US', 'Wales']
ALL_COUNTRIES = [
'Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT', 'Belarus', 'BY'
'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech', 'CZ',
'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR',
'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU',
'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP',
'Mexico', 'MX', 'Netherlands', 'NL', 'NewZealand', 'NZ',
'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', 'Slovakia', 'SK',
'South Africa', 'ZA', 'Spain', 'ES', 'Sweden', 'SE', 'Switzerland', 'CH',
'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales',
]
ALLOWED_DAYS = WEEKDAYS + ['holiday']
CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province'
CONF_WORKDAYS = 'workdays'
CONF_EXCLUDES = 'excludes'
CONF_OFFSET = 'days_offset'
# By default, Monday - Friday are workdays
DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri']
CONF_EXCLUDES = 'excludes'
# By default, public holidays, Saturdays and Sundays are excluded from workdays
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
DEFAULT_NAME = 'Workday Sensor'
ALLOWED_DAYS = WEEKDAYS + ['holiday']
CONF_OFFSET = 'days_offset'
DEFAULT_OFFSET = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -86,7 +89,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
else:
_LOGGER.error("There is no province/state %s in country %s",
province, country)
return False
return
_LOGGER.debug("Found the following holidays for your configuration:")
for date, name in sorted(obj_holidays.items()):

View File

@@ -65,28 +65,25 @@ async def _async_setup_iaszone(hass, config, async_add_entities,
async def _async_setup_remote(hass, config, async_add_entities,
discovery_info):
async def safe(coro):
"""Run coro, catching ZigBee delivery errors, and ignoring them."""
import zigpy.exceptions
try:
await coro
except zigpy.exceptions.DeliveryError as exc:
_LOGGER.warning("Ignoring error during setup: %s", exc)
remote = Remote(**discovery_info)
if discovery_info['new_join']:
from zigpy.zcl.clusters.general import OnOff, LevelControl
out_clusters = discovery_info['out_clusters']
if OnOff.cluster_id in out_clusters:
cluster = out_clusters[OnOff.cluster_id]
await safe(cluster.bind())
await safe(cluster.configure_reporting(0, 0, 600, 1))
await zha.configure_reporting(
remote.entity_id, cluster, 0, min_report=0, max_report=600,
reportable_change=1
)
if LevelControl.cluster_id in out_clusters:
cluster = out_clusters[LevelControl.cluster_id]
await safe(cluster.bind())
await safe(cluster.configure_reporting(0, 1, 600, 1))
await zha.configure_reporting(
remote.entity_id, cluster, 0, min_report=1, max_report=600,
reportable_change=1
)
sensor = Switch(**discovery_info)
async_add_entities([sensor], update_before_add=True)
async_add_entities([remote], update_before_add=True)
class BinarySensor(zha.Entity, BinarySensorDevice):
@@ -131,17 +128,18 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
async def async_update(self):
"""Retrieve latest state."""
from bellows.types.basic import uint16_t
from zigpy.types.basic import uint16_t
result = await zha.safe_read(self._endpoint.ias_zone,
['zone_status'],
allow_cache=False)
allow_cache=False,
only_cache=(not self._initialized))
state = result.get('zone_status', self._state)
if isinstance(state, (int, uint16_t)):
self._state = result.get('zone_status', self._state) & 3
class Switch(zha.Entity, BinarySensorDevice):
class Remote(zha.Entity, BinarySensorDevice):
"""ZHA switch/remote controller/button."""
_domain = DOMAIN

View File

@@ -9,8 +9,8 @@ import datetime
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
async_setup_platform, workaround)
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)

View File

@@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bimmer_connected==0.5.1']
REQUIREMENTS = ['bimmer_connected==0.5.2']
_LOGGER = logging.getLogger(__name__)

View File

@@ -50,7 +50,7 @@ class AxisCamera(MjpegCamera):
def __init__(self, hass, config, port):
"""Initialize Axis Communications camera component."""
super().__init__(hass, config)
super().__init__(config)
self.port = port
dispatcher_connect(
hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip)

View File

@@ -0,0 +1,210 @@
"""
This component provides support to the Logi Circle camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.logi_circle/
"""
import logging
import asyncio
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.logi_circle import (
DOMAIN as LOGI_CIRCLE_DOMAIN, CONF_ATTRIBUTION)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA, CAMERA_SERVICE_SCHEMA, SUPPORT_ON_OFF,
ATTR_ENTITY_ID, ATTR_FILENAME, DOMAIN)
from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL,
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF)
DEPENDENCIES = ['logi_circle']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
SERVICE_SET_CONFIG = 'logi_circle_set_config'
SERVICE_LIVESTREAM_SNAPSHOT = 'logi_circle_livestream_snapshot'
SERVICE_LIVESTREAM_RECORD = 'logi_circle_livestream_record'
DATA_KEY = 'camera.logi_circle'
BATTERY_SAVING_MODE_KEY = 'BATTERY_SAVING'
PRIVACY_MODE_KEY = 'PRIVACY_MODE'
LED_MODE_KEY = 'LED'
ATTR_MODE = 'mode'
ATTR_VALUE = 'value'
ATTR_DURATION = 'duration'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
})
LOGI_CIRCLE_SERVICE_SET_CONFIG = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(ATTR_MODE): vol.In([BATTERY_SAVING_MODE_KEY, LED_MODE_KEY,
PRIVACY_MODE_KEY]),
vol.Required(ATTR_VALUE): cv.boolean
})
LOGI_CIRCLE_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(ATTR_FILENAME): cv.template
})
LOGI_CIRCLE_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(ATTR_FILENAME): cv.template,
vol.Required(ATTR_DURATION): cv.positive_int
})
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up a Logi Circle Camera."""
devices = hass.data[LOGI_CIRCLE_DOMAIN]
cameras = []
for device in devices:
cameras.append(LogiCam(device, config))
async_add_entities(cameras, True)
async def service_handler(service):
"""Dispatch service calls to target entities."""
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 cameras
if dev.entity_id in entity_ids]
else:
target_devices = cameras
for target_device in target_devices:
if service.service == SERVICE_SET_CONFIG:
await target_device.set_config(**params)
if service.service == SERVICE_LIVESTREAM_SNAPSHOT:
await target_device.livestream_snapshot(**params)
if service.service == SERVICE_LIVESTREAM_RECORD:
await target_device.download_livestream(**params)
hass.services.async_register(
DOMAIN, SERVICE_SET_CONFIG, service_handler,
schema=LOGI_CIRCLE_SERVICE_SET_CONFIG)
hass.services.async_register(
DOMAIN, SERVICE_LIVESTREAM_SNAPSHOT, service_handler,
schema=LOGI_CIRCLE_SERVICE_SNAPSHOT)
hass.services.async_register(
DOMAIN, SERVICE_LIVESTREAM_RECORD, service_handler,
schema=LOGI_CIRCLE_SERVICE_RECORD)
class LogiCam(Camera):
"""An implementation of a Logi Circle camera."""
def __init__(self, camera, device_info):
"""Initialize Logi Circle camera."""
super().__init__()
self._camera = camera
self._name = self._camera.name
self._id = self._camera.mac_address
self._has_battery = self._camera.supports_feature('battery_level')
@property
def unique_id(self):
"""Return a unique ID."""
return self._id
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def supported_features(self):
"""Logi Circle camera's support turning on and off ("soft" switch)."""
return SUPPORT_ON_OFF
@property
def device_state_attributes(self):
"""Return the state attributes."""
state = {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'battery_saving_mode': (
STATE_ON if self._camera.battery_saving else STATE_OFF),
'ip_address': self._camera.ip_address,
'microphone_gain': self._camera.microphone_gain
}
# Add battery attributes if camera is battery-powered
if self._has_battery:
state[ATTR_BATTERY_CHARGING] = self._camera.is_charging
state[ATTR_BATTERY_LEVEL] = self._camera.battery_level
return state
async def async_camera_image(self):
"""Return a still image from the camera."""
return await self._camera.get_snapshot_image()
async def async_turn_off(self):
"""Disable streaming mode for this camera."""
await self._camera.set_streaming_mode(False)
async def async_turn_on(self):
"""Enable streaming mode for this camera."""
await self._camera.set_streaming_mode(True)
@property
def should_poll(self):
"""Update the image periodically."""
return True
async def set_config(self, mode, value):
"""Set an configuration property for the target camera."""
if mode == LED_MODE_KEY:
await self._camera.set_led(value)
if mode == PRIVACY_MODE_KEY:
await self._camera.set_privacy_mode(value)
if mode == BATTERY_SAVING_MODE_KEY:
await self._camera.set_battery_saving_mode(value)
async def download_livestream(self, filename, duration):
"""Download a recording from the camera's livestream."""
# Render filename from template.
filename.hass = self.hass
stream_file = filename.async_render(
variables={ATTR_ENTITY_ID: self.entity_id})
# Respect configured path whitelist.
if not self.hass.config.is_allowed_path(stream_file):
_LOGGER.error(
"Can't write %s, no access to path!", stream_file)
return
asyncio.shield(self._camera.record_livestream(
stream_file, timedelta(seconds=duration)), loop=self.hass.loop)
async def livestream_snapshot(self, filename):
"""Download a still frame from the camera's livestream."""
# Render filename from template.
filename.hass = self.hass
snapshot_file = filename.async_render(
variables={ATTR_ENTITY_ID: self.entity_id})
# Respect configured path whitelist.
if not self.hass.config.is_allowed_path(snapshot_file):
_LOGGER.error(
"Can't write %s, no access to path!", snapshot_file)
return
asyncio.shield(self._camera.get_livestream_image(
snapshot_file), loop=self.hass.loop)
async def async_update(self):
"""Update camera entity and refresh attributes."""
await self._camera.update()

View File

@@ -47,7 +47,7 @@ def async_setup_platform(hass, config, async_add_entities,
"""Set up a MJPEG IP Camera."""
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(hass, config)])
async_add_entities([MjpegCamera(config)])
def extract_image_from_mjpeg(stream):
@@ -65,7 +65,7 @@ def extract_image_from_mjpeg(stream):
class MjpegCamera(Camera):
"""An implementation of an IP camera that is reachable over a URL."""
def __init__(self, hass, device_info):
def __init__(self, device_info):
"""Initialize a MJPEG camera."""
super().__init__()
self._name = device_info.get(CONF_NAME)

View File

@@ -19,12 +19,14 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_TOPIC = 'topic'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_NAME = 'MQTT Camera'
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
@@ -38,6 +40,7 @@ def async_setup_platform(hass, config, async_add_entities,
async_add_entities([MqttCamera(
config.get(CONF_NAME),
config.get(CONF_UNIQUE_ID),
config.get(CONF_TOPIC)
)])
@@ -45,11 +48,12 @@ def async_setup_platform(hass, config, async_add_entities,
class MqttCamera(Camera):
"""representation of a MQTT camera."""
def __init__(self, name, topic):
def __init__(self, name, unique_id, topic):
"""Initialize the MQTT Camera."""
super().__init__()
self._name = name
self._unique_id = unique_id
self._topic = topic
self._qos = 0
self._last_image = None
@@ -64,6 +68,11 @@ class MqttCamera(Camera):
"""Return the name of this camera."""
return self._name
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe MQTT events."""

View File

@@ -62,6 +62,23 @@ class NestCamera(Camera):
"""Return the name of the nest, if any."""
return self._name
@property
def unique_id(self):
"""Return the serial number."""
return self.device.device_id
@property
def device_info(self):
"""Return information about the device."""
return {
'identifiers': {
(nest.DOMAIN, self.device.device_id)
},
'name': self.device.name_long,
'manufacturer': 'Nest Labs',
'model': "Camera",
}
@property
def should_poll(self):
"""Nest camera should poll periodically."""

View File

@@ -39,9 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a Ring Door Bell and StickUp Camera."""
ring = hass.data[DATA_RING]
@@ -67,14 +65,14 @@ def async_setup_platform(hass, config, async_add_entities,
''' following cameras: {}.'''.format(cameras)
_LOGGER.error(err_msg)
hass.components.persistent_notification.async_create(
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(err_msg),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
async_add_entities(cams, True)
add_entities(cams, True)
return True

View File

@@ -63,3 +63,39 @@ onvif_ptz:
zoom:
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
example: "ZOOM_IN"
logi_circle_set_config:
description: Set a configuration property.
fields:
entity_id:
description: Name(s) of entities to apply the operation mode to.
example: "camera.living_room_camera"
mode:
description: "Operation mode. Allowed values: BATTERY_SAVING, LED, PRIVACY_MODE."
example: "PRIVACY_MODE"
value:
description: "Operation value. Allowed values: true, false"
example: true
logi_circle_livestream_snapshot:
description: Take a snapshot from the camera's livestream. Will wake the camera from sleep if required.
fields:
entity_id:
description: Name(s) of entities to create snapshots from.
example: "camera.living_room_camera"
filename:
description: Template of a Filename. Variable is entity_id.
example: "/tmp/snapshot_{{ entity_id }}.jpg"
logi_circle_livestream_record:
description: Take a video recording from the camera's livestream.
fields:
entity_id:
description: Name(s) of entities to create recordings from.
example: "camera.living_room_camera"
filename:
description: Template of a Filename. Variable is entity_id.
example: "/tmp/snapshot_{{ entity_id }}.mp4"
duration:
description: Recording duration in seconds.
example: 60

View File

@@ -4,91 +4,47 @@ Support for ZoneMinder camera streaming.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.zoneminder/
"""
import asyncio
import logging
from urllib.parse import urljoin, urlencode
from homeassistant.const import CONF_NAME
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.components import zoneminder
from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['zoneminder']
DOMAIN = 'zoneminder'
# From ZoneMinder's web/includes/config.php.in
ZM_STATE_ALARM = "2"
def _get_image_url(hass, monitor, mode):
zm_data = hass.data[DOMAIN]
query = urlencode({
'mode': mode,
'buffer': monitor['StreamReplayBuffer'],
'monitor': monitor['Id'],
})
url = '{zms_url}?{query}'.format(
zms_url=urljoin(zm_data['server_origin'], zm_data['path_zms']),
query=query,
)
_LOGGER.debug('Monitor %s %s URL (without auth): %s',
monitor['Id'], mode, url)
if not zm_data['username']:
return url
url += '&user={:s}'.format(zm_data['username'])
if not zm_data['password']:
return url
return url + '&pass={:s}'.format(zm_data['password'])
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder cameras."""
cameras = []
monitors = zoneminder.get_state('api/monitors.json')
zm_client = hass.data[ZONEMINDER_DOMAIN]
monitors = zm_client.get_monitors()
if not monitors:
_LOGGER.warning("Could not fetch monitors from ZoneMinder")
return
for i in monitors['monitors']:
monitor = i['Monitor']
if monitor['Function'] == 'None':
_LOGGER.info("Skipping camera %s", monitor['Id'])
continue
_LOGGER.info("Initializing camera %s", monitor['Id'])
device_info = {
CONF_NAME: monitor['Name'],
CONF_MJPEG_URL: _get_image_url(hass, monitor, 'jpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(hass, monitor, 'single')
}
cameras.append(ZoneMinderCamera(hass, device_info, monitor))
if not cameras:
_LOGGER.warning("No active cameras found")
return
async_add_entities(cameras)
cameras = []
for monitor in monitors:
_LOGGER.info("Initializing camera %s", monitor.id)
cameras.append(ZoneMinderCamera(monitor))
add_entities(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, hass, device_info, monitor):
def __init__(self, monitor):
"""Initialize as a subclass of MjpegCamera."""
super().__init__(hass, device_info)
self._monitor_id = int(monitor['Id'])
device_info = {
CONF_NAME: monitor.name,
CONF_MJPEG_URL: monitor.mjpeg_image_url,
CONF_STILL_IMAGE_URL: monitor.still_image_url
}
super().__init__(device_info)
self._is_recording = None
self._monitor = monitor
@property
def should_poll(self):
@@ -97,17 +53,8 @@ class ZoneMinderCamera(MjpegCamera):
def update(self):
"""Update our recording state from the ZM API."""
_LOGGER.debug("Updating camera state for monitor %i", self._monitor_id)
status_response = zoneminder.get_state(
'api/monitors/alarm/id:%i/command:status.json' % self._monitor_id
)
if not status_response:
_LOGGER.warning("Could not get status for monitor %i",
self._monitor_id)
return
self._is_recording = status_response.get('status') == ZM_STATE_ALARM
_LOGGER.debug("Updating camera state for monitor %i", self._monitor.id)
self._is_recording = self._monitor.is_recording
@property
def is_recording(self):

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.",
"single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de Google Cast."
"single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de Google Cast."
},
"step": {
"confirm": {

View File

@@ -6,7 +6,7 @@
},
"step": {
"confirm": {
"description": "M\u00f6chten Sie Google Cast einrichten?",
"description": "M\u00f6chtest du Google Cast einrichten?",
"title": "Google Cast"
}
},

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.",
"single_instance_allowed": "Seulement une seule configuration de Google Cast est n\u00e9cessaire."
"single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire."
},
"step": {
"confirm": {

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Tidak ada perangkat Google Cast yang ditemukan pada jaringan.",
"single_instance_allowed": "Hanya satu konfigurasi Google Cast yang diperlukan."
},
"step": {
"confirm": {
"description": "Apakah Anda ingin menyiapkan Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Klar",
"single_instance_allowed": "Du treng berre \u00e5 sette opp \u00e9in Google Cast-konfigurasjon."
},
"step": {
"confirm": {
"description": "Vil du sette opp Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -35,4 +35,5 @@ async def _async_has_devices(hass):
config_entry_flow.register_discovery_flow(
DOMAIN, 'Google Cast', _async_has_devices)
DOMAIN, 'Google Cast', _async_has_devices,
config_entries.CONN_CLASS_LOCAL_PUSH)

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyeconet==0.0.5']
REQUIREMENTS = ['pyeconet==0.0.6']
_LOGGER = logging.getLogger(__name__)

View File

@@ -72,8 +72,9 @@ class OpenThermGateway(ClimateDevice):
"""Receive and handle a new report from the Gateway."""
_LOGGER.debug("Received report: %s", status)
ch_active = status.get(self.pyotgw.DATA_SLAVE_CH_ACTIVE)
flame_on = status.get(self.pyotgw.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(self.pyotgw.DATA_SLAVE_COOLING_ACTIVE)
if ch_active:
if ch_active and flame_on:
self._current_operation = STATE_HEAT
elif cooling_active:
self._current_operation = STATE_COOL

View File

@@ -118,7 +118,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
if self.external_temperature:
if self.external_temperature is not None:
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.hass, self.external_temperature, self.temperature_unit,
PRECISION_TENTHS)
@@ -126,16 +126,16 @@ class WinkThermostat(WinkDevice, ClimateDevice):
if self.smart_temperature:
data[ATTR_SMART_TEMPERATURE] = self.smart_temperature
if self.occupied:
if self.occupied is not None:
data[ATTR_OCCUPIED] = self.occupied
if self.eco_target:
if self.eco_target is not None:
data[ATTR_ECO_TARGET] = self.eco_target
if self.heat_on:
if self.heat_on is not None:
data[ATTR_HEAT_ON] = self.heat_on
if self.cool_on:
if self.cool_on is not None:
data[ATTR_COOL_ON] = self.cool_on
current_humidity = self.current_humidity

View File

@@ -10,8 +10,8 @@ from homeassistant.components.climate import (
DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
ZWaveDeviceEntity, async_setup_platform)
from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)

View File

@@ -23,12 +23,17 @@ from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import helpers as ga_h
from homeassistant.components.google_assistant import const as ga_c
from . import http_api, iot
from . import http_api, iot, auth_api
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.6.1']
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
STORAGE_ENABLE_ALEXA = 'alexa_enabled'
STORAGE_ENABLE_GOOGLE = 'google_enabled'
_LOGGER = logging.getLogger(__name__)
_UNDEF = object()
CONF_ALEXA = 'alexa'
CONF_ALIASES = 'aliases'
@@ -39,6 +44,7 @@ CONF_GOOGLE_ACTIONS = 'google_actions'
CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
CONF_GOOGLE_ACTIONS_SYNC_URL = 'google_actions_sync_url'
CONF_SUBSCRIPTION_INFO_URL = 'subscription_info_url'
DEFAULT_MODE = 'production'
DEPENDENCIES = ['http']
@@ -79,6 +85,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_REGION): str,
vol.Optional(CONF_RELAYER): str,
vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): str,
vol.Optional(CONF_SUBSCRIPTION_INFO_URL): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
}),
@@ -114,18 +121,21 @@ class Cloud:
def __init__(self, hass, mode, alexa, google_actions,
cognito_client_id=None, user_pool_id=None, region=None,
relayer=None, google_actions_sync_url=None):
relayer=None, google_actions_sync_url=None,
subscription_info_url=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
self.alexa_config = alexa
self._google_actions = google_actions
self._gactions_config = None
self._prefs = None
self.jwt_keyset = None
self.id_token = None
self.access_token = None
self.refresh_token = None
self.iot = iot.CloudIoT(self)
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
if mode == MODE_DEV:
self.cognito_client_id = cognito_client_id
@@ -133,6 +143,7 @@ class Cloud:
self.region = region
self.relayer = relayer
self.google_actions_sync_url = google_actions_sync_url
self.subscription_info_url = subscription_info_url
else:
info = SERVERS[mode]
@@ -142,6 +153,7 @@ class Cloud:
self.region = info['region']
self.relayer = info['relayer']
self.google_actions_sync_url = info['google_actions_sync_url']
self.subscription_info_url = info['subscription_info_url']
@property
def is_logged_in(self):
@@ -188,6 +200,16 @@ class Cloud:
return self._gactions_config
@property
def alexa_enabled(self):
"""Return if Alexa is enabled."""
return self._prefs[STORAGE_ENABLE_ALEXA]
@property
def google_enabled(self):
"""Return if Google is enabled."""
return self._prefs[STORAGE_ENABLE_GOOGLE]
def path(self, *parts):
"""Get config path inside cloud dir.
@@ -195,6 +217,15 @@ class Cloud:
"""
return self.hass.config.path(CONFIG_DIR, *parts)
async def fetch_subscription_info(self):
"""Fetch subscription info."""
await self.hass.async_add_executor_job(auth_api.check_token, self)
websession = self.hass.helpers.aiohttp_client.async_get_clientsession()
return await websession.get(
self.subscription_info_url, headers={
'authorization': self.id_token
})
@asyncio.coroutine
def logout(self):
"""Close connection and remove all credentials."""
@@ -217,10 +248,23 @@ class Cloud:
'refresh_token': self.refresh_token,
}, indent=4))
@asyncio.coroutine
def async_start(self, _):
async def async_start(self, _):
"""Start the cloud component."""
success = yield from self._fetch_jwt_keyset()
prefs = await self._store.async_load()
if prefs is None:
prefs = {}
if self.mode not in prefs:
# Default to True if already logged in to make this not a
# breaking change.
enabled = await self.hass.async_add_executor_job(
os.path.isfile, self.user_info_path)
prefs = {
STORAGE_ENABLE_ALEXA: enabled,
STORAGE_ENABLE_GOOGLE: enabled,
}
self._prefs = prefs
success = await self._fetch_jwt_keyset()
# Fetching keyset can fail if internet is not up yet.
if not success:
@@ -241,7 +285,7 @@ class Cloud:
with open(user_info, 'rt') as file:
return json.loads(file.read())
info = yield from self.hass.async_add_job(load_config)
info = await self.hass.async_add_job(load_config)
if info is None:
return
@@ -260,6 +304,15 @@ class Cloud:
self.hass.add_job(self.iot.connect())
async def update_preferences(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF):
"""Update user preferences."""
if google_enabled is not _UNDEF:
self._prefs[STORAGE_ENABLE_GOOGLE] = google_enabled
if alexa_enabled is not _UNDEF:
self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled
await self._store.async_save(self._prefs)
@asyncio.coroutine
def _fetch_jwt_keyset(self):
"""Fetch the JWT keyset for the Cognito instance."""

View File

@@ -11,6 +11,8 @@ SERVERS = {
'relayer': 'wss://cloud.hass.io:8000/websocket',
'google_actions_sync_url': ('https://24ab3v80xd.execute-api.us-east-1.'
'amazonaws.com/prod/smart_home_sync'),
'subscription_info_url': ('https://stripe-api.nabucasa.com/payments/'
'subscription_info')
}
}

View File

@@ -6,22 +6,56 @@ import logging
import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import (
RequestDataValidator)
from homeassistant.components import websocket_api
from . import auth_api
from .const import DOMAIN, REQUEST_TIMEOUT
from .iot import STATE_DISCONNECTED
_LOGGER = logging.getLogger(__name__)
WS_TYPE_STATUS = 'cloud/status'
SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_STATUS,
})
WS_TYPE_UPDATE_PREFS = 'cloud/update_prefs'
SCHEMA_WS_UPDATE_PREFS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_UPDATE_PREFS,
vol.Optional('google_enabled'): bool,
vol.Optional('alexa_enabled'): bool,
})
WS_TYPE_SUBSCRIPTION = 'cloud/subscription'
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_SUBSCRIPTION,
})
async def async_setup(hass):
"""Initialize the HTTP API."""
hass.components.websocket_api.async_register_command(
WS_TYPE_STATUS, websocket_cloud_status,
SCHEMA_WS_STATUS
)
hass.components.websocket_api.async_register_command(
WS_TYPE_SUBSCRIPTION, websocket_subscription,
SCHEMA_WS_SUBSCRIPTION
)
hass.components.websocket_api.async_register_command(
WS_TYPE_UPDATE_PREFS, websocket_update_prefs,
SCHEMA_WS_UPDATE_PREFS
)
hass.http.register_view(GoogleActionsSyncView)
hass.http.register_view(CloudLoginView)
hass.http.register_view(CloudLogoutView)
hass.http.register_view(CloudAccountView)
hass.http.register_view(CloudRegisterView)
hass.http.register_view(CloudResendConfirmView)
hass.http.register_view(CloudForgotPasswordView)
@@ -102,9 +136,7 @@ class CloudLoginView(HomeAssistantView):
data['password'])
hass.async_add_job(cloud.iot.connect)
# Allow cloud to start connecting.
await asyncio.sleep(0, loop=hass.loop)
return self.json(_account_data(cloud))
return self.json({'success': True})
class CloudLogoutView(HomeAssistantView):
@@ -125,23 +157,6 @@ class CloudLogoutView(HomeAssistantView):
return self.json_message('ok')
class CloudAccountView(HomeAssistantView):
"""View to retrieve account info."""
url = '/api/cloud/account'
name = 'api:cloud:account'
async def get(self, request):
"""Get account info."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
return self.json_message('Not logged in', 400)
return self.json(_account_data(cloud))
class CloudRegisterView(HomeAssistantView):
"""Register on the Home Assistant cloud."""
@@ -209,12 +224,73 @@ class CloudForgotPasswordView(HomeAssistantView):
return self.json_message('ok')
@callback
def websocket_cloud_status(hass, connection, msg):
"""Handle request for account info.
Async friendly.
"""
cloud = hass.data[DOMAIN]
connection.to_write.put_nowait(
websocket_api.result_message(msg['id'], _account_data(cloud)))
@websocket_api.async_response
async def websocket_subscription(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
connection.to_write.put_nowait(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
response = await cloud.fetch_subscription_info()
if response.status == 200:
connection.send_message_outside(websocket_api.result_message(
msg['id'], await response.json()))
else:
connection.send_message_outside(websocket_api.error_message(
msg['id'], 'request_failed', 'Failed to request subscription'))
@websocket_api.async_response
async def websocket_update_prefs(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
connection.to_write.put_nowait(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
changes = dict(msg)
changes.pop('id')
changes.pop('type')
await cloud.update_preferences(**changes)
connection.send_message_outside(websocket_api.result_message(
msg['id'], {'success': True}))
def _account_data(cloud):
"""Generate the auth data JSON response."""
if not cloud.is_logged_in:
return {
'logged_in': False,
'cloud': STATE_DISCONNECTED,
}
claims = cloud.claims
return {
'logged_in': True,
'email': claims['email'],
'sub_exp': claims['custom:sub-exp'],
'cloud': cloud.iot.state,
'google_enabled': cloud.google_enabled,
'alexa_enabled': cloud.alexa_enabled,
}

View File

@@ -227,6 +227,9 @@ def async_handle_message(hass, cloud, handler_name, payload):
@asyncio.coroutine
def async_handle_alexa(hass, cloud, payload):
"""Handle an incoming IoT message for Alexa."""
if not cloud.alexa_enabled:
return alexa.turned_off_response(payload)
result = yield from alexa.async_handle_message(
hass, cloud.alexa_config, payload)
return result
@@ -236,6 +239,9 @@ def async_handle_alexa(hass, cloud, payload):
@asyncio.coroutine
def async_handle_google_actions(hass, cloud, payload):
"""Handle an incoming IoT message for Google Actions."""
if not cloud.google_enabled:
return ga.turned_off_response(payload)
result = yield from ga.async_handle_message(
hass, cloud.gactions_config, payload)
return result

View File

@@ -13,8 +13,17 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script',
'entity_registry', 'config_entries')
SECTIONS = (
'automation',
'config_entries',
'core',
'customize',
'device_registry',
'entity_registry',
'group',
'hassbian',
'script',
)
ON_DEMAND = ('zwave',)

View File

@@ -54,6 +54,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
'title': entry.title,
'source': entry.source,
'state': entry.state,
'connection_class': entry.connection_class,
} for entry in hass.config_entries.async_entries()])

View File

@@ -0,0 +1,47 @@
"""HTTP views to interact with the device registry."""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.device_registry import async_get_registry
from homeassistant.components import websocket_api
DEPENDENCIES = ['websocket_api']
WS_TYPE_LIST = 'config/device_registry/list'
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_LIST,
})
async def async_setup(hass):
"""Enable the Entity Registry views."""
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list_devices,
SCHEMA_WS_LIST
)
return True
@callback
def websocket_list_devices(hass, connection, msg):
"""Handle list devices command.
Async friendly.
"""
async def retrieve_entities():
"""Get devices from registry."""
registry = await async_get_registry(hass)
connection.send_message_outside(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()]
))
hass.async_add_job(retrieve_entities())

View File

@@ -8,6 +8,11 @@ from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['websocket_api']
WS_TYPE_LIST = 'config/entity_registry/list'
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_LIST,
})
WS_TYPE_GET = 'config/entity_registry/get'
SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_GET,
@@ -26,6 +31,10 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
async def async_setup(hass):
"""Enable the Entity Registry views."""
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list_entities,
SCHEMA_WS_LIST
)
hass.components.websocket_api.async_register_command(
WS_TYPE_GET, websocket_get_entity,
SCHEMA_WS_GET
@@ -37,6 +46,29 @@ async def async_setup(hass):
return True
@callback
def websocket_list_entities(hass, connection, msg):
"""Handle list registry entries command.
Async friendly.
"""
async def retrieve_entities():
"""Get entities from registry."""
registry = await async_get_registry(hass)
connection.send_message_outside(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()]
))
hass.async_add_job(retrieve_entities())
@callback
def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command.

View File

@@ -35,8 +35,9 @@ ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEVICE_CLASSES = [
'window', # Window control
'damper',
'garage', # Garage door control
'window', # Window control
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
@@ -140,7 +141,7 @@ def stop_cover_tilt(hass, entity_id=None):
async def async_setup(hass, config):
"""Track states and offer events for covers."""
component = EntityComponent(
component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS)
await component.async_setup(config)
@@ -195,6 +196,16 @@ async def async_setup(hass, config):
return True
async def async_setup_entry(hass, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class CoverDevice(Entity):
"""Representation a cover."""

View File

@@ -0,0 +1,146 @@
"""
Support for deCONZ covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.deconz/
"""
from homeassistant.components.deconz.const import (
COVER_TYPES, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB,
DECONZ_DOMAIN)
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN,
SUPPORT_SET_POSITION)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Unsupported way of setting up deCONZ covers."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up covers for deCONZ component.
Covers are based on same device class as lights in deCONZ.
"""
@callback
def async_add_cover(lights):
"""Add cover from deCONZ."""
entities = []
for light in lights:
if light.type in COVER_TYPES:
entities.append(DeconzCover(light))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover))
async_add_cover(hass.data[DATA_DECONZ].lights.values())
class DeconzCover(CoverDevice):
"""Representation of a deCONZ cover."""
def __init__(self, cover):
"""Set up cover and add update callback to get data from websocket."""
self._cover = cover
self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
async def async_added_to_hass(self):
"""Subscribe to covers events."""
self._cover.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._cover.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect cover object when removed."""
self._cover.remove_callback(self.async_update_callback)
self._cover = None
@callback
def async_update_callback(self, reason):
"""Update the cover's state."""
self.async_schedule_update_ha_state()
@property
def current_cover_position(self):
"""Return the current position of the cover."""
if self.is_closed:
return 0
return int(self._cover.brightness / 255 * 100)
@property
def is_closed(self):
"""Return if the cover is closed."""
return not self._cover.state
@property
def name(self):
"""Return the name of the cover."""
return self._cover.name
@property
def unique_id(self):
"""Return a unique identifier for this cover."""
return self._cover.uniqueid
@property
def device_class(self):
"""Return the class of the cover."""
return 'damper'
@property
def supported_features(self):
"""Flag supported features."""
return self._features
@property
def available(self):
"""Return True if light is available."""
return self._cover.reachable
@property
def should_poll(self):
"""No polling needed."""
return False
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
data = {'on': False}
if position > 0:
data['on'] = True
data['bri'] = int(position / 100 * 255)
await self._cover.async_set_state(data)
async def async_open_cover(self, **kwargs):
"""Open cover."""
data = {ATTR_POSITION: 100}
await self.async_set_cover_position(**data)
async def async_close_cover(self, **kwargs):
"""Close cover."""
data = {ATTR_POSITION: 0}
await self.async_set_cover_position(**data)
@property
def device_info(self):
"""Return a device description for device registry."""
if (self._cover.uniqueid is None or
self._cover.uniqueid.count(':') != 7):
return None
serial = self._cover.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},
'manufacturer': self._cover.manufacturer,
'model': self._cover.modelid,
'name': self._cover.name,
'sw_version': self._cover.swversion,
'via_hub': (DECONZ_DOMAIN, bridgeid),
}

View File

@@ -44,6 +44,8 @@ class ISYCoverDevice(ISYDevice, CoverDevice):
@property
def current_cover_position(self) -> int:
"""Return the current cover position."""
if self.is_unknown() or self.value is None:
return None
return sorted((0, self.value, 100))[1]
@property

View File

@@ -11,8 +11,8 @@ import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
from homeassistant.const import (
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
STATE_OPENING)
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_OPEN,
STATE_CLOSING, STATE_OPENING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.15']
@@ -23,6 +23,7 @@ DEFAULT_NAME = 'myq'
MYQ_TO_HASS = {
'closed': STATE_CLOSED,
'open': STATE_OPEN,
'closing': STATE_CLOSING,
'opening': STATE_OPENING
}

View File

@@ -45,6 +45,11 @@ class TuyaCover(TuyaDevice, CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed or not."""
state = self.tuya.state()
if state == 1:
return False
if state == 2:
return True
return None
def open_cover(self, **kwargs):

View File

@@ -9,10 +9,9 @@ https://home-assistant.io/components/cover.zwave/
import logging
from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.zwave import workaround
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
ZWaveDeviceEntity, async_setup_platform, workaround)
from homeassistant.components.cover import CoverDevice
_LOGGER = logging.getLogger(__name__)

View File

@@ -28,6 +28,6 @@
"title": "Opcions de configuraci\u00f3 addicionals per deCONZ"
}
},
"title": "deCONZ"
"title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee"
}
}

View File

@@ -14,10 +14,10 @@
"host": "Host",
"port": "Port (Standartwert : '80')"
},
"title": "Definieren Sie den deCONZ-Gateway"
"title": "Definiere das deCONZ-Gateway"
},
"link": {
"description": "Entsperren Sie Ihr deCONZ-Gateway, um sich bei Home Assistant zu registrieren. \n\n 1. Gehen Sie zu den deCONZ-Systemeinstellungen \n 2. Dr\u00fccken Sie die Taste \"Gateway entsperren\"",
"description": "Entsperre dein deCONZ-Gateway, um dich bei Home Assistant zu registrieren. \n\n 1. Gehe zu den deCONZ-Systemeinstellungen \n 2. Dr\u00fccke die Taste \"Gateway entsperren\"",
"title": "Mit deCONZ verbinden"
},
"options": {

View File

@@ -2,7 +2,8 @@
"config": {
"abort": {
"already_configured": "\u05d4\u05de\u05d2\u05e9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8",
"no_bridges": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05d2\u05e9\u05e8\u05d9 deCONZ"
"no_bridges": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05d2\u05e9\u05e8\u05d9 deCONZ",
"one_instance_only": "\u05d4\u05e8\u05db\u05d9\u05d1 \u05ea\u05d5\u05de\u05da \u05e8\u05e7 \u05d0\u05d7\u05d3 deCONZ \u05dc\u05de\u05e9\u05dc"
},
"error": {
"no_key": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05d4\u05d9\u05d4 \u05dc\u05e7\u05d1\u05dc \u05de\u05e4\u05ea\u05d7 API"

View File

@@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "Bridge sudah dikonfigurasi",
"no_bridges": "deCONZ bridges tidak ditemukan",
"one_instance_only": "Komponen hanya mendukung satu instance deCONZ"
},
"error": {
"no_key": "Tidak bisa mendapatkan kunci API"
},
"step": {
"init": {
"data": {
"host": "Host",
"port": "Port (nilai default: '80')"
},
"title": "Tentukan deCONZ gateway"
},
"link": {
"description": "Buka gerbang deCONZ Anda untuk mendaftar dengan Home Assistant. \n\n 1. Pergi ke pengaturan sistem deCONZ \n 2. Tekan tombol \"Buka Kunci Gateway\"",
"title": "Tautan dengan deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Izinkan mengimpor sensor virtual",
"allow_deconz_groups": "Izinkan mengimpor grup deCONZ"
},
"title": "Opsi konfigurasi tambahan untuk deCONZ"
}
},
"title": "deCONZ Zigbee gateway"
}
}

View File

@@ -3,7 +3,7 @@
"abort": {
"already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9bf\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4",
"one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 deCONZ \uc778\uc2a4\ud134\uc2a4 \ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4"
"one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 deCONZ \uc778\uc2a4\ud134\uc2a4\ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4"
},
"error": {
"no_key": "API \ud0a4\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"

View File

@@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "Brua er allereie konfigurert",
"no_bridges": "Oppdaga ingen deCONZ-bruer",
"one_instance_only": "Komponenten st\u00f8ttar berre \u00e9in deCONZ-instans"
},
"error": {
"no_key": "Kunne ikkje f\u00e5 ein API-n\u00f8kkel"
},
"step": {
"init": {
"data": {
"host": "Vert",
"port": "Port (standardverdi: '80')"
},
"title": "Definer deCONZ-gateway"
},
"link": {
"description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere den med Home Assistant.\n\n1. G\u00e5 til systeminnstillingane til deCONZ\n2. Trykk p\u00e5 \"L\u00e5s opp gateway\"-knappen",
"title": "Link med deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Tillat importering av virtuelle sensorar",
"allow_deconz_groups": "Tillat importering av deCONZ-grupper"
},
"title": "Ekstra konfigurasjonsalternativ for deCONZ"
}
},
"title": "deCONZ Zigbee gateway"
}
}

View File

@@ -26,6 +26,9 @@ from .const import (
REQUIREMENTS = ['pydeconz==47']
SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
'light', 'scene', 'sensor', 'switch']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_KEY): cv.string,
@@ -104,7 +107,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[DATA_DECONZ_EVENT] = []
hass.data[DATA_DECONZ_UNSUB] = []
for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
for component in SUPPORTED_PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, component))
@@ -127,7 +130,7 @@ async def async_setup_entry(hass, config_entry):
device_registry = await \
hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create(
config_entry=config_entry.entry_id,
config_entry_id=config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)},
identifiers={(DOMAIN, deconz.config.bridgeid)},
manufacturer='Dresden Elektronik', model=deconz.config.modelid,
@@ -228,7 +231,7 @@ async def async_unload_entry(hass, config_entry):
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
deconz.close()
for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
for component in SUPPORTED_PLATFORMS:
await hass.config_entries.async_forward_entry_unload(
config_entry, component)

View File

@@ -2,7 +2,7 @@
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.helpers import aiohttp_client
@@ -23,10 +23,11 @@ def configured_hosts(hass):
@config_entries.HANDLERS.register(DOMAIN)
class DeconzFlowHandler(data_entry_flow.FlowHandler):
class DeconzFlowHandler(config_entries.ConfigFlow):
"""Handle a deCONZ config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
def __init__(self):
"""Initialize the deCONZ config flow."""

View File

@@ -16,6 +16,8 @@ CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
ATTR_DARK = 'dark'
ATTR_ON = 'on'
COVER_TYPES = ["Level controllable output"]
POWER_PLUGS = ["On/Off plug-in unit", "Smart plug"]
SIRENS = ["Warning device"]
SWITCH_TYPES = POWER_PLUGS + SIRENS

View File

@@ -5,19 +5,30 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bbox/
"""
from collections import namedtuple
import logging
from datetime import timedelta
import logging
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN, DeviceScanner
import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['pybbox==0.0.5-alpha']
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = '192.168.1.254'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
})
def get_scanner(hass, config):
"""Validate the configuration and return a Bbox scanner."""
@@ -33,6 +44,9 @@ class BboxDeviceScanner(DeviceScanner):
"""This class scans for devices connected to the bbox."""
def __init__(self, config):
"""Get host from config."""
self.host = config[CONF_HOST]
"""Initialize the scanner."""
self.last_results = [] # type: List[Device]
@@ -64,7 +78,7 @@ class BboxDeviceScanner(DeviceScanner):
import pybbox
box = pybbox.Bbox()
box = pybbox.Bbox(ip=self.host)
result = box.get_all_connected_devices()
now = dt_util.now()

View File

@@ -6,35 +6,25 @@ https://home-assistant.io/components/device_tracker.bluetooth_le_tracker/
"""
import logging
import voluptuous as vol
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA, load_config, SOURCE_TYPE_BLUETOOTH_LE
load_config, SOURCE_TYPE_BLUETOOTH_LE
)
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['gattlib==0.20150805']
REQUIREMENTS = ['pygatt==3.2.0']
BLE_PREFIX = 'BLE_'
MIN_SEEN_NEW = 5
CONF_SCAN_DURATION = 'scan_duration'
CONF_BLUETOOTH_DEVICE = 'device_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int,
vol.Optional(CONF_BLUETOOTH_DEVICE, default='hci0'): cv.string
})
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the Bluetooth LE Scanner."""
# pylint: disable=import-error
from gattlib import DiscoveryService
import pygatt
new_devices = {}
def see_device(address, name, new_device=False):
@@ -61,17 +51,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Discover Bluetooth LE devices."""
_LOGGER.debug("Discovering Bluetooth LE devices")
try:
service = DiscoveryService(ble_dev_id)
devices = service.discover(duration)
adapter = pygatt.GATTToolBackend()
devs = adapter.scan()
devices = {x['address']: x['name'] for x in devs}
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
except RuntimeError as error:
_LOGGER.error("Error during Bluetooth LE scan: %s", error)
devices = []
return {}
return devices
yaml_path = hass.config.path(YAML_DEVICES)
duration = config.get(CONF_SCAN_DURATION)
ble_dev_id = config.get(CONF_BLUETOOTH_DEVICE)
devs_to_track = []
devs_donot_track = []
@@ -102,11 +92,11 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Lookup Bluetooth LE devices and update status."""
devs = discover_ble_devices()
for mac in devs_to_track:
_LOGGER.debug("Checking %s", mac)
result = mac in devs
if not result:
# Could not lookup device name
if mac not in devs:
continue
if devs[mac] is None:
devs[mac] = mac
see_device(mac, devs[mac])
if track_new:
@@ -119,5 +109,4 @@ def setup_scanner(hass, config, see, discovery_info=None):
track_point_in_utc_time(hass, update_ble, dt_util.utcnow() + interval)
update_ble(dt_util.utcnow())
return True

View File

@@ -80,7 +80,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
request_rssi = config.get(CONF_REQUEST_RSSI, False)
def update_bluetooth():
def update_bluetooth(_):
"""Update Bluetooth and set timer for the next update."""
update_bluetooth_once()
track_point_in_utc_time(
@@ -111,7 +111,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Update bluetooth devices on demand."""
update_bluetooth_once()
update_bluetooth()
update_bluetooth(dt_util.utcnow())
hass.services.register(
DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth)

View File

@@ -0,0 +1,65 @@
"""
Support for Huawei LTE routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.huawei_lte/
"""
from typing import Any, Dict, List, Optional
import attr
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DeviceScanner,
)
from homeassistant.const import CONF_URL
from ..huawei_lte import DATA_KEY, RouterData
DEPENDENCIES = ['huawei_lte']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_URL): cv.url,
})
def get_scanner(hass, config):
"""Get a Huawei LTE router scanner."""
data = hass.data[DATA_KEY].get_data(config)
return HuaweiLteScanner(data)
@attr.s
class HuaweiLteScanner(DeviceScanner):
"""Huawei LTE router scanner."""
data = attr.ib(type=RouterData)
_hosts = attr.ib(init=False, factory=dict)
def scan_devices(self) -> List[str]:
"""Scan for devices."""
self.data.update()
self._hosts = {
x["MacAddress"]: x
for x in self.data["wlan_host_list.Hosts.Host"]
if x.get("MacAddress")
}
return list(self._hosts)
def get_device_name(self, device: str) -> Optional[str]:
"""Get name for a device."""
host = self._hosts.get(device)
return host.get("HostName") or None if host else None
def get_extra_attributes(self, device: str) -> Dict[str, Any]:
"""
Get extra attributes of a device.
Some known extra attributes that may be returned in the dict
include MacAddress (MAC address), ID (client ID), IpAddress
(IP address), AssociatedSsid (associated SSID), AssociatedTime
(associated time in seconds), and HostName (host name).
"""
return self._hosts.get(device) or {}

View File

@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==2.1.0']
REQUIREMENTS = ['librouteros==2.1.1']
MTK_DEFAULT_API_PORT = '8728'

View File

@@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==2.0.0']
REQUIREMENTS = ['netdisco==2.1.0']
DOMAIN = 'discovery'
@@ -48,6 +48,7 @@ CONFIG_ENTRY_HANDLERS = {
SERVICE_DECONZ: 'deconz',
'google_cast': 'cast',
SERVICE_HUE: 'hue',
SERVICE_IKEA_TRADFRI: 'tradfri',
'sonos': 'sonos',
}
@@ -55,7 +56,6 @@ SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
SERVICE_NETGEAR: ('device_tracker', None),
SERVICE_WEMO: ('wemo', None),
SERVICE_IKEA_TRADFRI: ('tradfri', None),
SERVICE_HASSIO: ('hassio', None),
SERVICE_AXIS: ('axis', None),
SERVICE_APPLE_TV: ('apple_tv', None),

View File

@@ -15,7 +15,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, \
EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['sucks==0.9.1']
REQUIREMENTS = ['sucks==0.9.3']
_LOGGER = logging.getLogger(__name__)
@@ -59,8 +59,9 @@ def setup(hass, config):
_LOGGER.debug("Ecobot devices: %s", devices)
for device in devices:
_LOGGER.info("Discovered Ecovacs device on account: %s",
device['nick'])
_LOGGER.info(
"Discovered Ecovacs device on account: %s with nickname %s",
device['did'], device['nick'])
vacbot = VacBot(ecovacs_api.uid,
ecovacs_api.REALM,
ecovacs_api.resource,
@@ -74,7 +75,7 @@ def setup(hass, config):
"""Shut down open connections to Ecovacs XMPP server."""
for device in hass.data[ECOVACS_DEVICES]:
_LOGGER.info("Shutting down connection to Ecovacs device %s",
device.vacuum['nick'])
device.vacuum['did'])
device.disconnect()
# Listen for HA stop to disconnect.

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