Compare commits

..

286 Commits
0.49 ... 0.51

Author SHA1 Message Date
Paulus Schoutsen
adbcbe3a67 Merge pull request #8919 from home-assistant/release-0-51
0.51
2017-08-12 11:31:30 -07:00
Martin Hjelmare
eef3dda1e9 Fix SET_TEMPERATURE_SCHEMA in climate component (#8879)
* Require either temperature or high/low target temperatures.
* Add tests.
2017-08-12 10:57:10 -07:00
William Scanlon
08899ade00 Update python-wink version to fix Dome water valve bug. (#8923) 2017-08-12 10:57:10 -07:00
Philipp Schmitt
5814fdadd0 Update roombapy to 1.3.1 to avoid installing all the mapping dependencies (#8925) 2017-08-12 10:57:09 -07:00
cribbstechnologies
daf7d9ea7f fixing emulated hue issue and testing it (#8928)
* fixing emulated hue issue and testing it

* fixing hound issues

* I should probably stop using vim

* Check against dict directly instead of items.
2017-08-12 10:57:09 -07:00
Martin Hjelmare
956543ae1e Remove not needed call to update (#8930)
* This will ensure no I/O in entity properties.
2017-08-12 10:57:09 -07:00
Fabian Affolter
8523aaca64 Prepare for release 2017-08-10 23:26:19 +02:00
Fabian Affolter
e3236d1a3b Honor PEP8 naming convention (#8909)
* Honor PEP8 naming convention

* Update validator
2017-08-10 19:31:28 +02:00
Marcus Schmidt
d7e8616651 Added possibilities to use template in the command_line sensor (#8505)
* Added possibilities to use template in the command_line sensor

* Minor style guideline conforms

* Minor style guideline conforms

* Added new test for template rendering

* Minor style guideline conforms

* Minor style guideline conforms

* Fixed failing testcases

* Fix style violations

* fix code pretty
2017-08-10 18:52:52 +02:00
Fabian Affolter
c0663bf722 Add Shodan sensor (#8902) 2017-08-10 17:27:49 +02:00
Abílio Costa
d195fd47f7 Add new device tracker for Huawei Routers. (#8488)
* Add new device tracker for Huawei Routers.

	This was tested with the HG8247H model, used by Vodafone
	Portugal for the Fiber service.

* add to .coveragerc; remove import and space

* add comments and fix lint

* rename methods

* huawei_router: add constants to scanner class

* huawei_router: remove lock; use format() in string

* huawei_router: use tupple instead of member only class

* huawei_router: reduce min scan time

* huawei_router: lint

* huawei_router: lint

* huawei_router: add missing lines in imports

* huawei_router: correctly decode string after router firmware update

* Remove things that is done on core now
2017-08-10 17:01:52 +02:00
Erik Eriksson
e84ff61d4a Support media position and media duration (will display progressbar in ui) (#8904) 2017-08-10 16:56:34 +02:00
Anders Melchiorsen
317bc10ccb LIFX: improve performance of multi-light transitions (#8873)
* LIFX: improve performance of multi-light transitions

To avoid hub overload, the light.turn_on call will change each light
sequentially.

As LIFX has no hub we can safely increase performance by starting all
light transitions concurrently.

* Improve state updates after light changes

The light.turn_on call will set a new state and then immediately read it
back. However, reading the state of a LIFX light right after a state
change can still return the old value.

To handle this situation we have previously delayed the update request a
little while to allow a potential state change to settle. Because light
updates are now run in parallel, this delay might be too short when many
lights are set at once.

This commit introduces a per-light Lock to make it explicit when the
state cannot yet be trusted.

We must then do the state update ourselves. This was already done at the
end of a long transition and that code can be reused for also doing the
update at the start of a transition.
2017-08-10 10:29:04 +02:00
William Scanlon
1cb42087f9 Update simplisafe-python version (#8908) 2017-08-10 07:58:39 +02:00
karlkar
b035577cf5 Fix for Neato D3 Connected state obtaining (#8817) 2017-08-09 23:22:08 +02:00
Paulus Schoutsen
55c84eaee3 Update frontend 2017-08-09 00:47:29 -07:00
kfcook
eb6017e16c added support for setting/getting position of lutron caseta covers (#8898) 2017-08-09 06:57:32 +02:00
PhracturedBlue
19ee3c42b6 Add longer text strings to mailbox demo to test string truncation (#8893)
* Add longer text strings to mailbox demo to test string truncation in frontend

* Remove lorem ipsum txt file

* Use format instead of %
2017-08-08 23:37:16 +02:00
Aaron Bach
af70054692 Changed Pi-hole graphs from stacked bar to line (#8896) 2017-08-08 22:57:35 +02:00
Fabian Affolter
be94f6e939 Do not call update() in constructor (#8892) 2017-08-08 22:36:59 +02:00
Fabian Affolter
f513f6271e Do not call update() in constructor (#8878)
* Do not call update() in constructor

* Fix lint issues
2017-08-08 20:21:33 +02:00
Alexey
588b36dff2 Fix media_extractor for some sites (#8887) 2017-08-08 15:21:32 +02:00
Fabian Affolter
cc5893ed8b Upgrade youtube_dl to 2017.8.6 (#8880) 2017-08-08 11:53:19 +02:00
Fabian Affolter
124a6cc8c0 Change level (#8883) 2017-08-08 11:53:04 +02:00
Fabian Affolter
0fe4245620 Allow usage of colorlog 3.0.1 (#8885) 2017-08-08 10:16:04 +02:00
Aaron Bach
289c88ff71 Add RainMachine switch platform (#8827)
* Add RainMachine switch platform

* Updated requirements_all.txt

* Cleaning up CI and coverage results

* Small update to deal with older pylint

* Fixed small indentation-based error

* Added some more defensive try/except logic around calls

* I'm not a fan of importing a library multiple times :)

* Making PR-requested changes

* Fixed ref to positional parameter

* Attempting to fix broken linting

* Ignoring no-value-for-parameter pylint error
2017-08-08 09:49:25 +02:00
Fabian Affolter
57f3bed465 Do not call update() in constructor (#8881) 2017-08-08 06:52:27 +02:00
Oleksii Serdiuk
62e86270e6 RFLink: Add send_command service (#8876)
Add an optional extended description…
2017-08-07 17:37:30 +02:00
Philipp Schmitt
3aceca9d8a Add nuki lock'n'go and unlatch services and add attributes (#8687)
* Add lock'n'go service

* Add unlatch service

* Implement changes requested by @MartinHjelmare

* Fix service domain
2017-08-07 14:58:31 +02:00
Philipp Schmitt
e81b3f7bc0 Implement Roomba fan speed (#8863)
* Implement Roomba fanspeed

* Fix: fan_speed_list is always empty

* Log instead of raising an exception when incorrect fan speed has been provided

* Don't attempt to set any preference if fan speed is invalid
2017-08-06 23:43:33 +02:00
Andy Castille
cc6c2bf25e Fix spelling error and update link (#8869) 2017-08-06 21:18:44 +02:00
Paulus Schoutsen
9575cbde09 Consolidate config panels (#8857)
* Remove automation panel registration

* Move Z-Wave config API to config.zwave

* Remove no longer needed test

* Lint

* Update frontend
2017-08-06 12:05:34 -07:00
Paulus Schoutsen
4e79517971 Update mailbox panel icon 2017-08-06 11:51:58 -07:00
Tim Lyakhovetskiy
4ec4cfc44e Add Leviton Decora Smart WiFi Device Platform (#8529)
* Add Leviton Decora Smart WiFi Device Platform

* Decora WiFi Code Review Fixes
2017-08-06 11:30:28 -07:00
PhracturedBlue
d74f4eaf52 Add Initial Mailbox panel and sensor (#8233)
* Initial implementation of Asterisk Mailbox

* Rework asterisk_mbox handler to avoid using the hass.data hash.  Fix requirements.

* Handle potential asterisk server disconnect.  bump asterisk_mbox requirement to 0.4.0

* Use async method for mp3 fetch from server

* Add http as dependency

* Minor log fix. try to force Travis to rebuild

* Updates based on review

* Fix error handling as per review

* Fix error handling as per review

* Refactor voicemail into mailbox component

* Hide mailbox component from front page

* Add demo for mailbox

* Add tests for mailbox

* Remove asterisk_mbox sensor and replace with a generic mailbox sensor

* Fix linting errors

* Remove mailbox sensor.  Remove demo.mp3.  Split entity from platform object.

* Update mailbox test

* Update mailbox test

* Use events to indicate state change rather than entity last-updated

* Make mailbox platform calls async.  Fix other review concerns

* Rewrite mailbox tests to live at root level and be async.  Fixmailbox dependency on http

* Only store number of messages not content in mailbox entity
2017-08-06 11:19:47 -07:00
Paulus Schoutsen
5696e38dd6 Warn instead of raise on duplicate YAML key (#8834)
* Warn instead of raise on duplicate key

* Update test_yaml.py

* Lint

* Change to error
2017-08-06 10:47:19 -07:00
Eugenio Panadero
c6aaacbb08 Add new service clean_spot to vacuums (#8862)
* Add new service `clean_spot` to vacuums

    - Add as base component service, with associated support flag to make it optional
    - Implement on Demo vacuum
    - Implement on Xiaomi vacuum
    - Update tests for platforms Demo and Xiaomi
    - Change default icon for vacuums to `mdi:roomba`, but keep the one for the Xiaomi
    - (In a polymer PR: add new service to command toolbar in the 'more-info' card)

* Add `clean_spot` service description

* fix default properties for vacuum component
2017-08-06 10:23:22 -07:00
Fabian Affolter
d8ca04a4bc Do not call update() in constructor (#8859) 2017-08-06 10:21:55 -07:00
Paulus Schoutsen
ac9c1235bb Allow get local ip to work without internet (#8855) 2017-08-06 09:15:17 -07:00
Andrey Kupreychik
c49cce7243 Do not use pychromecast.Chromecast for Cast Groups (#8786)
* Do not use pychromecast.Chromecast for Cast Groups

pychromecast.Chromecast creates Chromecast instance with friendly_name and cast_type of the device and not of a group.
Which leads to collisions

* Update cast.py

* using hass.data

* Fixed and extended tests

* Line length in tests

* Lint in tests
2017-08-06 09:15:01 -07:00
John Arild Berentsen
99a20c845c Fix off_delay for zwave trigger sensors (#8864) 2017-08-06 18:31:32 +03:00
Kevin Fronczak
3723f67dc1 Added rounding to Google Wifi (#8866) 2017-08-06 18:29:52 +03:00
Fabian Affolter
b655fe6e04 Allow to set coordinates (#8858) 2017-08-06 15:20:51 +02:00
Fabian Affolter
24e9fa238a Upgrade pyasn1 to 0.3.2 and pyasn1-modules to 0.0.11 (#8856) 2017-08-06 15:20:13 +02:00
Charles Blonde
83afd12807 Add support to Dyson 360 Eye robot vacuum using new vacuum platform (#8852)
* Add support to Dyson 360 Eye robot vacuum using new vacuum platform

* Fix tests with Python 3.5

* Code review

* Code review - v2

* Code review - v3
2017-08-06 13:08:46 +02:00
Philipp Schmitt
82a7dffc03 Wi-Fi enabled Roomba support (#8825)
* Roomba vacuum component

* Update requirements and coveragerc

* Update error handling message

* Implement changes requested by @azogue

* Add missing import

* Don't wrap commands with functools.partial

* Refactoring

* Remove state attribute and use double quotes for log messages strings

* Remove unused constants

* Sorting

* Sorting + remove None arg from dict.get() calls

* Re-sort imports
2017-08-06 11:08:45 +02:00
Fabian Affolter
c11b6798dc Upgrade pylast to 1.9.0 (#8854) 2017-08-06 10:08:45 +02:00
Fabian Affolter
8e4c799ad1 Upgrade sqlalchemy to 1.1.13 (#8850) 2017-08-06 10:08:24 +02:00
Fabian Affolter
5059d4c54b Catch ConnectionRefusedError (#8844)
* Do not call update() in constructor

* Catch ConnectionRefusedError
2017-08-06 10:08:00 +02:00
Fabian Affolter
569d9764ab Do not call update() in constructor (#8847) 2017-08-06 10:07:45 +02:00
Fabian Affolter
058deb5be3 Make 'monitored_conditions' optional (#8848)
* Do not call update() in constructor

* Update tests
2017-08-06 10:07:22 +02:00
Fabian Affolter
cd36a71f64 Do not call update() in constructor (#8849)
* Do not call update() in constructor

* Fix pylint issues
2017-08-06 10:07:05 +02:00
Fabian Affolter
6832a2e642 Make 'monitored_conditions' optional (#8843)
* Do not call update() in constructor

* Make 'monitored_conditions' optional

* Update tests
2017-08-06 10:05:37 +02:00
Fabian Affolter
2c7b2fe19e Do not call update() in constructor (#8840) 2017-08-06 10:03:57 +02:00
Fabian Affolter
45ec7f6180 Upgrade sendgrid to 4.2.1 (#8839) 2017-08-06 10:03:32 +02:00
Fabian Affolter
cb8517834a Do not call update() in constructor. (#8837) 2017-08-06 10:03:09 +02:00
Jeroen ter Heerdt
f41ef5d727 Egardia (#8389)
* Added support for Egardia / Woonveilig alarm control panel

* Added support for Egardia / Woonveilig alarm control panel

* Added support for Egardia / Woonveilig alarms

* Updating egardia support with exception handling and other fixes

* Egardia platform, requirements file updated

* Fixing state checking

* Adding exception handling

* Removing unnecessary logging

* Removing unnecessary logging

* Updating to egardiadevice component 1.0.10

* Improving exception handling

* Adding implementation of egardiaserver for alarm triggered status

* Clean-up

* Fix my previous change
2017-08-05 22:04:00 +02:00
Eugenio Panadero
a221b10694 Update xiaomi vacuum tests and include in coverage (#8845)
* Fix tests for Demo vacuum platform (and increase coverage)

* increase coverage of xiaomi vacuum tests and include in coverage

Also little fixes

* remove print statement
2017-08-05 21:45:59 +02:00
Greg Laabs
6e1785173f History query and schema optimizations for huge performance boost (#8748)
* Add DEBUG-level log for db row to native object conversion

This is now the bottleneck (by a large margin) for big history queries, so I'm leaving this log feature in to help diagnose users with a slow history page

* Rewrite of the "first synthetic datapoint" query for multiple entities

The old method was written in a manner that prevented an index from being used in the inner-most GROUP BY statement, causing massive performance issues especially when querying for a large time period.

The new query does have one material change that will cause it to return different results than before: instead of using max(state_id) to get the latest entry, we now get the max(last_updated). This is more appropriate (primary key should not be assumed to be in order of event firing) and allows an index to be used on the inner-most query. I added another JOIN layer to account for cases where there are two entries on the exact same `last_created` for a given entity. In this case we do use `state_id` as a tiebreaker.

For performance reasons the domain filters were also moved to the outermost query, as it's way more efficient to do it there than on the innermost query as before (due to indexing with GROUP BY problems)

The result is a query that only needs to do a filesort on the final result set, which will only be as many rows as there are entities.

* Remove the ORDER BY entity_id when fetching states, and add logging

Having this ORDER BY in the query prevents it from using an index due to the range filter, so it has been removed.

We already do a `groupby` in the `states_to_json` method which accomplishes exactly what the ORDER BY in the query was trying to do anyway, so this change causes no functional difference.

Also added DEBUG-level logging to allow diagnosing a user's slow history page.

* Add DEBUG-level logging for the synthetic-first-datapoint query

For diagnosing a user's slow history page

* Missed a couple instances of `created` that should be `last_updated`

* Remove `entity_id` sorting from state_changes; match significant_update

This is the same change as 09b3498f410106881fc5e095c49a8d527fa89644 , but applied to the `state_changes_during_period` method which I missed before. This should give the same performance boost to the history sensor component!

* Bugfix in History query used for History Sensor

The date filter was using a different column for the upper and lower bounds. It would work, but it would be slow!

* Update Recorder purge script to use more appropriate columns

Two reasons: 1. the `created` column's meaning is fairly arbitrary and does not represent when an event or state change actually ocurred. It seems more correct to purge based on the event date than the time the database row was written.
2. The new columns are indexed, which will speed up this purge script by orders of magnitude

* Updating db model to match new query optimizations

A few things here: 1. New schema version with a new index and several removed indexes
2. A new method in the migration script to drop old indexes
3. Added an INFO-level log message when a new index will be added, as this can take quite some time on a Raspberry Pi
2017-08-04 23:16:53 -07:00
Fabian Affolter
52cff83267 Upgrade aiohttp to 2.2.5 (#8828) 2017-08-04 23:14:05 -07:00
Paulus Schoutsen
e49b970665 Block dependencies that depend on enum34 (#8698)
* Block dependencies that depend on enum34

* Remove uninstalling enum34

* Update validation script

* Add constraints to tox.ini

* Upgrade yeelight to version that uses enum-compat

* Disable sensor.skybeacon

* Lint
2017-08-04 23:06:10 -07:00
Paulus Schoutsen
a0530d8b9c Update frontend 2 2017-08-04 23:02:07 -07:00
Paulus Schoutsen
99d4021f47 Update frontend 2017-08-04 22:58:19 -07:00
Charles Blonde
7f0d0607f1 Fix Dyson sensors if devices are configured without standby monitoring. Fixes #8569 (#8826)
Upgrade libpurecoolink libraries without unused enum34 dependency
2017-08-04 14:27:23 -07:00
Hellowlol
cf298c2435 Make HA discover sabnzbd and add it to the Configurator (#8634)
* Init discover sab.

* Fix hound errors

Nobody likes being hound at :(

* sabnzbd discovery says if ssl is active.

* Fixups after codereview.
2017-08-04 23:24:55 +02:00
Boyi C
77cdc833f0 Update yweather.py (#8820)
Fix missing weather unit support.
Move some weather code to their correct classes.
2017-08-04 17:22:38 +02:00
Eugenio Panadero
96f8c37dcd Xiaomi vacuum as platform of new vacuum component derived from ToggleEntity, and services (#8623)
* Xiaomi vacuum as component with switch, sensors and services

- Conversion from switch platform to async component.
- Add services proposed in #8416 to the new component, with shorter names.
- Add sensors for the vacuum robot as a selectable list from `battery`, `state`, `error`, `fanspeed`, `clean_time` and `clean_area` (the state attributes of the switch). The sensors don't poll, but listen to a signal to update the state, the switch fires this signal when updating.
- Assign default icons to sensors and the switch (`mdi:google-circles-group` looks like the robot!)

* path change in requirements_all (from switch platform to component)

* copy pasting is a bad habit

* services to the components services.yaml, modify .coveragerc

* review: use with multiple hosts, fix calls to async_add_devices, fix ranges for services

* `icon_for_battery_level` util method

* Xiaomi vacuum as platform of new component vacuum

- Created new component `vacuum` from a ToggleEntity.
- Add services `turn_on`, `turn_off`, `cleaning_play_pause`, `stop`, `return_to_base`, `locate`, `set_fanspeed` and `send_command`.
- Remove the main switch for the xiaomi vacuum (the toggable main entity is the switch).
- Add `support flags` for the common services
- Assign default icons to sensors and the switch (`mdi:google-circles-group` looks like the robot!)
- Move services descriptions to a yaml file for the new component.
- Update requirements_all.
- Update coveragerc.

* fix coveragerc

* fix battery icon helper to use more icons

* remove sensors, create properties and support flags for custom UI

* cleaning

* updated state_attrs for filtering in UI, renamed platform to simply `xiaomi`

* fix platform rename

* change fanspeed and expose `fanspeed_list` to use speed steps

* minor fixes

- Rename service `start_pause`
- Add 'Error' attribute only if `got_error`.
- Minor changes

* rename state attrs

* rename state attrs

* review changes: cut fan__speed, style changes, remove logging, and more

* add ATTR_COMMAND = 'command' to const

* pop entity_id from service data

* remove property accessor for vacuum object

* lint fix

* fix extra attrs names

* module level functions for calling the services

* params as optional keyword for `send_command`

* params as optional keyword for `send_command`, remove debug logs

* explicit parameters for `set_fan_speed` and `send_command`

* Demo platform for the vacuum component

* vacuum tests for the Demo platform

* some fixes

* don't omit vacuum

* vacuum tests for the Xiaomi platform

* fix test

* fix

* fix xiaomi test

* fix coveragerc

* test send command

* fix coveragerc

* fix string formatting

* The coverage is to low. It need 93% or more
2017-08-04 15:27:10 +02:00
Julian Kahnert
5b4e30cde3 geizhals sensor component (#8458)
* initial create of the geizhals component

* only .coveragerc, geizhals.py, and requirements_all.txt included
2017-08-04 12:11:33 +02:00
Paulus Schoutsen
d4dfb4d80c Polymer 2 (#8815)
* Update build for Polymer 2

* Update webcomponents polyfills/helpers

* Load ES5 class adapter when not in dev mode

* Update frontend
2017-08-03 23:46:57 -07:00
Andrey
c895f1f1db When Sonos gets a tts source - dont't show an image (#8777) 2017-08-03 17:39:11 +03:00
Haim Gelfenbeyn
944af9cd7d InfluxDB component improvements (#8633)
* Allow reporting some state attributes as tags to InfluxDB

Some state attributes should really be tags in InfluxDB. E.g.
it is helpful to be able to group by friendly_name, or add a custom
attribute like "location" and group by that. Graphs in Grafana are much
easier to read when friendly names are used, and not node ids.

This commit adds an optional setting to InfluxDB config:
'tags_attributes'. Any attribute on this list will be reported as tag
and not as field to InfluxDB.

* Allow overriding InfluxDB measurement for each reported item separately

Bundling all items with the same "unit of measurement" together does not
always makes sense. For example, both "relatively humidity" and "battery
level" are reported as "%", but I'd rather see them as separate
measurements in InfluxDB. This commit allows for 'influxdb_measurement'
attribute. When set on node, it will take precedence over the global
'override_measurement' and component-specific 'unit_of_measurement'.

* Minor updates to InfluxDB component improvements, as suggested by
@MartinHjelmare.

* Moved per-component config from 'customize' into 'influxdb'
configuration section. The following three sub-sections were added:
'component_config', 'component_config_domain' and
'component_config_glob'. The sole supported per-component attribute
at this point is 'override_measurement'.

* Lint

* Fixed mocked entity_ids in InfluxDB tests to be in domain.entity_id
format, to satisfy EntityValues requirements.

* Added tests for new InfluxDB configuration parameters

* Fixes to some docstrings
2017-08-03 16:26:01 +02:00
John Mihalic
f3e16ca304 Catch divide by zero errors when a sleep type is 0 (#8809)
Add an optional extended description…
2017-08-03 15:58:40 +02:00
Fabian Affolter
6de38cb941 Upgrade aiohttp to 2.2.4 (#8805) 2017-08-03 11:37:02 +02:00
Pascal Vizeli
8e51e66c9b Update numpy 1.13.1 (#8806)
* Update opencv.py

* Update requirements_all.txt
2017-08-03 11:36:50 +02:00
Matthew Treinish
57dfe378a1 Add mochad light component (#8476)
* Add mochad light component

This commit adds a new component to control x10 dimmers/lights with
mochad.

* Create comm_type and address constants

The comm_type and address conf constants are shared between all mochad
devices because they are required information used for configuring a
device. This commit moves the definition into const.py so they're
consistent between all component types.
2017-08-03 10:51:01 +02:00
Fabian Affolter
d8cded637c Revert "Upgrade aiohttp to 2.2.4"
This reverts commit 7c92f7e1ad.
2017-08-03 10:11:32 +02:00
Fabian Affolter
7c92f7e1ad Upgrade aiohttp to 2.2.4 2017-08-03 10:08:09 +02:00
Abílio Costa
ccf0559059 mqtt switch: add voluptuous for availability topic (#8797) 2017-08-03 07:18:18 +02:00
Paulus Schoutsen
2d38e70268 Merge branch 'polymer-build' into dev 2017-08-02 21:34:20 -07:00
Paulus Schoutsen
9dae1ca5c2 Update frontend 2017-08-02 21:34:04 -07:00
Luuk
6ac8caa857 Fix referencing unset variable in tado climate component (causes update to fail when tado zone is in manual mode) (#8723)
Add an optional extended description…
2017-08-02 15:07:03 +02:00
Fabian Affolter
39131d06ba Improvements (configuration and validation) (#8785) 2017-08-02 14:51:09 +02:00
Fabian Affolter
8a626e1572 Upgrade sphinx-autodoc-typehints to 1.2.1 (#8783)
Add an optional extended description…
2017-08-02 14:15:00 +02:00
Fabian Affolter
bc376f7045 Upgrade pyasn1 to 0.3.1 and pyasn1-modules to 0.0.10 (#8787) 2017-08-02 14:14:01 +02:00
Paulus Schoutsen
cad1de790e Build frontend with polymer-build 2017-08-02 01:46:08 -07:00
Sebastian Muszynski
32b7f4d16f Fixes UnboundLocalError: local variable 'setting' referenced before assignment (#8782) 2017-08-02 09:14:28 +02:00
Steve Rhoades
1adb5040e7 Feature alexa launch request (#8730)
* Add support for LaunchRequest alexa intent

* Support LaunchRequest for multiple skills

* formatting

* adding tests to cover launch request

* formatting
2017-08-01 22:53:36 -07:00
Lukas Barth
47dad547eb Add 'forecast' ability to yr weather sensor (#8650)
* Add forecast option to YR sensor

* Fix some style issues

* Fix linting
2017-08-01 22:42:51 -07:00
thrawnarn
86c06ad76e New component: bluesound (#7192)
* New component: bluesound

* New component: bluesound

* Removed response.release()
Fixed update_sync_status bug
Changed should_poll to True

* Fix lint error

* Changes to init

* Fixed blank line

* updated requirements

* bump to xmltodict 0.11.0
2017-08-01 22:41:51 -07:00
pezinek
7dbcf63543 flux_led: support for property "available" (#8764)
* flux_led: support for property "available"

* Implemented changes from code review

* Implemented changes from code review

* Implemented changes from code review
2017-08-01 21:26:27 -07:00
Thomas Friedel
6ff340492b use updated osram lightify 1.0.6 component, including bugfix allowing more than 27 devices (#8774) 2017-08-01 20:36:31 +02:00
Fabian Affolter
50cd6c9a9c Catch exception (fixes #8724) (#8731) 2017-08-01 19:30:26 +02:00
Dan Sarginson
365f21b209 Honeywell fixes and improvements (#8756)
* Honeywell fixes and improvements

Give the Honeywell device a state ('On', 'Off', etc) that
can be displayed to user and understood by other components.
Previously this was always 'Unknown'. Update also raises a
state_changed event when a new temperature is polled.

These two together fix an issue (#8688) where Honeywell
climate data couldn't be logged in InfluxDB.

* Roll back some changes

These were not necessary to achieve the result I wanted.

* Renamed RoundThermostat's 'device' member for greater clarity

Now called 'client'

* Improve and simplify discovering thermostat mode

Per code review, this is a rather neater way to discover the thermostat mode

* Update tests for compatibility with new component

The tests previously relied upon the update() method being
called in the constructor. This is no longer the case.

* Address formatting review comment

Parens not necessary

* This system mode is not certain to apply to domestic hot water

Moved the mode lookup to only happen on update of radiator devices,
since hot water devices seem to be treated differently and I can't test.
2017-08-01 16:18:14 +02:00
Tsvi Mostovicz
075422e7ad Add support for file attachments in pushbullet (#8763)
* Add support for file attachments in pishbullet

* Check filepath is allowed
2017-08-01 14:55:46 +02:00
Steven Looman
342ec8ec99 mpd improvements (#8655)
* Don't require the MPD device to online during HASS startup

* Hide private variables

* Keep tox/flake8 happy

* Fix typo

* Force direct update

* Implement MpdDevice.available

* Fix typo
2017-07-31 23:18:26 -07:00
Matt Colyer
2b59b917c4 Allow sonos to select playlists as a source (#8258)
* Allow sonos to select playlists as a source

Most of this was taken from
https://github.com/home-assistant/home-assistant/issues/5598#issuecomment-278229895
however I made a few small improvements so that it works for other
services than Spotify and it should properly switch to playing the queue
if you had another song playing previously.

/cc @PatBoud

* Attempt to fix style issues

* More indent changes

* Fix misplaced period

* Move playlist replacement to function

* Privatize replace_queue_with_playlist and explain

* Remove unneeded decorator

* Fix doc formatting
2017-07-31 23:16:05 -07:00
viswa-swami
e40388e7ad Enable/Disable Motion detection for Foscam Cameras (#8582)
* Added support to enable/disable motion detection for foscam cameras. This support was added in 0.48.1 as a generic service for cameras. Motion detection can be enabled/disabled for foscam cameras with this code-set.

* Fixed the violation identified by hound-bot

* Fixed the comment posted by HoundCI-Bot regarding using imperative mood statement for pydocstyle

* Fixed the error that travis-ci bot found.

* As per comment from @balloob, Instead of directly using the URL to talk to foscam, used a 3rd party foscam library to communicate with it. This library already has support to enable/disable motion detection and also APIs to change the motion detection schedule etc. Need to add more support in the pyfoscam 3rd party library for checking if motion was detected or even if sound was detected. Once that is done, we can add that into HASS as well.

* Lint

* Removed the requests library import which is not used anymore

* Updating requirements_all.txt based on the code-base of home assistant that i have. Generated using the gen_requirements_all.py script

* Updating requirements_all.txt and requirements_test_all.txt generated by gen_requirements_all.py after latest pull from origin/dev

* Updated requirements_all.txt with script

* Updated the foscam camera code to fix lint errors

* Fixed houndci violation
2017-07-31 23:14:34 -07:00
William Scanlon
cb292a0b18 Wink discovery (#8739)
* Support for Wink discovery

* Switched try/except for if/else
2017-07-31 20:54:07 -07:00
Martin Hjelmare
33663f9502 Clean up remote component (#8728)
* Clean up remote component

* Don't have device be required in send_command service and method.
* Don't have entity_id be required in the base service schema.
* Don't always add activity in the data dict for a service call.
* Update harmony remote platform according to new service schema.
* Remove not needed properties and attributes from the Kira remote
  platform.
* Add send_command method to demo platform.
* Add tests and remove duplicate tests.

* Break out required argument as positional argument
2017-07-31 20:52:39 -07:00
emlt
e57d6f679a Change units from KW to W (#8761)
* Change units from KW to W

Change power unit from KW to W to be consistent with other energy sensors.

* Change units from kW to W
2017-07-31 20:41:45 -07:00
mjj4791
775185896a buienradar dates tz-aware (#8767) 2017-07-31 20:37:33 -07:00
Paulus Schoutsen
e6331aafb2 Merge remote-tracking branch 'origin/master' into dev 2017-07-31 18:30:40 -07:00
Paulus Schoutsen
fbb4c43353 Merge pull request #8757 from home-assistant/release-0-50-2
0.50.2
2017-07-31 18:28:14 -07:00
gwhiteCL
455ac9724a added invert_state optional parameter (#8695)
* added invert_state optional parameter

* removed superfluous parens

* moved state inversion to the is_closed method

* added relay_invert feature

* fixed syntax to comply with houndci-bot rules

* changed state_invert to invert_state and relay_invert to invert_relay
2017-07-31 20:24:21 -04:00
Alan Fischer
e6be560e00 Add toggle to remotes (#8483)
* Add toggle to remotes

* Only include activity if specified, and add service description
2017-07-31 19:46:12 +02:00
Adam Mills
59891fa838 Fix Z-Wave barrier discovery for new API (#8706) 2017-07-31 09:15:10 -07:00
Adam Mills
475ab68853 Correctly discover GE Fan Controllers (#8682) 2017-07-31 09:15:10 -07:00
Paulus Schoutsen
d3f8ad15a4 Version bump to 0.50.2 2017-07-31 09:14:07 -07:00
Sean Gollschewsky
c45fc84859 Move I/O outside of properties for light/tplink platform (#8699)
* Add new component for TPLink light bulbs.

* Update with result of gen_requirements_all.

* Add new component light.tplink.

* Move I/O outside of properties as per https://goo.gl/Nvioub.
2017-07-31 09:10:04 -07:00
Adam Mills
2a09ac017f Fix Kodi reconnection after websocket disconnect (#8704) 2017-07-31 09:10:04 -07:00
Paulus Schoutsen
f9e8d4237d Fix alexa cards (#8708) 2017-07-31 09:10:04 -07:00
Kevin Fronczak
30e16c97fc Fixed sensor issue with Google Wifi routers in bridge mode (#8710)
* Fixed issue with routers in bridge mode

- Router in brdige mode apparently don't report all of the stats
- Re-wrote the data_format function so it's a bit easier to follow and able to log keys that aren't supported by a router in a given mode
- Changed config so that it properly ignores conditions when not explicitly listed
- Added tests to check for the above and also to verify we log that a key doesn't exist rather than throwing an exception

* Mistakenly was calling MONITORED_CONDITIONS in data_format

- Changed to be the actual config values to prevent log error
2017-07-31 09:10:04 -07:00
Eugenio Panadero
0e1f664102 Retry set_webhook up to three times, reduce timeout to 5s again (#8716) 2017-07-31 09:10:04 -07:00
Fabian Affolter
60ca79ce35 Supress exception if host is not available (fixes #8684) (#8732) 2017-07-31 09:10:04 -07:00
Martin Hjelmare
f576b37e9f Fix tradfri error spam (#8738)
* Catch tradfri timout exception

* Remove not needed return statement

* Remove test logging

* Log warning instead of error
2017-07-31 09:10:04 -07:00
Nathan Henrie
592f9901f9 Fix typo (#8754) 2017-07-31 09:10:04 -07:00
Sean Gollschewsky
7991e2df5f Fix brightness issue #8744. (#8755) 2017-07-31 09:10:04 -07:00
Paulus Schoutsen
91b062f9b7 Update frontend 2017-07-31 09:06:50 -07:00
Sean Gollschewsky
9919eec596 Fix brightness issue #8744. (#8755) 2017-07-31 09:02:04 -07:00
Nathan Henrie
e525d13a5d Fix typo (#8754) 2017-07-31 09:00:09 -07:00
Martin Hjelmare
ce67be2fff Fix tradfri error spam (#8738)
* Catch tradfri timout exception

* Remove not needed return statement

* Remove test logging

* Log warning instead of error
2017-07-31 08:58:47 -07:00
Fabian Affolter
53048f71a0 Supress exception if host is not available (fixes #8684) (#8732) 2017-07-31 08:58:13 -07:00
Martin Donlon
164e953e8c New media_player platform for Russound devices using the RIO protocol (#8448)
* New media_player platform for Russound devices using the RIO protocol
Auto discovers zones and sources
Handles media metadata from sources that support it
asyncio implementation
Push updates for any zone or source changes so no polling required.

* Fixed up linting issues

* Addressing PR feedback

Updated russound_rio dependency to 0.1.3
Use enumerate_zones and enumerate_sources methods instead of doing it in
the platform.
Register callbacks in async_added_to_hass coroutine
Corrected behavior of async methods
2017-07-31 14:42:55 +02:00
David McNett
7156e4782e python-insteonplm module version bump (#8736)
Requiring python-insteonplm v0.7.5 (up from 0.7.4) now
2017-07-31 14:33:51 +02:00
Eugenio Panadero
37fef4016e Add proxy support for telegram_bot (#8717)
* Add proxy support for telegram_bot

New optional config parameters `proxy_url` and `proxy_params` (a dict)
```yaml
telegram_bot:
  platform: polling
  api_key: !secret telegram_bot_api_key
  allowed_chat_ids:
    - !secret telegram_bot_chatid
  proxy_url: socks5://proxy_ip:proxy_port
  proxy_params:
    username: my-username
password: my-secret-password
```

* change `ATTR_` for `CONF_` for config params
2017-07-30 12:08:19 +02:00
Eugenio Panadero
cee49f313f Retry set_webhook up to three times, reduce timeout to 5s again (#8716) 2017-07-30 11:14:28 +02:00
Eugenio Panadero
05330ac763 bump python-telegram-bot to 7.0.1 for fully support Bot API 3.2 (#8715) 2017-07-30 11:13:51 +02:00
Nicholas Sielicki
6884965c80 directv: add configuration glue for Genie slaves (#8713)
DirectPy, the third party library used for controlling directv boxes,
has the ability to accept an ID in order to act as a remote for Genie
slaves instead of just the master directv box. This commit adds glue
such that one can configure home assistant to interface with these slave
genie boxes.

Signed-off-by: Nicholas Sielicki <sielicki@yandex.com>
2017-07-30 10:17:56 +02:00
Adam Mills
e992527c68 Fix Kodi reconnection after websocket disconnect (#8704) 2017-07-29 21:55:08 -07:00
Sean Gollschewsky
431a381c8d Move I/O outside of properties for light/tplink platform (#8699)
* Add new component for TPLink light bulbs.

* Update with result of gen_requirements_all.

* Add new component light.tplink.

* Move I/O outside of properties as per https://goo.gl/Nvioub.
2017-07-29 21:53:37 -07:00
Paulus Schoutsen
22088d192a Fix alexa cards (#8708) 2017-07-29 21:52:26 -07:00
Kevin Fronczak
418a8bab11 Fixed sensor issue with Google Wifi routers in bridge mode (#8710)
* Fixed issue with routers in bridge mode

- Router in brdige mode apparently don't report all of the stats
- Re-wrote the data_format function so it's a bit easier to follow and able to log keys that aren't supported by a router in a given mode
- Changed config so that it properly ignores conditions when not explicitly listed
- Added tests to check for the above and also to verify we log that a key doesn't exist rather than throwing an exception

* Mistakenly was calling MONITORED_CONDITIONS in data_format

- Changed to be the actual config values to prevent log error
2017-07-29 21:50:02 -07:00
Adam Mills
a94e7ec25d Fix Z-Wave barrier discovery for new API (#8706) 2017-07-30 00:40:56 -04:00
Adam Mills
8ac63fd70c Remove deprecated sensor_class config options (#8702) 2017-07-29 19:46:27 -04:00
Adam Mills
8ba9e8016b Remove deprecated substitute interfaces (#8701) 2017-07-29 19:18:06 -04:00
Adam Mills
750ea44b4b Remove deprecated host and ssl logic from Kodi (#8700) 2017-07-29 19:17:41 -04:00
Paulus Schoutsen
5876d6766d Version bump to 0.50.1 2017-07-29 15:11:45 -07:00
Paulus Schoutsen
78428b0acd Version bump to 0.51.0.dev0 2017-07-29 13:33:52 -07:00
Paulus Schoutsen
72db28abac Merge remote-tracking branch 'origin/master' into dev 2017-07-29 13:33:15 -07:00
Paulus Schoutsen
e13fd05e7d Merge pull request #8685 from home-assistant/release-0-50
0.50
2017-07-29 13:28:22 -07:00
Adam Mills
80ab02c3e8 Correctly discover GE Fan Controllers (#8682) 2017-07-29 16:24:15 -04:00
Paulus Schoutsen
a760673ad6 Persist shopping list + clear completed (#8697) 2017-07-29 12:22:52 -07:00
Paulus Schoutsen
0bde0a6f3a Persist shopping list + clear completed (#8697) 2017-07-29 12:22:38 -07:00
Paulus Schoutsen
12dec93565 Update frontend 2017-07-29 12:18:50 -07:00
William Scanlon
c376bc2e45 Support for Wink local control (#8607)
* Support for Wink local control
2017-07-29 10:50:56 -07:00
Paulus Schoutsen
f0e5f68865 Shopping List: edit name / complete status (#8666)
* Shopping List: edit name / complete status

* Change ID to be UUID based
2017-07-29 10:50:56 -07:00
Paulus Schoutsen
56f4486e0b Update frontend 2017-07-29 10:45:14 -07:00
kfcook
d1b73a96f4 Added Lutron Caseta Scene Support (#8690)
* added scene support

* Updated pylutron_caseta version number

* Updated pylutron_caseta version number

* Fixed lint errors

* fix style
2017-07-29 18:07:28 +02:00
Paulus Schoutsen
1749859cdf Shopping List: edit name / complete status (#8666)
* Shopping List: edit name / complete status

* Change ID to be UUID based
2017-07-29 17:48:09 +02:00
Fabian Affolter
931f4d8161 Upgrade mypy to 0.521 (#8692) 2017-07-28 23:22:56 -07:00
Fabian Affolter
61508deed3 Upgrade pushbullet.py to 0.11.0 (#8691)
* Upgrade pushbullet.py to 0.11.0

* Update sensor as well
2017-07-28 23:22:35 -07:00
Paulus Schoutsen
828c469ef7 Fix Lint 2017-07-28 20:53:15 -07:00
William Scanlon
1b57566e8e Support for Wink local control (#8607)
* Support for Wink local control
2017-07-28 12:02:16 -04:00
Paulus Schoutsen
0a6d519b9d Merge remote-tracking branch 'origin/master' into release-0-50 2017-07-28 02:42:58 -07:00
Paulus Schoutsen
0c97fe7eac Version bump to 0.50 2017-07-28 02:38:06 -07:00
Chris
e8ce41874c Fix COMMAND_CLASS_BARRIER_OPERATOR for dev branch of OpenZwave (#8574)
* Update zwave.py to work with updated OpenZwave library

Update zwave.py to work with updated OpenZwave library

* Update zwave.py

* Update zwave.py

* Update to fix garage door openers

Update to fix garage door support for latest version of openzwavelib

* Update to cover.zwave list of states

Update to cover.zwave to provide list of states based on dev version of
openzwave lib

* Some values not saved

* Formatting fix

* Formatting fix

* Variable typo

* Formatting fix

* Formatting

* Variable Update

Variable Update and properties added

* Formatting fixes

* Formatting Fix

* Update test case for door states

* Formatting / Testing process fix

* Formatting

* Formatting / Test Fixes

* Variable rename

* Added members to CoverDevice

* Removed un-needed else

* Formatting

* Formatting

* Variable name changes and const updates

* Changed variable names to cover_state
* Added constains into const.py
* Updated to change the main state on the cover device

* Fixes

* Formatting fixes

* Formatting/Variables

* Formatting

* Variable fixes

* Import update

* Formatting  / Variables

* Update test for new states

* Revert state changes

* Test fix

* Variable Fix

* Formatting

* Variable typo

* Missing constant

* Variable fix

* Requested changes

* Added is_opening
* Added is_closing
* Updated test based on changes

* Formatting

* Changed cover_state back to _state

* Formatting and variable fixes

* Test fixes

* Formatting and variable touchup

* Formatting

* Optimizations

* Add new cover features to demo

* Add tests for demo cover closing/opening

* Remove unused STATE_STOPPED

* Add tests for new zwave cover values
2017-07-27 18:57:30 -04:00
Brian Gehrich
0ab0e35d59 Updated pysnmp to 4.3.9 (#8675) 2017-07-27 22:33:17 +02:00
Pascal Vizeli
51108b8fe9 Hass.io: logo support / timeout handling (#8668)
* Disable auth on logo / no timeout for addons update/install

* fix tests
2017-07-27 12:23:22 -07:00
Daniel Perna
9e6817b6d0 Upgrade pyhomematic to 0.1.30 (#8673) 2017-07-27 17:45:59 +02:00
Fabian Affolter
74581b57f8 Upgrade sqlalchemy to 1.1.12 (#8669) 2017-07-27 17:23:51 +02:00
Fabian Affolter
4fcaea23a8 Upgrade libnacl to 1.5.2 (#8670) 2017-07-27 17:23:07 +02:00
Fabian Affolter
b59c29943b Upgrade fuzzywuzzy to 0.15.1 (#8671) 2017-07-27 17:22:40 +02:00
Richard Cox
1e8c00ac02 Adding support for mapping keys to value in statsd (#8665)
* Adding ability to map specific states to values

* Adding test for mapping
2017-07-27 08:58:34 +02:00
Abílio Costa
9d5c61b2f0 MQTT Switch: add availability_topic for online/offline status (#8593)
* mqtt switch: add availability_topic for online/offline status

* fix method doc strings

* MQTT Switch: add test
2017-07-27 08:24:15 +02:00
kfcook
f5eeb252a7 Added support for SerenaHoneycombShades to Lutron Caseta (#8662)
Add an optional extended description…
2017-07-27 08:14:12 +02:00
Robin
3b4ea864a1 Add uk_transport component. (#8600) 2017-07-26 20:49:52 +01:00
Jeff Wilson
3318f02664 Add transition support to light.zha (#8548)
* Add transition support to light.zha

* Address hound formatting

* Address hound comments

Look, nobody is perfect... alright?

* Update zha.py
2017-07-26 17:22:31 +02:00
Greg Laabs
438edc5ca1 History performance improvements for single-entity requests (#8632)
* Bugfix: remove superfluous domain filter

This filter is already applied later in the function by the `filters` object, where it is conditionally applied when appropriate. This fixes the problem where we get a domain filter even when searching for a single entity_id, which needlessly harms the query's performance.

* Performance: build different query when only getting single entity

When querying the history of a single entity, we can use an entirely different method for the "synthetic zero data point" by simply sorting by date and doing a LIMIT 1. This performs thousands of times better than the multi-entity query when the current recorder_run has been going for a while.

* Add entity_id filter to single-entity request

The entity_id filter was handled inside the `filters.apply` logic which is used in most cases, BUT didn't work when no `filters` was passed in to the method. Now it'll work even if no `filters` object is passed in.

* Fix linting errors in history.py

* Undo removal of domain filter

Putting back the domain filter that was removed in 76a6371705dcd57483e55dcc03435ae867c184d2 - there are use-cases where get_states is called without a filter object, so we need the domain filter to work in those cases as well.

* Fix truncated comment
2017-07-26 11:22:01 -04:00
Boyi C
abcfcdd887 Yahoo Weather update, supports forecast for more days (#8626)
* work on weather panel

* update yahooweather with more forecast details

* Update yweather to allow user input forecast date

* fix for houndci

* fix long line

* fix1

* Revert "work on weather panel"

This reverts commit 28b4972233de42617fb05df574de22743604edfd.

revert unintentional submodule change

* fix2

fix typo, add try catch to another int()

* fix pylint

* fix3

* fix4

* Update yweather.py

* Update yweather.py

* Remove global data construct

* Yahoo API support only 5 days forecast

* remove forecast

* fix lint

* fix lint p2

* Update yweather.py
2017-07-26 16:46:21 +02:00
Thomas Delaet
fff269e790 Velbus (#8076)
* add Velbus changes

* update library version

* fix python-velbus version

* bug fix and update python-velbus

* change config handling

* update velbus components/platforms

* add support for Velbus switches

* fix bugs

* typo

* add velbus fan

* update velbus library

* bug fix in logic of fan handling of speed settings

* add Velbus changes

change config handling

update velbus components/platforms

add support for Velbus switches

add velbus fan

* remove duplicate entry

* fix documentation links

* fix linting error

* regen requirements_all.txt

* add support for Velbus cover

* bugfix in cover component

* bugfix in cover component

* remove unused imports

* Travis fixes

* fix style

* fix style

* Update velbus.py

* Update velbus.py

* Update velbus.py

* Update requirements_all.txt

* Update velbus.py

* Update velbus.py

* Update velbus.py

* Update velbus.py

* fix style

* Update velbus.py

* Update velbus.py

* Update velbus.py

* Update velbus.py

* Update velbus.py

* Update velbus.py
2017-07-26 14:03:29 +02:00
Anders Melchiorsen
81a27e726c Upgrade aiolifx (#8648)
This release includes a fix for multizone lights with zone counts that are not
a multiple of eight.
2017-07-26 12:33:00 +02:00
Marcelo Moreira de Mello
7c120748ce Fixes Fitbit sensor to report battery level with the expected device (#8647)
Add an optional extended description…
2017-07-26 11:05:48 +02:00
Sean Gollschewsky
e83816c055 Add component Light TPLink (#8643)
* Add new component for TPLink light bulbs.

* Update with result of gen_requirements_all.

* Add new component light.tplink.
2017-07-26 09:04:40 +02:00
Paulus Schoutsen
cd2703e121 Update dependencies cast + discovery (#8646) 2017-07-25 23:35:05 -07:00
Paulus Schoutsen
c2828bac2c Tweak conversation/intent/shopping list (#8636) 2017-07-25 00:42:59 -07:00
Paulus Schoutsen
ad7370e1c2 Update frontend 2017-07-25 00:29:05 -07:00
Adam Mills
3b7f16f189 Catch and log Lyft API errors (#8635) 2017-07-25 00:05:47 -04:00
Colin O'Dell
cc03f7ee6a Manual alarm with MQTT control (#8257)
* Manual alarm with MQTT control

* Duplicate manual control panel code instead of extending it

* Duplicate manual alarm test as well; modify for manual_mqtt

* Add MQTT-specific tests for manual_mqtt alarm
2017-07-24 09:06:38 -07:00
Corey Pauley
ecc1429453 Added support for default value when environment variable is missing (#8484)
* Added support for a default value when an environment variable is missing

* Shouldn't have used docstring
2017-07-24 09:00:01 -07:00
Justin Dray
98568b5eb7 Add support for using credstash as a secret store (#8494) 2017-07-24 08:59:10 -07:00
Paulus Schoutsen
9d9ca64f26 Update README.rst 2017-07-24 07:53:14 -07:00
Paulus Schoutsen
1d31137616 Update README.rst 2017-07-24 07:47:57 -07:00
Pascal Vizeli
f86bd15580 Cleanup old device_tracker stuff (#8627)
* Cleanup old device_tracker stuff

* Fix lint
2017-07-24 07:45:02 -07:00
Paulus Schoutsen
cbf65220aa Merge pull request #8625 from home-assistant/release-0-49-1
0.49.1
2017-07-24 07:35:29 -07:00
Daniel Høyer Iversen
c100b8cb52 Add is_lighting4 to RfxtrxBinarySensor (#8563) 2017-07-24 07:33:37 -07:00
matt2005
654ad41464 added onvif camera fix for non-virtual env installations (#8592)
* added fix for non-virtual env installations

* fixed new line at end of file

* moved import os to top of file
2017-07-24 15:22:12 +02:00
Paulus Schoutsen
a2abb4ae0a Update frontend 2017-07-24 00:11:58 -07:00
Jeff Wilson
36e266442f Properly slugify switch.flux update service name (#8545) 2017-07-23 23:53:21 -07:00
Jeff Wilson
f3d9086ff4 Properly slugify switch.flux update service name (#8545) 2017-07-23 23:53:03 -07:00
Anton Lundin
0c09cfc6c4 ubus: Make multiple instances work again (#8571)
Back in "ubus: Refresh session on Access denied (#8111)" I added the
decorator _refresh_on_acccess_denied. Somehow that stopped multiple ubus
trackers from working in parallel, and only the one first init'ed
worked.

Changing the order of the decorators fixes the issue but, I'm sorry to
say I can't figure out why. There's some magic somewhere which I'm
missing.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2017-07-23 23:51:25 -07:00
Anton Lundin
b0b6026c68 ubus: Make multiple instances work again (#8571)
Back in "ubus: Refresh session on Access denied (#8111)" I added the
decorator _refresh_on_acccess_denied. Somehow that stopped multiple ubus
trackers from working in parallel, and only the one first init'ed
worked.

Changing the order of the decorators fixes the issue but, I'm sorry to
say I can't figure out why. There's some magic somewhere which I'm
missing.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2017-07-23 23:51:07 -07:00
Russell Cloran
8f47a9109c prometheus: Fix zwave battery level (#8615) 2017-07-23 23:49:22 -07:00
Russell Cloran
f0293eeac2 prometheus: Fix zwave battery level (#8615) 2017-07-23 23:49:03 -07:00
Phil Cole
e4317a6741 Tado Fix #8606 (#8621)
Handle case where 'mode' and 'fanSpeed' are missing JSON. Based on
changes in commit
adfb608f86
2017-07-23 23:48:33 -07:00
Phil Cole
4b449f5f93 Tado Fix #8606 (#8621)
Handle case where 'mode' and 'fanSpeed' are missing JSON. Based on
changes in commit
adfb608f86
2017-07-23 23:48:20 -07:00
Daniel Schaal
8760dc9b29 Check if /dev/input/by-id exists (#8601) 2017-07-23 23:47:52 -07:00
Daniel Schaal
1831a7da68 Check if /dev/input/by-id exists (#8601) 2017-07-23 23:47:38 -07:00
Marcelo Moreira de Mello
3e34f34f6b Bumped Amcrest version (#8624) 2017-07-23 23:46:35 -07:00
Marcelo Moreira de Mello
3fec2955a5 Bumped Amcrest version (#8624) 2017-07-23 23:46:23 -07:00
Chia-liang Kao
2cf9254a08 Fix STATION_SCHEMA validation on longitude (#8610) 2017-07-23 23:35:49 -07:00
Russell Cloran
333da0dc6d zha: Update to bellows 0.3.4 (#8594) 2017-07-23 23:35:49 -07:00
Yannick POLLART
7b10f0a14f Fix broken status update for lighting4 devices (#8543)
* Fix broken status update for lighting4 devices

* Fixed indentation
2017-07-23 23:35:49 -07:00
Anders Melchiorsen
fb6bdfaba9 LIFX: assume default features for unknown products (#8553)
This makes the detection work for prototypes as well.
2017-07-23 23:35:48 -07:00
Pierre Ståhl
d7da90ae54 Fix support for multiple Apple TVs (#8539) 2017-07-23 23:35:48 -07:00
Eugenio Panadero
a5bfcceacd Attach the chat_id for a callback query from a chat group (fixes #8461) (#8523) 2017-07-23 23:35:48 -07:00
Pascal Vizeli
4961ece931 Realfix for dlib (#8517)
* Update dlib_face_detect.py

* fix lint

* Update dlib_face_detect.py
2017-07-23 23:35:48 -07:00
Pascal Vizeli
7d99d6aad9 Update dlib_face_detect.py (#8516) 2017-07-23 23:35:48 -07:00
Russell Cloran
6dc93c2751 prometheus: Convert fahrenheit to celsius (#8511) 2017-07-23 23:35:48 -07:00
Maikel Wever
5c39eebea8 Fix TP-Link device tracker regression since 0.49 (#8497)
* Fix TP-Link device tracker regression since 0.49

This regression was introduced by #8322.

Fix is to utf encode the password like the other TP-Link backends do.

* Fix linting issue introduced in previous commit

Commit in question: 677f3fbb7f821ee925364c8260d235dce4f0ddbe
2017-07-23 23:35:48 -07:00
Paulus Schoutsen
ffd295b38b Update to 0.49.1 2017-07-23 23:34:08 -07:00
Phil Hawthorne
5d810dae86 REST binary sensor value_template optional (#8596)
According to the documentation, the `value_template` for the REST
binary_sensor is not required. However, if you don't provide this when
setting up a binary sensor, the component fails. Looks like a variable
was not being set, which I've now included.

This should make the REST binary sensor act the same way as the REST
sensor now.
2017-07-23 22:42:41 +02:00
Fabian Affolter
486bcc4cae Upgrade mypy to 0.520 (#8616) 2017-07-23 20:20:38 +02:00
Fabian Affolter
cc2de5e1dc Upgrade youtube_dl to 2017.7.23 (#8617) 2017-07-23 20:20:23 +02:00
cribbstechnologies
77d8e393a1 better but still not great (#8618) 2017-07-23 20:19:58 +02:00
Koen Ekelschot
c6bf529d38 Allow set_cover_position in scenes (#8613) 2017-07-23 15:59:27 +02:00
Chia-liang Kao
dac9716cf4 Fix STATION_SCHEMA validation on longitude (#8610) 2017-07-23 10:22:49 +02:00
Thomas Klingbeil
9043895407 make attributes in the fritzdect module easier to process (#8436)
* make attributes in the fritzdect module easier to process
* remove spaces in attribute names
* move units to separate attributes

* make attributes in the fritzdect module easier to process
* remove spaces in attribute names
* move units to separate attributes

* Use new python formating syntax and attribute constant

* Shorten too long line

* Fix indent
2017-07-22 20:34:58 +02:00
Open Home Automation
2f08a91fdd Simplified percent conversion, better logging (#8568)
* Simplified percent conversion, better logging

*  Unnecessary pass statement (unnecessary-pass)
2017-07-22 20:00:13 +02:00
Sean Gollschewsky
1807b45222 Binary sensor ping fixed for hassio (#8573)
* Add support for multiple ping utilities.

* Added support for differing flavours of ping included with
different distributions (specifically alpine linux for hassio's
homeassistant).

* Updated as per comments in PR
2017-07-22 19:50:31 +02:00
Teemu R
b4f392b181 bump python-mirobo version for more robust protocol handling, make the platform to update on startup (#8602) 2017-07-22 19:36:14 +02:00
William Scanlon
8e8ec7a7c3 Remove code in wink.py overwriting hass.data configurator (#8595) 2017-07-22 09:21:38 -04:00
Paulus Schoutsen
7edf14e55f Add Intent component (#8434)
* Add intent component

* Add intent script component

* Add shopping list component

* Convert Snips to use intent component

* Convert Alexa to use intent component

* Lint

* Fix Alexa tests

* Update snips test

* Add intent support to conversation

* Add API to view shopping list contents

* Lint

* Fix demo test

* Lint

* lint

* Remove type from slot schema

* Add dependency to conversation

* Move intent to be a helper

* Fix conversation

* Clean up intent helper

* Fix Alexa

* Snips to use new hass.components

* Allow registering intents with conversation at any point in time

* Shopping list to register sentences

* Add HTTP endpoint to Conversation

* Add async action option to intent_script

* Update API.ai to use intents

* Cleanup Alexa

* Shopping list component to register built-in panel

* Rename shopping list intent to inlude Hass name
2017-07-21 21:38:53 -07:00
Paulus Schoutsen
7bea69ce83 update frontend 2017-07-21 21:29:58 -07:00
Russell Cloran
8d31c5fbf6 zha: Update to bellows 0.3.4 (#8594) 2017-07-21 21:22:43 -07:00
William Scanlon
dc42b6358a Support for Wink oauth application authorization (#8208) 2017-07-21 20:18:57 -04:00
Per Osbäck
06ceadfd54 upgrade pywebpush and PyJWT (#8588) 2017-07-21 22:35:19 +02:00
Daniel Høyer Iversen
4359e0babf xiaomi binary sensor bug fix (#8586)
* xiaomi binary sensor bug fig

* Is not need on binary_sensor
2017-07-21 12:39:25 +02:00
Marcelo Moreira de Mello
ee153062ab Extends Fitbit sensors to track the device battery level (#8583)
* Extends Fitbit sensors to track the device battery level

* cleanup old stuff

* remove update from init
2017-07-21 10:19:26 +02:00
Sebastian Muszynski
fada6d3f49 Device support for different new sensors of the xiaomi aqara gateway (#8577)
* The gateway configuration accepts a MAC address or a SID value in uppercase already.
The ringtone services accepts the same values now. I hope it will avoid confusion.

* Device support for the new wall switches with neutral lead (ctrl_ln1, ctrl_ln2) added.

* Measurement unit from pressure of weather.v1 fixed.

* Device support for sensor_magnet.aq2 added.

* Device support for sensor_motion.aq2 (motion and lux) added.

* Code reformatted.

* The ringtone service (start/stop) uses the parameter gw_mac instead of gw_sid now.

* Version of the required library updated.
2017-07-21 10:13:42 +02:00
Daniel Høyer Iversen
f6a5e0887d upgade xiaomi lib to 0.2 (#8584) 2017-07-21 10:10:03 +02:00
William Scanlon
4f8d2ec317 Added Time Remaining and Time Elapsed sensors for octoprint (#8581)
Add an optional extended description…
2017-07-21 09:40:07 +02:00
Aaron Bach
e63a96cf53 Bumped python-simplisafe version (#8578)
* Bumped python-simplisafe version
2017-07-20 18:59:41 -04:00
Daniel Høyer Iversen
a5c0831dc1 xiaomi bug fix (#8576) 2017-07-20 23:04:21 +02:00
namadori
718949481f fix #8263 corrected Adafruit DHT library version from 1.3.0 to 1.3.2 (#8562)
Add an optional extended description…
2017-07-20 15:53:06 +02:00
Daniel Høyer Iversen
90639d33ab Xiaomi gw support (#8555)
* xiaomi support

* xiaomi support

* style

* style

* style

* style

* style

* coveragerc

* Update xiaomi.py

* Update xiaomi.py

* Update xiaomi.py

* refactorization

* refactorization

* config validation

* style

* package

* refactorization

* refactorization

* refactorization

* HA integration
2017-07-20 15:20:00 +02:00
Greg Dowling
966809c1a1 Merge pull request #8564 from home-assistant/bump_pyvera
Bump pyvera to fix colour exception
2017-07-20 11:53:53 +01:00
pavoni
bc27d173d0 Bump pyver to fix exception in fetching colours. 2017-07-20 10:21:08 +01:00
Daniel Høyer Iversen
fde291f866 Add is_lighting4 to RfxtrxBinarySensor (#8563) 2017-07-20 11:12:42 +02:00
Paulus Schoutsen
49c399c358 Update persistent deps dir version in config.py (#8479)
* Update persistent deps dir version in config.py

* Update last version to remove deps dir in tests
2017-07-19 22:59:21 -07:00
Sean Dague
8d1999dc12 Enhance python_script to support "_getitem_" (#8541)
* Enhance python_script to support "_getitem_"

In order to use dict / list structures in python scripts we need
_getitem_ allowed in the RestrictedPython environment. There is a
default_guarded_getitem included with RestrictedPython, which is a
pass through used in the Eval code paths.

* Add tests for dict/list support in python_scripts

* Lint
2017-07-19 22:56:24 -07:00
Yannick POLLART
ee05a4ab89 Fix broken status update for lighting4 devices (#8543)
* Fix broken status update for lighting4 devices

* Fixed indentation
2017-07-19 22:56:11 -07:00
Anders Melchiorsen
8a42e1551a LIFX: assume default features for unknown products (#8553)
This makes the detection work for prototypes as well.
2017-07-19 22:54:46 -07:00
Jeff Wilson
9cc3e7e47b Handle manual edits to emulated_hue_ids.json (#8560) 2017-07-19 22:51:50 -07:00
Open Home Automation
54755df9ea Added a service to write to KNX group addressed including documentation (#8491)
* Added a service to write to KNX group addressed including documentation

* Define parameters as required

* Reformating

* Moved service documentation to service.yaml

* Moved service documentation to services.yaml

* Update knx.py
2017-07-20 07:01:05 +02:00
William Scanlon
84ebcd8a59 Support for Wink Switch and Light groups also fix fan speed selection (#8501)
* Support for Switch and Light groups, fix fan speed

* Fixed hound violations
2017-07-20 00:27:39 +02:00
Marcelo Moreira de Mello
f1280d3edb Extends Pi-hole sensor to support the new sensors: (#8549)
- domains_being_blocked
  - queries_cached
  - queries_forwarded
  - unique_clients
  - unique_domains
2017-07-19 19:45:53 +02:00
Eugenio Panadero
c27074e6f7 turn_on_action and turn_off_action with script syntax (#8558) 2017-07-19 13:33:16 -04:00
viswa-swami
c8bfcd2ed4 Upgrade the alarmdecoder dependency library from 0.12.1 to 0.12.3. (#8542)
* Upgrade the alarmdecoder dependency library from 0.12.1 to 0.12.3. Nutech software who owns this library have upgraded this library with some fixes regarding arming it to home/away and then disarming the alarm. Without this upgraded library, HASS is having a problem when we try to disarm an armed alarm after around 8 hours or so.

* Updated the requirements_all.txt by running the script gen_requirements_all.py
2017-07-19 12:21:39 +02:00
Jeff Wilson
42699b7a60 Report Harmony remote off if state is unknown (#8547)
Add an optional extended description…
2017-07-19 12:20:45 +02:00
Aliaksandr
6bc07298d3 [media_extractor] Add support for custom stream queries for media_extractor (#8538)
* Add support for different stream formats

* Encapsulate logic inside MediaExtractor class

* Add CONFIG_SCHEMA

* Fix for cases when youtube-dl returns content of playlist as list
2017-07-19 08:14:48 +01:00
Marcelo Moreira de Mello
4ece4bf241 Fix exception dlib_face_identify when image is not recognized by face_recognition module (#8552)
*   Some images are not supported by face_recognition, so this patch treats the error
  messages instead throwing a traceback. Fixes #7867

* Makes lint happy
2017-07-19 09:04:01 +02:00
Kevin Fronczak
1a86fa5a02 Initial support for Google Wifi/OnHub (#8485)
* Initial support for Google Wifi/OnHub

* Moved state logic to update function of API class

- Throttle added to update
- State logic implementation is cleaner
- Modified tests to work with the new throttle on update
2017-07-19 00:16:32 +02:00
Paulus Schoutsen
d54a634f11 Update demo.py 2017-07-18 15:11:17 -07:00
Thibault Cohen
5e1ff20b09 Decora: Fix set brightness and improve reconnection (#8522) 2017-07-19 00:02:42 +02:00
Pierre Ståhl
29266213a0 Fix support for multiple Apple TVs (#8539) 2017-07-18 19:19:36 +01:00
Fabian Affolter
2aa89cfe07 Upgrade TwitterAPI to 2.4.6 (#8535) 2017-07-18 16:24:32 +02:00
Fabian Affolter
879c816f5c Update docstrings (#8536) 2017-07-18 16:23:57 +02:00
Thibault Cohen
4ae11c009d Fix #6469 and #6828 (#8537) 2017-07-18 16:23:39 +02:00
Phil Lavin
dcd6f7a29e Return a 0 temperature value when none is found (#8518)
* Return a 0 temperature value when none is found

It's well documented that these TRVs will only return the current temperature
for a short time after the actuator has moved. This means that, usually, they will
not return the current temperature. Setting a non-value here causes errors in the logs
and for the TRV to not show on the dashboard at all

* Fix lint issue
2017-07-17 23:34:38 +02:00
Jan Losinski
fde4a7d029 Citybikes: Allow None as result for empty slots (#8528)
The citibykes API returns "null" as value for empty_slots on some
stations (see #8527). This causes the component to not process the data.

This is fixed by accepting None as valid data. The row in the frontend
is left empty if "null" was returned by the service.

fixes #8527
2017-07-17 22:50:55 +02:00
Anders Melchiorsen
b83ff739bc Remove deprecated automation keywords (#8510)
* Remove deprecated automation keywords

* Remove retired test case

* Remove retired keyword
2017-07-17 22:24:05 +02:00
Eugenio Panadero
8c9b3898fc handle timeout errors without logging.exception when updating hue lights; double quotes in log msgs (#8524) 2017-07-17 22:16:18 +02:00
Fabian Affolter
95e0027924 Fix KeyError 2017-07-17 20:21:16 +02:00
Mike Christianson
c67c20f752 fix for a bug introduced with media support in #8282 (#8513)
data may be None if twitter data property unconfigured:
  File "/opt/homeassistant/homeassistant_venv/lib/python3.4/site-packages/homeassistant/components/notify/twitter.py", line 63, in send_message
    media = data.get(ATTR_MEDIA)
2017-07-17 19:45:42 +02:00
Kevin Fronczak
1a1571cd52 Added sensor state rounding (#8499) 2017-07-17 19:21:41 +02:00
Eugenio Panadero
cca0d3ed44 Attach the chat_id for a callback query from a chat group (fixes #8461) (#8523) 2017-07-17 13:47:28 +02:00
Pascal Vizeli
f0479855bd Realfix for dlib (#8517)
* Update dlib_face_detect.py

* fix lint

* Update dlib_face_detect.py
2017-07-17 12:09:42 +02:00
Russell Cloran
40aafcdf5d prometheus: Convert fahrenheit to celsius (#8511) 2017-07-17 11:25:20 +02:00
Pascal Vizeli
8c9557401f Update dlib_face_detect.py (#8516) 2017-07-17 11:03:16 +02:00
Alexander Rust
ffd3081743 Added additional attributes to OwnTracks device_tracker (#8503)
* Added additional attributes to OwnTracks device_tracker

* Added missing space after :
2017-07-16 21:43:47 +02:00
Paulus Schoutsen
d0275c8075 Persistent notification import (#8507)
* Rewrite persistent notification creation

* Update components.is_on to use auto loading

* Fix two hass parameters
2017-07-16 21:39:38 +02:00
Maikel Wever
f6c3832e90 Fix TP-Link device tracker regression since 0.49 (#8497)
* Fix TP-Link device tracker regression since 0.49

This regression was introduced by #8322.

Fix is to utf encode the password like the other TP-Link backends do.

* Fix linting issue introduced in previous commit

Commit in question: 677f3fbb7f821ee925364c8260d235dce4f0ddbe
2017-07-16 10:27:48 -07:00
Paulus Schoutsen
d29bdddaa7 Add bind_hass to components (#8502)
* Add bind_hass to components

* Add bind_hass to group
2017-07-16 10:14:46 -07:00
Paulus Schoutsen
d3be056d15 Expose all components on hass [Concept] (#8490)
* Add components concept

* Lint

* Raise ImportError if component not found
2017-07-16 09:23:06 -07:00
Open Home Automation
bffa0d2b04 Bump to KNXIP 0.5 (#8492)
* Bump to KNXIP 0.5

* Updated requirements file
2017-07-16 13:34:13 +02:00
Paulus Schoutsen
23b65bfb30 Version bump to 0.50.0dev0 2017-07-15 21:07:15 -07:00
Martin Hjelmare
543e8bb62e Fix check for running inside venv (#8481)
* Import and use the function from pip instead of defining it
  ourselves.
* Fix tests.
2017-07-15 07:25:02 -07:00
Andrey
6ca828fd14 Make themes API work even when themes are not defined. (#8473) 2017-07-14 11:26:26 -07:00
Anders Melchiorsen
87b83f3602 Accept transition for light.toggle (#8466) 2017-07-13 20:04:23 -07:00
Bryce Edwards
5829cdfdf1 Radarr sensor fix for issue #8250 (#8456)
* Radarr sensor fix for issue #8250

* Radarr sensor fix for issue #8250
2017-07-13 20:02:07 -07:00
Sean Gollschewsky
d473f3407b Remove km from visibility, add visibility_distance (#8454)
* Remove km from visibility, add visibility_distance

* Fix line length

* Fix trailing space and line break indentation

* Indentation

* More whitespace
2017-07-13 20:01:25 -07:00
dersger
9373d5e901 Fix media_position for cast component (#8452)
* Make it available during state paused.

* Don't adjust for media_position_updated_at. I.e. do as vlc, sonos etc
  so that returned position is the position at the time of
  media_position_updated_at, not now.
2017-07-13 20:00:23 -07:00
Dougal Matthews
d8abef9210 Add RGB support to switch.flux (#8417) 2017-07-13 19:53:19 -07:00
Anders Melchiorsen
4fde0ffe9c LIFX: support for multizone (#8399)
* Make aiolifx modules easily available

* Use aiolifx features_map for deciding bulb features

Also move the feature detection out of Light so it is available even
during the initial detection.

* Move each LIFX light type to a separate class

* Simplify AwaitAioLIFX

This has become possible with recent aiolifx that calls the callback even
when a message is lost.

Now the wrapper can be used also before a Light is added though the register
callback then has to become a coroutine.

* Refactor send_color

* Add support for multizone

This lets lifx_set_state work on individual zones.

Also update to aiolifx_effects 0.1.1 that restores the state for individual
zones.
2017-07-13 19:38:36 -07:00
Martin Hjelmare
ba019c799a Make deps directory persistent over upgrades (#7801)
* Use pip install --user if venv not active

* Set PYTHONUSERBASE to deps directory, when installing with --user
  option.
* Reset --prefix option to workaround incompatability when installing
  with --user option. This requires pip version 8.0.0 or greater.
* Require pip version 8.0.3.
* Do not delete deps directory on home assistant upgrade.
* Fix local lib mount and check package exist.

* Update and add tests

* Fix upgrade from before version 0.46

* Extract function to get user site

* Add function(s) to package util to get user site.
* Use async subprocess for one of the functions to get user site.
* Add function to package util to check if virtual environment is
  active.
* Add and update tests.

* Update version for last removal of deps dir

* Address comments

* Rewrite package util tests with pytest

* Rewrite all existing unittest class based tests for package util as
  test functions, and capitalize pytest fixtures.
* Add test for installing with target inside venv.
2017-07-13 19:26:21 -07:00
Paulus Schoutsen
5581c6295e Fix iFrame panel test 2017-07-13 10:19:59 -07:00
435 changed files with 17501 additions and 3935 deletions

View File

@@ -29,6 +29,9 @@ omit =
homeassistant/components/arlo.py
homeassistant/components/*/arlo.py
homeassistant/components/asterisk_mbox.py
homeassistant/components/*/asterisk_mbox.py
homeassistant/components/axis.py
homeassistant/components/*/axis.py
@@ -173,6 +176,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/velbus.py
homeassistant/components/*/velbus.py
homeassistant/components/velux.py
homeassistant/components/*/velux.py
@@ -193,6 +199,13 @@ omit =
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/xiaomi.py
homeassistant/components/binary_sensor/xiaomi.py
homeassistant/components/cover/xiaomi.py
homeassistant/components/light/xiaomi.py
homeassistant/components/sensor/xiaomi.py
homeassistant/components/switch/xiaomi.py
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
@@ -208,6 +221,8 @@ omit =
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/egardia.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
@@ -257,6 +272,7 @@ omit =
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/huawei_router.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/linksys_ap.py
homeassistant/components/device_tracker/linksys_smart.py
@@ -274,7 +290,6 @@ omit =
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/xiaomi.py
homeassistant/components/downloader.py
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
@@ -291,6 +306,7 @@ omit =
homeassistant/components/light/blinkt.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/decora.py
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
@@ -303,6 +319,7 @@ omit =
homeassistant/components/light/piglow.py
homeassistant/components/light/sensehat.py
homeassistant/components/light/tikteck.py
homeassistant/components/light/tplink.py
homeassistant/components/light/tradfri.py
homeassistant/components/light/x10.py
homeassistant/components/light/yeelight.py
@@ -315,6 +332,7 @@ omit =
homeassistant/components/media_extractor.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/bluesound.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/clementine.py
@@ -344,6 +362,7 @@ omit =
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rio.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
@@ -430,6 +449,7 @@ omit =
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/geizhals.py
homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
@@ -476,6 +496,7 @@ omit =
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/shodan.py
homeassistant/components/sensor/skybeacon.py
homeassistant/components/sensor/sma.py
homeassistant/components/sensor/snmp.py
@@ -520,17 +541,18 @@ omit =
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainmachine.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/switch/xiaomi_vacuum.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/metoffice.py

1
.gitignore vendored
View File

@@ -73,6 +73,7 @@ pyvenv.cfg
pip-selfcheck.json
venv
.venv
Pipfile*
# vimmy stuff
*.swp

View File

@@ -4,11 +4,11 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
The process is straight-forward.
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0)
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing.
- 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 peak 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://home-assistant.io/developers/) to get more details.

View File

@@ -29,8 +29,7 @@ COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet && \
pip3 uninstall -y enum34
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
# Copy source
COPY . .

View File

@@ -1,5 +1,5 @@
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs|
==============================================================================================================================================================================================
Home Assistant |Build Status| |Coverage Status| |Chat Status|
=============================================================
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
@@ -31,6 +31,8 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
:target: https://travis-ci.org/home-assistant/home-assistant
.. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://discord.gg/c5DvZ4e
.. |Join the chat at https://gitter.im/home-assistant/home-assistant| image:: https://img.shields.io/badge/gitter-general-blue.svg
:target: https://gitter.im/home-assistant/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs| image:: https://img.shields.io/badge/gitter-development-yellowgreen.svg

View File

@@ -19,6 +19,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
import homeassistant.loader as loader
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, get_user_site
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.signal import async_register_signal_handling
@@ -39,7 +40,7 @@ def from_config_dict(config: Dict[str, Any],
skip_pip: bool=False,
log_rotate_days: Any=None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a config dict.
"""Try to configure Home Assistant from a configuration dictionary.
Dynamically loads required components and its dependencies.
"""
@@ -48,7 +49,8 @@ def from_config_dict(config: Dict[str, Any],
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
hass.loop.run_until_complete(
async_mount_local_lib_path(config_dir, hass.loop))
# run task
hass = hass.loop.run_until_complete(
@@ -69,7 +71,7 @@ def async_from_config_dict(config: Dict[str, Any],
skip_pip: bool=False,
log_rotate_days: Any=None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a config dict.
"""Try to configure Home Assistant from a configuration dictionary.
Dynamically loads required components and its dependencies.
This method is a coroutine.
@@ -90,8 +92,8 @@ def async_from_config_dict(config: Dict[str, Any],
hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning('Skipping pip installation of required modules. '
'This may cause issues.')
_LOGGER.warning("Skipping pip installation of required modules. "
"This may cause issues")
if not loader.PREPARED:
yield from hass.async_add_job(loader.prepare, hass)
@@ -116,13 +118,13 @@ def async_from_config_dict(config: Dict[str, Any],
# pylint: disable=not-an-iterable
res = yield from core_components.async_setup(hass, config)
if not res:
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
_LOGGER.error("Home Assistant core failed to initialize. "
"further initialization aborted")
return hass
yield from persistent_notification.async_setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
_LOGGER.info("Home Assistant core initialized")
# stage 1
for component in components:
@@ -141,7 +143,7 @@ def async_from_config_dict(config: Dict[str, Any],
yield from hass.async_block_till_done()
stop = time()
_LOGGER.info('Home Assistant initialized in %.2fs', stop-start)
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
async_register_signal_handling(hass)
return hass
@@ -183,7 +185,7 @@ def async_from_config_file(config_path: str,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
yield from hass.async_add_job(mount_local_lib_path, config_dir)
yield from async_mount_local_lib_path(config_dir, hass.loop)
async_enable_logging(hass, verbose, log_rotate_days)
@@ -191,7 +193,7 @@ def async_from_config_file(config_path: str,
config_dict = yield from hass.async_add_job(
conf_util.load_yaml_config_file, config_path)
except HomeAssistantError as err:
_LOGGER.error('Error loading %s: %s', config_path, err)
_LOGGER.error("Error loading %s: %s", config_path, err)
return None
finally:
clear_secret_cache()
@@ -276,11 +278,23 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path."""
deps_dir = os.path.join(config_dir, 'deps')
lib_dir = get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
@asyncio.coroutine
def async_mount_local_lib_path(config_dir: str,
loop: asyncio.AbstractEventLoop) -> str:
"""Add local library to Python Path.
Async friendly.
This function is a coroutine.
"""
deps_dir = os.path.join(config_dir, 'deps')
if deps_dir not in sys.path:
sys.path.insert(0, os.path.join(config_dir, 'deps'))
lib_dir = yield from async_get_user_site(deps_dir, loop=loop)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir

View File

@@ -15,7 +15,6 @@ import homeassistant.core as ha
import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
@@ -33,25 +32,27 @@ def is_on(hass, entity_id=None):
If there is no entity id given we will check all.
"""
if entity_id:
group = get_component('group')
entity_ids = group.expand_entity_ids(hass, [entity_id])
entity_ids = hass.components.group.expand_entity_ids([entity_id])
else:
entity_ids = hass.states.entity_ids()
for ent_id in entity_ids:
domain = ha.split_entity_id(ent_id)[0]
module = get_component(domain)
try:
if module.is_on(hass, ent_id):
return True
component = getattr(hass.components, domain)
except AttributeError:
# module is None or method is_on does not exist
_LOGGER.exception("Failed to call %s.is_on for %s",
module, ent_id)
except ImportError:
_LOGGER.error('Failed to call %s.is_on: component not found',
domain)
continue
if not hasattr(component, 'is_on'):
_LOGGER.warning("Component %s has no is_on method.", domain)
continue
if component.is_on(ent_id):
return True
return False
@@ -161,10 +162,9 @@ def async_setup(hass, config):
return
if errors:
notif = get_component('persistent_notification')
_LOGGER.error(errors)
notif.async_create(
hass, "Config error. See dev-info panel for details.",
hass.components.persistent_notification.async_create(
"Config error. See dev-info panel for details.",
"Config validating", "{0}.check_config".format(ha.DOMAIN))
return

View File

@@ -15,6 +15,7 @@ from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
@@ -44,6 +45,7 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
})
@bind_hass
def alarm_disarm(hass, code=None, entity_id=None):
"""Send the alarm the command for disarm."""
data = {}
@@ -55,6 +57,7 @@ def alarm_disarm(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
@bind_hass
def alarm_arm_home(hass, code=None, entity_id=None):
"""Send the alarm the command for arm home."""
data = {}
@@ -66,6 +69,7 @@ def alarm_arm_home(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
@bind_hass
def alarm_arm_away(hass, code=None, entity_id=None):
"""Send the alarm the command for arm away."""
data = {}
@@ -77,6 +81,7 @@ def alarm_arm_away(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
@bind_hass
def alarm_trigger(hass, code=None, entity_id=None):
"""Send the alarm the command for trigger."""
data = {}

View File

@@ -0,0 +1,182 @@
"""
Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.egardia/
"""
import logging
import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
REQUIREMENTS = ['pythonegardia==1.0.17']
_LOGGER = logging.getLogger(__name__)
CONF_REPORT_SERVER_CODES = 'report_server_codes'
CONF_REPORT_SERVER_ENABLED = 'report_server_enabled'
CONF_REPORT_SERVER_PORT = 'report_server_port'
DEFAULT_NAME = 'Egardia'
DEFAULT_PORT = 80
DEFAULT_REPORT_SERVER_ENABLED = False
DEFAULT_REPORT_SERVER_PORT = 85
DOMAIN = 'egardia'
NOTIFICATION_ID = 'egardia_notification'
NOTIFICATION_TITLE = 'Egardia'
STATES = {
'ARM': STATE_ALARM_ARMED_AWAY,
'DAY HOME': STATE_ALARM_ARMED_HOME,
'DISARM': STATE_ALARM_DISARMED,
'HOME': STATE_ALARM_ARMED_HOME,
'TRIGGERED': STATE_ALARM_TRIGGERED,
'UNKNOWN': STATE_UNKNOWN,
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_REPORT_SERVER_CODES): vol.All(cv.ensure_list),
vol.Optional(CONF_REPORT_SERVER_ENABLED,
default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean,
vol.Optional(CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT):
cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Egardia platform."""
from pythonegardia import egardiadevice
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
rs_enabled = config.get(CONF_REPORT_SERVER_ENABLED)
rs_port = config.get(CONF_REPORT_SERVER_PORT)
rs_codes = config.get(CONF_REPORT_SERVER_CODES)
try:
egardiasystem = egardiadevice.EgardiaDevice(
host, port, username, password, '')
except requests.exceptions.RequestException:
raise exc.PlatformNotReady()
except egardiadevice.UnauthorizedError:
_LOGGER.error("Unable to authorize. Wrong password or username")
return False
add_devices([EgardiaAlarm(
name, egardiasystem, hass, rs_enabled, rs_port, rs_codes)], True)
class EgardiaAlarm(alarm.AlarmControlPanel):
"""Representation of a Egardia alarm."""
def __init__(self, name, egardiasystem, hass, rs_enabled=False,
rs_port=None, rs_codes=None):
"""Initialize object."""
self._name = name
self._egardiasystem = egardiasystem
self._status = STATE_UNKNOWN
self._rs_enabled = rs_enabled
self._rs_port = rs_port
self._hass = hass
if rs_codes is not None:
self._rs_codes = rs_codes[0]
else:
self._rs_codes = rs_codes
if self._rs_enabled:
self.listen_to_system_status()
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._status
def handle_system_status_event(self, event):
"""Handle egardia_system_status_event."""
if event.data.get('status') is not None:
statuscode = event.data.get('status')
status = self.lookupstatusfromcode(statuscode)
self.parsestatus(status)
def listen_to_system_status(self):
"""Subscribe to egardia_system_status event."""
self._hass.bus.listen(
'egardia_system_status', self.handle_system_status_event)
def lookupstatusfromcode(self, statuscode):
"""Look at the rs_codes and returns the status from the code."""
status = 'UNKNOWN'
if self._rs_codes is not None:
statuscode = str(statuscode).strip()
for i in self._rs_codes:
val = str(self._rs_codes[i]).strip()
if ',' in val:
splitted = val.split(',')
for code in splitted:
code = str(code).strip()
if statuscode == code:
status = i.upper()
break
elif statuscode == val:
status = i.upper()
break
return status
def parsestatus(self, status):
"""Parse the status."""
newstatus = ([v for k, v in STATES.items()
if status.upper() == k][0])
self._status = newstatus
def update(self):
"""Update the alarm status."""
status = self._egardiasystem.getstate()
self.parsestatus(status)
def alarm_disarm(self, code=None):
"""Send disarm command."""
try:
self._egardiasystem.alarm_disarm()
except requests.exceptions.RequestException as err:
_LOGGER.error("Egardia device exception occurred when "
"sending disarm command: %s", err)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
try:
self._egardiasystem.alarm_arm_home()
except requests.exceptions.RequestException as err:
_LOGGER.error("Egardia device exception occurred when "
"sending arm home command: %s", err)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
try:
self._egardiasystem.alarm_arm_away()
except requests.exceptions.RequestException as err:
_LOGGER.error("Egardia device exception occurred when "
"sending arm away command: %s", err)

View File

@@ -0,0 +1,235 @@
"""
Support for manual alarms controllable via MQTT.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
"""
import asyncio
import datetime
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM,
CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
import homeassistant.components.mqtt as mqtt
from homeassistant.helpers.event import async_track_state_change
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
DEFAULT_DISARM_AFTER_TRIGGER = False
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
DEFAULT_DISARM = 'DISARM'
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_PLATFORM): 'manual_mqtt',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
vol.Required(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Required(mqtt.CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the manual MQTT alarm platform."""
add_devices([ManualMQTTAlarm(
hass,
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_PENDING_TIME, DEFAULT_PENDING_TIME),
config.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
config.get(mqtt.CONF_STATE_TOPIC),
config.get(mqtt.CONF_COMMAND_TOPIC),
config.get(mqtt.CONF_QOS),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY))])
class ManualMQTTAlarm(alarm.AlarmControlPanel):
"""
Representation of an alarm status.
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to the previous state
or disarm if `disarm_after_trigger` is true.
"""
def __init__(self, hass, name, code, pending_time,
trigger_time, disarm_after_trigger,
state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away):
"""Init the manual MQTT alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._pending_time = datetime.timedelta(seconds=pending_time)
self._trigger_time = datetime.timedelta(seconds=trigger_time)
self._disarm_after_trigger = disarm_after_trigger
self._pre_trigger_state = self._state
self._state_ts = None
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state_ts + self._pending_time > dt_util.utcnow():
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time +
self._trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
return self._pre_trigger_state
return self._state
@property
def code_format(self):
"""One or more characters."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, STATE_ALARM_DISARMED):
return
self._state = STATE_ALARM_DISARMED
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return
self._state = STATE_ALARM_ARMED_HOME
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return
self._state = STATE_ALARM_ARMED_AWAY
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._trigger_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time + self._trigger_time)
def _validate_code(self, code, state):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning("Invalid code given for %s", state)
return check
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method must be run in the event loop and returns a coroutine.
"""
async_track_state_change(
self.hass, self.entity_id, self._async_state_changed_listener
)
@callback
def message_received(topic, payload, qos):
"""Run when new MQTT message has been received."""
if payload == self._payload_disarm:
self.async_alarm_disarm(self._code)
elif payload == self._payload_arm_home:
self.async_alarm_arm_home(self._code)
elif payload == self._payload_arm_away:
self.async_alarm_arm_away(self._code)
else:
_LOGGER.warning("Received unexpected payload: %s", payload)
return
return mqtt.async_subscribe(
self.hass, self._command_topic, message_received, self._qos)
@asyncio.coroutine
def _async_state_changed_listener(self, entity_id, old_state, new_state):
"""Publish state change to MQTT."""
mqtt.async_publish(self.hass, self._state_topic, new_state.state,
self._qos, True)

View File

@@ -15,9 +15,8 @@ from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
REQUIREMENTS = ['simplisafe-python==1.0.2']
REQUIREMENTS = ['simplisafe-python==1.0.4']
_LOGGER = logging.getLogger(__name__)
@@ -42,7 +41,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
persistent_notification = loader.get_component('persistent_notification')
simplisafe = SimpliSafeApiInterface()
status = simplisafe.set_credentials(username, password)
if status:
@@ -53,8 +51,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
else:
message = 'Failed to log into SimpliSafe. Check credentials.'
_LOGGER.error(message)
persistent_notification.create(
hass, message,
hass.components.persistent_notification.create(
message,
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False

View File

@@ -15,7 +15,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['alarmdecoder==0.12.1.0']
REQUIREMENTS = ['alarmdecoder==0.12.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,8 +15,8 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers import intent, template, config_validation as cv
from homeassistant.components import http
_LOGGER = logging.getLogger(__name__)
@@ -60,6 +60,12 @@ class SpeechType(enum.Enum):
ssml = "SSML"
SPEECH_MAPPINGS = {
'plain': SpeechType.plaintext,
'ssml': SpeechType.ssml,
}
class CardType(enum.Enum):
"""The Alexa card types."""
@@ -69,20 +75,6 @@ class CardType(enum.Enum):
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_CARD): {
vol.Required(CONF_TYPE): cv.enum(CardType),
vol.Required(CONF_TITLE): cv.template,
vol.Required(CONF_CONTENT): cv.template,
},
vol.Optional(CONF_SPEECH): {
vol.Required(CONF_TYPE): cv.enum(SpeechType),
vol.Required(CONF_TEXT): cv.template,
}
}
},
CONF_FLASH_BRIEFINGS: {
cv.string: vol.All(cv.ensure_list, [{
vol.Required(CONF_UID, default=str(uuid.uuid4())): cv.string,
@@ -96,40 +88,27 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Activate Alexa component."""
intents = config[DOMAIN].get(CONF_INTENTS, {})
flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {})
hass.http.register_view(AlexaIntentsView(hass, intents))
hass.http.register_view(AlexaIntentsView)
hass.http.register_view(AlexaFlashBriefingView(hass, flash_briefings))
return True
class AlexaIntentsView(HomeAssistantView):
class AlexaIntentsView(http.HomeAssistantView):
"""Handle Alexa requests."""
url = INTENTS_API_ENDPOINT
name = 'api:alexa'
def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__()
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
hass, intent[CONF_ACTION], "Alexa intent {}".format(name))
self.intents = intents
@asyncio.coroutine
def post(self, request):
"""Handle Alexa."""
hass = request.app['hass']
data = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', data)
@@ -146,53 +125,61 @@ class AlexaIntentsView(HomeAssistantView):
if req_type == 'SessionEndedRequest':
return None
intent = req.get('intent')
response = AlexaResponse(request.app['hass'], intent)
alexa_intent_info = req.get('intent')
alexa_response = AlexaResponse(hass, alexa_intent_info)
if req_type == 'LaunchRequest':
response.add_speech(
SpeechType.plaintext,
"Hello, and welcome to the future. How may I help?")
return self.json(response)
if req_type != 'IntentRequest':
if req_type != 'IntentRequest' and req_type != 'LaunchRequest':
_LOGGER.warning('Received unsupported request: %s', req_type)
return self.json_message(
'Received unsupported request: {}'.format(req_type),
HTTP_BAD_REQUEST)
intent_name = intent['name']
config = self.intents.get(intent_name)
if req_type == 'LaunchRequest':
intent_name = data.get('session', {}) \
.get('application', {}) \
.get('applicationId')
else:
intent_name = alexa_intent_info['name']
if config is None:
try:
intent_response = yield from intent.async_handle(
hass, DOMAIN, intent_name,
{key: {'value': value} for key, value
in alexa_response.variables.items()})
except intent.UnknownIntent as err:
_LOGGER.warning('Received unknown intent %s', intent_name)
response.add_speech(
alexa_response.add_speech(
SpeechType.plaintext,
"This intent is not yet configured within Home Assistant.")
return self.json(response)
return self.json(alexa_response)
speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception('Error handling request for %s', intent_name)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
if action is not None:
yield from action.async_run(response.variables)
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
if intent_speech in intent_response.speech:
alexa_response.add_speech(
alexa_speech,
intent_response.speech[intent_speech]['speech'])
break
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
if 'simple' in intent_response.card:
alexa_response.add_card(
CardType.simple, intent_response.card['simple']['title'],
intent_response.card['simple']['content'])
if card is not None:
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
card[CONF_CONTENT])
return self.json(response)
return self.json(alexa_response)
class AlexaResponse(object):
"""Help generating the response for Alexa."""
def __init__(self, hass, intent=None):
def __init__(self, hass, intent_info):
"""Initialize the response."""
self.hass = hass
self.speech = None
@@ -201,8 +188,9 @@ class AlexaResponse(object):
self.session_attributes = {}
self.should_end_session = True
self.variables = {}
if intent is not None and 'slots' in intent:
for key, value in intent['slots'].items():
# Intent is None if request was a LaunchRequest or SessionEndedRequest
if intent_info is not None:
for key, value in intent_info.get('slots', {}).items():
if 'value' in value:
underscored_key = key.replace('.', '_')
self.variables[underscored_key] = value['value']
@@ -219,8 +207,8 @@ class AlexaResponse(object):
self.card = card
return
card["title"] = title.async_render(self.variables)
card["content"] = content.async_render(self.variables)
card["title"] = title
card["content"] = content
self.card = card
def add_speech(self, speech_type, text):
@@ -229,9 +217,6 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.async_render(self.variables)
self.speech = {
'type': speech_type.value,
key: text
@@ -272,7 +257,7 @@ class AlexaResponse(object):
}
class AlexaFlashBriefingView(HomeAssistantView):
class AlexaFlashBriefingView(http.HomeAssistantView):
"""Handle Alexa Flash Briefing skill requests."""
url = FLASH_BRIEFINGS_API_ENDPOINT

View File

@@ -11,14 +11,13 @@ import aiohttp
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
import homeassistant.loader as loader
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.0']
REQUIREMENTS = ['amcrest==1.2.1']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
@@ -92,7 +91,6 @@ def setup(hass, config):
amcrest_cams = config[DOMAIN]
persistent_notification = loader.get_component('persistent_notification')
for device in amcrest_cams:
camera = AmcrestCamera(device.get(CONF_HOST),
device.get(CONF_PORT),
@@ -103,8 +101,8 @@ def setup(hass, config):
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
persistent_notification.create(
hass, 'Error: {}<br />'
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,

View File

@@ -5,13 +5,12 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apiai/
"""
import asyncio
import copy
import logging
import voluptuous as vol
from homeassistant.const import PROJECT_NAME, HTTP_BAD_REQUEST
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.helpers import intent, template
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
@@ -29,24 +28,14 @@ DOMAIN = 'apiai'
DEPENDENCIES = ['http']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_SPEECH): cv.template,
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ASYNC_ACTION,
default=DEFAULT_CONF_ASYNC_ACTION): cv.boolean
}
}
}
DOMAIN: {}
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Activate API.AI component."""
intents = config[DOMAIN].get(CONF_INTENTS, {})
hass.http.register_view(ApiaiIntentsView(hass, intents))
hass.http.register_view(ApiaiIntentsView)
return True
@@ -57,24 +46,10 @@ class ApiaiIntentsView(HomeAssistantView):
url = INTENTS_API_ENDPOINT
name = 'api:apiai'
def __init__(self, hass, intents):
"""Initialize API.AI view."""
super().__init__()
self.hass = hass
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
hass, intent[CONF_ACTION], "Apiai intent {}".format(name))
self.intents = intents
@asyncio.coroutine
def post(self, request):
"""Handle API.AI."""
hass = request.app['hass']
data = yield from request.json()
_LOGGER.debug("Received api.ai request: %s", data)
@@ -91,55 +66,41 @@ class ApiaiIntentsView(HomeAssistantView):
if action_incomplete:
return None
# use intent to no mix HASS actions with this parameter
intent = req.get('action')
action = req.get('action')
parameters = req.get('parameters')
# contexts = req.get('contexts')
response = ApiaiResponse(parameters)
apiai_response = ApiaiResponse(parameters)
# Default Welcome Intent
# Maybe is better to handle this in api.ai directly?
#
# if intent == 'input.welcome':
# response.add_speech(
# "Hello, and welcome to the future. How may I help?")
# return self.json(response)
if intent == "":
if action == "":
_LOGGER.warning("Received intent with empty action")
response.add_speech(
apiai_response.add_speech(
"You have not defined an action in your api.ai intent.")
return self.json(response)
return self.json(apiai_response)
config = self.intents.get(intent)
try:
intent_response = yield from intent.async_handle(
hass, DOMAIN, action,
{key: {'value': value} for key, value
in parameters.items()})
if config is None:
_LOGGER.warning("Received unknown intent %s", intent)
response.add_speech(
"Intent '%s' is not yet configured within Home Assistant." %
intent)
return self.json(response)
except intent.UnknownIntent as err:
_LOGGER.warning('Received unknown intent %s', action)
apiai_response.add_speech(
"This intent is not yet configured within Home Assistant.")
return self.json(apiai_response)
speech = config.get(CONF_SPEECH)
action = config.get(CONF_ACTION)
async_action = config.get(CONF_ASYNC_ACTION)
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data: %s', err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception('Error handling request for %s', action)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
if action is not None:
# API.AI expects a response in less than 5s
if async_action:
# Do not wait for the action to be executed.
# Needed if the action will take longer than 5s to execute
self.hass.async_add_job(action.async_run(response.parameters))
else:
# Wait for the action to be executed so we can use results to
# render the answer
yield from action.async_run(response.parameters)
if 'plain' in intent_response.speech:
apiai_response.add_speech(
intent_response.speech['plain']['speech'])
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(speech)
return self.json(response)
return self.json(apiai_response)
class ApiaiResponse(object):

View File

@@ -15,10 +15,9 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.2']
REQUIREMENTS = ['pyatv==0.3.4']
_LOGGER = logging.getLogger(__name__)
@@ -66,27 +65,24 @@ APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({
def request_configuration(hass, config, atv, credentials):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
@asyncio.coroutine
def configuration_callback(callback_data):
"""Handle the submitted configuration."""
from pyatv import exceptions
pin = callback_data.get('pin')
notification = get_component('persistent_notification')
try:
yield from atv.airplay.finish_authentication(pin)
notification.async_create(
hass,
hass.components.persistent_notification.async_create(
'Authentication succeeded!<br /><br />Add the following '
'to credentials: in your apple_tv configuration:<br /><br />'
'{0}'.format(credentials),
title=NOTIFICATION_AUTH_TITLE,
notification_id=NOTIFICATION_AUTH_ID)
except exceptions.DeviceAuthenticationError as ex:
notification.async_create(
hass,
hass.components.persistent_notification.async_create(
'Authentication failed! Did you enter correct PIN?<br /><br />'
'Details: {0}'.format(ex),
title=NOTIFICATION_AUTH_TITLE,
@@ -119,9 +115,7 @@ def scan_for_apple_tvs(hass):
if not devices:
devices = ['No device(s) found']
notification = get_component('persistent_notification')
notification.async_create(
hass,
hass.components.persistent_notification.async_create(
'The following devices were found:<br /><br />' +
'<br /><br />'.join(devices),
title=NOTIFICATION_SCAN_TITLE,

View File

@@ -9,7 +9,6 @@ import logging
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
import homeassistant.loader as loader
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
@@ -40,7 +39,6 @@ def setup(hass, config):
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
persistent_notification = loader.get_component('persistent_notification')
try:
from pyarlo import PyArlo
@@ -50,8 +48,8 @@ def setup(hass, config):
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
persistent_notification.create(
hass, 'Error: {}<br />'
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,

View File

@@ -0,0 +1,82 @@
"""Support for Asterisk Voicemail interface."""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import (CONF_HOST,
CONF_PORT, CONF_PASSWORD)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
REQUIREMENTS = ['asterisk_mbox==0.4.0']
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
DOMAIN = 'asterisk_mbox'
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): int,
vol.Required(CONF_PASSWORD): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up for the Asterisk Voicemail box."""
conf = config.get(DOMAIN)
host = conf.get(CONF_HOST)
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)
return True
class AsteriskData(object):
"""Store Asterisk mailbox data."""
def __init__(self, hass, host, port, password):
"""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 = []
async_dispatcher_connect(
self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages)
@callback
def handle_data(self, command, msg):
"""Handle changes to the mailbox."""
from asterisk_mbox.commands import CMD_MESSAGE_LIST
if command == CMD_MESSAGE_LIST:
_LOGGER.info("AsteriskVM sent updated message list")
self.messages = sorted(msg,
key=lambda item: item['info']['origtime'],
reverse=True)
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
self.messages)
@callback
def _request_messages(self):
"""Handle changes to the mailbox."""
_LOGGER.info("Requesting message list")
self.client.messages()

View File

@@ -13,6 +13,7 @@ import voluptuous as vol
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import CoreState
from homeassistant.loader import bind_hass
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -26,7 +27,6 @@ from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
from homeassistant.components.frontend import register_built_in_panel
DOMAIN = 'automation'
DEPENDENCIES = ['group']
@@ -105,6 +105,7 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({
RELOAD_SERVICE_SCHEMA = vol.Schema({})
@bind_hass
def is_on(hass, entity_id):
"""
Return true if specified automation entity_id is on.
@@ -114,35 +115,41 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
@bind_hass
def turn_on(hass, entity_id=None):
"""Turn on specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
@bind_hass
def turn_off(hass, entity_id=None):
"""Turn off specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
@bind_hass
def toggle(hass, entity_id=None):
"""Toggle specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
@bind_hass
def trigger(hass, entity_id=None):
"""Trigger specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
@bind_hass
def reload(hass):
"""Reload the automation from config."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def async_reload(hass):
"""Reload the automation from config.
@@ -224,10 +231,6 @@ def async_setup(hass, config):
DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service), schema=SERVICE_SCHEMA)
if 'frontend' in hass.config.components:
register_built_in_panel(hass, 'automation', 'Automations',
'mdi:playlist-play')
return True

View File

@@ -12,13 +12,11 @@ import homeassistant.util.dt as dt_util
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
from homeassistant.helpers.event import (
async_track_state_change, async_track_point_in_utc_time)
from homeassistant.helpers.deprecation import get_deprecated
import homeassistant.helpers.config_validation as cv
CONF_ENTITY_ID = 'entity_id'
CONF_FROM = 'from'
CONF_TO = 'to'
CONF_STATE = 'state'
CONF_FOR = 'for'
TRIGGER_SCHEMA = vol.All(
@@ -28,11 +26,9 @@ TRIGGER_SCHEMA = vol.All(
# These are str on purpose. Want to catch YAML conversions
CONF_FROM: str,
CONF_TO: str,
CONF_STATE: str,
CONF_FOR: vol.All(cv.time_period, cv.positive_timedelta),
}),
vol.Any(cv.key_dependency(CONF_FOR, CONF_TO),
cv.key_dependency(CONF_FOR, CONF_STATE))
cv.key_dependency(CONF_FOR, CONF_TO),
)
@@ -41,7 +37,7 @@ def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = get_deprecated(config, CONF_TO, CONF_STATE, MATCH_ALL)
to_state = config.get(CONF_TO, MATCH_ALL)
time_delta = config.get(CONF_FOR)
async_remove_state_for_cancel = None
async_remove_state_for_listener = None

View File

@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_AT, CONF_PLATFORM, CONF_AFTER
from homeassistant.const import CONF_AT, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_time_change
@@ -23,12 +23,10 @@ _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'time',
CONF_AT: cv.time,
CONF_AFTER: cv.time,
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES,
CONF_SECONDS, CONF_AT, CONF_AFTER))
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
@asyncio.coroutine
@@ -37,11 +35,6 @@ def async_trigger(hass, config, action):
if CONF_AT in config:
at_time = config.get(CONF_AT)
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
elif CONF_AFTER in config:
_LOGGER.warning("'after' is deprecated for the time trigger. Please "
"rename 'after' to 'at' in your configuration file.")
at_time = config.get(CONF_AFTER)
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
else:
hours = config.get(CONF_HOURS)
minutes = config.get(CONF_MINUTES)

View File

@@ -21,7 +21,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['axis==8']
@@ -79,7 +78,7 @@ SERVICE_SCHEMA = vol.Schema({
def request_configuration(hass, name, host, serialnumber):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
def configuration_callback(callback_data):
"""Called when config is submitted."""
@@ -242,12 +241,11 @@ def setup_device(hass, config):
if enable_metadatastream:
device.initialize_new_event = event_initialized
if not device.initiate_metadatastream():
notification = get_component('persistent_notification')
notification.create(hass,
'Dependency missing for sensors, '
'please check documentation',
title=DOMAIN,
notification_id='axis_notification')
hass.components.persistent_notification.create(
'Dependency missing for sensors, '
'please check documentation',
title=DOMAIN,
notification_id='axis_notification')
AXIS_DEVICES[device.serial_number] = device

View File

@@ -14,7 +14,6 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.deprecation import deprecated_substitute
DOMAIN = 'binary_sensor'
SCAN_INTERVAL = timedelta(seconds=30)
@@ -66,7 +65,6 @@ class BinarySensorDevice(Entity):
return STATE_ON if self.is_on else STATE_OFF
@property
@deprecated_substitute('sensor_class')
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return None

View File

@@ -20,9 +20,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up an Online Status binary sensor."""
add_entities((OnlineStatus(config, apcupsd.DATA),))
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an APCUPSd Online Status binary sensor."""
add_devices([OnlineStatus(config, apcupsd.DATA)], True)
class OnlineStatus(BinarySensorDevice):
@@ -33,7 +33,6 @@ class OnlineStatus(BinarySensorDevice):
self._config = config
self._data = data
self._state = None
self.update()
@property
def name(self):

View File

@@ -13,10 +13,9 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@@ -26,7 +25,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_PIN): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -35,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = config.get(CONF_DEVICE_CLASS)
try:
response = requests.get(resource, timeout=10).json()

View File

@@ -37,7 +37,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for device in bloomsky.BLOOMSKY.devices.values():
for variable in sensors:
add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)])
add_devices(
[BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True)
class BloomSkySensor(BinarySensorDevice):
@@ -50,7 +51,7 @@ class BloomSkySensor(BinarySensorDevice):
self._sensor_name = sensor_name
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
self.update()
self._state = None
@property
def name(self):

View File

@@ -4,19 +4,18 @@ Support for custom shell commands to retrieve values.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command_line/
"""
from datetime import timedelta
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
CONF_COMMAND, CONF_DEVICE_CLASS)
_LOGGER = logging.getLogger(__name__)
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
@@ -44,15 +42,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
command = config.get(CONF_COMMAND)
payload_off = config.get(CONF_PAYLOAD_OFF)
payload_on = config.get(CONF_PAYLOAD_ON)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = config.get(CONF_DEVICE_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(command)
data = CommandSensorData(hass, command)
add_devices([CommandBinarySensor(
hass, data, name, device_class, payload_on, payload_off,
value_template)])
value_template)], True)
class CommandBinarySensor(BinarySensorDevice):
@@ -69,7 +67,6 @@ class CommandBinarySensor(BinarySensorDevice):
self._payload_on = payload_on
self._payload_off = payload_off
self._value_template = value_template
self.update()
@property
def name(self):

View File

@@ -72,9 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
)
)
add_devices(sensors)
return True
add_devices(sensors, True)
def get_opening_type(zone):
@@ -100,7 +98,6 @@ class Concord232ZoneSensor(BinarySensorDevice):
self._zone = zone
self._number = zone['number']
self._zone_type = zone_type
self.update()
@property
def device_class(self):
@@ -130,7 +127,7 @@ class Concord232ZoneSensor(BinarySensorDevice):
if last_update > datetime.timedelta(seconds=1):
self._client.zones = self._client.list_zones()
self._client.last_zone_update = datetime.datetime.now()
_LOGGER.debug("Updated from Zone: %s", self._zone['name'])
_LOGGER.debug("Updated from zone: %s", self._zone['name'])
if hasattr(self._client, 'zones'):
self._zone = next((x for x in self._client.zones

View File

@@ -19,7 +19,7 @@ from homeassistant.components.digital_ocean import (
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'moving'
DEFAULT_DEVICE_CLASS = 'moving'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -69,7 +69,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
return DEFAULT_DEVICE_CLASS
@property
def device_state_attributes(self):

View File

@@ -26,7 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev.append(EcobeeBinarySensor(sensor['name'], index))
add_devices(dev)
add_devices(dev, True)
class EcobeeBinarySensor(BinarySensorDevice):
@@ -39,7 +39,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
self.index = sensor_index
self._state = None
self._device_class = 'occupancy'
self.update()
@property
def name(self):

View File

@@ -12,9 +12,8 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.components import enocean
from homeassistant.const import (
CONF_NAME, CONF_ID, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
CONF_NAME, CONF_ID, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@@ -24,7 +23,6 @@ DEFAULT_NAME = 'EnOcean binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -33,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Binary Sensor platform for EnOcean."""
dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = config.get(CONF_DEVICE_CLASS)
add_devices([EnOceanBinarySensor(dev_id, devname, device_class)])

View File

@@ -64,7 +64,6 @@ class IssBinarySensor(BinarySensorDevice):
self._state = None
self._name = name
self._show_on_map = show
self.update()
@property
def name(self):

View File

@@ -15,10 +15,9 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -49,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async_add_devices([MqttBinarySensor(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),
config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),

View File

@@ -44,18 +44,19 @@ CONF_WELCOME_SENSORS = 'welcome_sensors'
CONF_PRESENCE_SENSORS = 'presence_sensors'
CONF_TAG_SENSORS = 'tag_sensors'
DEFAULT_TIMEOUT = 15
DEFAULT_OFFSET = 90
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_TIMEOUT): cv.positive_int,
vol.Optional(CONF_OFFSET): cv.positive_int,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES.keys()):
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
vol.Optional(
CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES.keys()):
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int,
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
})
@@ -63,16 +64,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the access to Netatmo binary sensor."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
timeout = config.get(CONF_TIMEOUT, 15)
offset = config.get(CONF_OFFSET, 90)
home = config.get(CONF_HOME)
timeout = config.get(CONF_TIMEOUT)
offset = config.get(CONF_OFFSET)
module_name = None
import lnetatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
if data.get_camera_names() == []:
if not data.get_camera_names():
return None
except lnetatmo.NoDevice:
return None
@@ -93,7 +94,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for variable in welcome_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout,
offset, camera_type, variable)])
offset, camera_type, variable)], True)
if camera_type == 'NOC':
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
@@ -102,14 +103,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for variable in presence_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
camera_type, variable)])
camera_type, variable)], True)
for module_name in data.get_module_names(camera_name):
for variable in tag_sensors:
camera_type = None
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
camera_type, variable)])
camera_type, variable)], True)
class NetatmoBinarySensor(BinarySensorDevice):
@@ -137,7 +138,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
self._name, camera_id)
self._cameratype = camera_type
self.update()
self._state = None
@property
def name(self):

View File

@@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1], 'flags')
devices.append(new_sensor)
add_devices(devices)
add_devices(devices, True)
class OctoPrintBinarySensor(BinarySensorDevice):
@@ -69,8 +69,6 @@ class OctoPrintBinarySensor(BinarySensorDevice):
self.api_endpoint = endpoint
self.api_group = group
self.api_tool = tool
# Set initial state
self.update()
_LOGGER.debug("Created OctoPrint binary sensor %r", self)
@property

View File

@@ -28,13 +28,16 @@ CONF_PING_COUNT = 'count'
DEFAULT_NAME = 'Ping Binary sensor'
DEFAULT_PING_COUNT = 5
DEFAULT_SENSOR_CLASS = 'connectivity'
DEFAULT_DEVICE_CLASS = 'connectivity'
SCAN_INTERVAL = timedelta(minutes=5)
PING_MATCHER = re.compile(
r'(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)\/(?P<mdev>\d+.\d+)')
PING_MATCHER_BUSYBOX = re.compile(
r'(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)')
WIN32_PING_MATCHER = re.compile(
r'(?P<min>\d+)ms.+(?P<max>\d+)ms.+(?P<avg>\d+)ms')
@@ -70,7 +73,7 @@ class PingBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
return DEFAULT_DEVICE_CLASS
@property
def is_on(self):
@@ -126,7 +129,14 @@ class PingData(object):
'avg': rtt_avg,
'max': rtt_max,
'mdev': ''}
if 'max/' not in str(out):
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': ''}
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {

View File

@@ -14,11 +14,10 @@ from homeassistant.components.binary_sensor import (
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = config.get(CONF_HEADERS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = config.get(CONF_DEVICE_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
@@ -74,7 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
add_devices([RestBinarySensor(
hass, rest, name, device_class, value_template)])
hass, rest, name, device_class, value_template)], True)
class RestBinarySensor(BinarySensorDevice):
@@ -89,7 +87,6 @@ class RestBinarySensor(BinarySensorDevice):
self._state = False
self._previous_data = None
self._value_template = value_template
self.update()
@property
def name(self):
@@ -107,6 +104,8 @@ class RestBinarySensor(BinarySensorDevice):
if self.rest.data is None:
return False
response = self.rest.data
if self._value_template is not None:
response = self._value_template.\
async_render_with_possible_json_value(self.rest.data, False)

View File

@@ -62,6 +62,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device.hass = hass
device.is_lighting4 = (packet_id[2:4] == '13')
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device
@@ -94,6 +95,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id)
sensor.hass = hass
sensor.is_lighting4 = (pkt_id[2:4] == '13')
rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor])
_LOGGER.info("Added binary sensor %s "
@@ -111,12 +114,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
if sensor.is_pt2262:
cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits)
_LOGGER.info("applying cmd %s to device_id: %s)",
cmd, sensor.masked_id)
sensor.apply_cmd(int(cmd, 16))
if sensor.is_lighting4:
if sensor.data_bits is not None:
cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits)
sensor.apply_cmd(int(cmd, 16))
else:
sensor.update_state(True)
else:
rfxtrx.apply_received_command(event)
@@ -151,6 +154,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
self._device_class = device_class
self._off_delay = off_delay
self._state = False
self.is_lighting4 = False
self.delay_listener = None
self._data_bits = data_bits
self._cmd_on = cmd_on
@@ -170,11 +174,6 @@ class RfxtrxBinarySensor(BinarySensorDevice):
"""Return the device name."""
return self._name
@property
def is_pt2262(self):
"""Return true if the device is PT2262-based."""
return self._data_bits is not None
@property
def masked_id(self):
"""Return the masked device id (isolated address bits)."""

View File

@@ -15,11 +15,9 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS,
EVENT_HOMEASSISTANT_START, STATE_ON)
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
@@ -30,7 +28,6 @@ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -49,8 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
device_class = get_deprecated(
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = device_config.get(CONF_DEVICE_CLASS)
if value_template is not None:
value_template.hass = hass

View File

@@ -13,10 +13,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS,
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN,
ATTR_ENTITY_ID, CONF_DEVICE_CLASS)
from homeassistant.core import callback
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
@@ -38,7 +37,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -50,7 +48,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
name = config.get(CONF_NAME)
threshold = config.get(CONF_THRESHOLD)
limit_type = config.get(CONF_TYPE)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = config.get(CONF_DEVICE_CLASS)
async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,

View File

@@ -16,9 +16,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_SENSOR_CLASS,
CONF_DEVICE_CLASS, STATE_UNKNOWN,)
from homeassistant.helpers.deprecation import get_deprecated
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, STATE_UNKNOWN)
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
@@ -32,7 +30,6 @@ SENSOR_SCHEMA = vol.Schema({
vol.Optional(CONF_ATTRIBUTE): cv.string,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_INVERT, default=False): cv.boolean,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -50,8 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
entity_id = device_config[ATTR_ENTITY_ID]
attribute = device_config.get(CONF_ATTRIBUTE)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
device_class = get_deprecated(
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
device_class = device_config.get(CONF_DEVICE_CLASS)
invert = device_config[CONF_INVERT]
sensors.append(

View File

@@ -0,0 +1,96 @@
"""
Support for Velbus Binary Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.velbus/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_DEVICES
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.velbus import DOMAIN
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['velbus']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [
{
vol.Required('module'): cv.positive_int,
vol.Required('channel'): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional('is_pushbutton'): cv.boolean
}
])
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Velbus binary sensors."""
velbus = hass.data[DOMAIN]
add_devices(VelbusBinarySensor(sensor, velbus)
for sensor in config[CONF_DEVICES])
class VelbusBinarySensor(BinarySensorDevice):
"""Representation of a Velbus Binary Sensor."""
def __init__(self, binary_sensor, velbus):
"""Initialize a Velbus light."""
self._velbus = velbus
self._name = binary_sensor[CONF_NAME]
self._module = binary_sensor['module']
self._channel = binary_sensor['channel']
self._is_pushbutton = 'is_pushbutton' in binary_sensor \
and binary_sensor['is_pushbutton']
self._state = False
@asyncio.coroutine
def async_added_to_hass(self):
"""Add listener for Velbus messages on bus."""
yield from self.hass.async_add_job(
self._velbus.subscribe, self._on_message)
def _on_message(self, message):
import velbus
if isinstance(message, velbus.PushButtonStatusMessage):
if message.address == self._module and \
self._channel in message.get_channels():
if self._is_pushbutton:
if self._channel in message.closed:
self._toggle()
else:
pass
else:
self._toggle()
def _toggle(self):
if self._state is True:
self._state = False
else:
self._state = True
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this sensor."""
return self._name
@property
def is_on(self):
"""Return true if the sensor is on."""
return self._state

View File

@@ -34,5 +34,5 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
"""Return the class of this sensor, from DEVICE_CLASSES."""
return 'safety'

View File

@@ -0,0 +1,316 @@
"""Support for Xiaomi binary sensors."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.xiaomi import (PY_XIAOMI_GATEWAY, XiaomiDevice)
_LOGGER = logging.getLogger(__name__)
NO_CLOSE = 'no_close'
ATTR_OPEN_SINCE = 'Open since'
MOTION = 'motion'
NO_MOTION = 'no_motion'
ATTR_NO_MOTION_SINCE = 'No motion since'
DENSITY = 'density'
ATTR_DENSITY = 'Density'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Xiaomi devices."""
devices = []
for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items():
for device in gateway.devices['binary_sensor']:
model = device['model']
if model == 'motion':
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model == 'sensor_motion.aq2':
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model == 'magnet':
devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'sensor_magnet.aq2':
devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'smoke':
devices.append(XiaomiSmokeSensor(device, gateway))
elif model == 'natgas':
devices.append(XiaomiNatgasSensor(device, gateway))
elif model == 'switch':
devices.append(XiaomiButton(device, 'Switch', 'status',
hass, gateway))
elif model == 'sensor_switch.aq2':
devices.append(XiaomiButton(device, 'Switch', 'status',
hass, gateway))
elif model == '86sw1':
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway))
elif model == '86sw2':
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
'channel_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
elif model == 'cube':
devices.append(XiaomiCube(device, hass, gateway))
add_devices(devices)
class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice):
"""Representation of a base XiaomiBinarySensor."""
def __init__(self, device, name, xiaomi_hub, data_key, device_class):
"""Initialize the XiaomiSmokeSensor."""
self._data_key = data_key
self._device_class = device_class
self._should_poll = False
self._density = 0
XiaomiDevice.__init__(self, device, name, xiaomi_hub)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return self._should_poll
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of binary sensor."""
return self._device_class
def update(self):
"""Update the sensor state."""
_LOGGER.debug('Updating xiaomi sensor by polling')
self._get_from_hub(self._sid)
class XiaomiNatgasSensor(XiaomiBinarySensor):
"""Representation of a XiaomiNatgasSensor."""
def __init__(self, device, xiaomi_hub):
"""Initialize the XiaomiSmokeSensor."""
self._density = None
XiaomiBinarySensor.__init__(self, device, 'Natgas Sensor', xiaomi_hub,
'alarm', 'gas')
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_DENSITY: self._density}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
value = data.get(self._data_key)
if value is None:
return False
if value == '1':
if self._state:
return False
self._state = True
return True
elif value == '0':
if self._state:
self._state = False
return True
return False
class XiaomiMotionSensor(XiaomiBinarySensor):
"""Representation of a XiaomiMotionSensor."""
def __init__(self, device, hass, xiaomi_hub):
"""Initialize the XiaomiMotionSensor."""
self._hass = hass
self._no_motion_since = 0
XiaomiBinarySensor.__init__(self, device, 'Motion Sensor', xiaomi_hub,
'status', 'motion')
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_NO_MOTION_SINCE: self._no_motion_since}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
self._should_poll = False
if NO_MOTION in data: # handle push from the hub
self._no_motion_since = data[NO_MOTION]
self._state = False
return True
value = data.get(self._data_key)
if value is None:
return False
if value == MOTION:
self._should_poll = True
if self.entity_id is not None:
self._hass.bus.fire('motion', {
'entity_id': self.entity_id
})
self._no_motion_since = 0
if self._state:
return False
self._state = True
return True
elif value == NO_MOTION:
if not self._state:
return False
self._state = False
return True
class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor."""
def __init__(self, device, xiaomi_hub):
"""Initialize the XiaomiDoorSensor."""
self._open_since = 0
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
xiaomi_hub, 'status', 'opening')
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_OPEN_SINCE: self._open_since}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
self._should_poll = False
if NO_CLOSE in data: # handle push from the hub
self._open_since = data[NO_CLOSE]
return True
value = data.get(self._data_key)
if value is None:
return False
if value == 'open':
self._should_poll = True
if self._state:
return False
self._state = True
return True
elif value == 'close':
self._open_since = 0
if self._state:
self._state = False
return True
return False
class XiaomiSmokeSensor(XiaomiBinarySensor):
"""Representation of a XiaomiSmokeSensor."""
def __init__(self, device, xiaomi_hub):
"""Initialize the XiaomiSmokeSensor."""
self._density = 0
XiaomiBinarySensor.__init__(self, device, 'Smoke Sensor', xiaomi_hub,
'alarm', 'smoke')
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_DENSITY: self._density}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
value = data.get(self._data_key)
if value is None:
return False
if value == '1':
if self._state:
return False
self._state = True
return True
elif value == '0':
if self._state:
self._state = False
return True
return False
class XiaomiButton(XiaomiBinarySensor):
"""Representation of a Xiaomi Button."""
def __init__(self, device, name, data_key, hass, xiaomi_hub):
"""Initialize the XiaomiButton."""
self._hass = hass
XiaomiBinarySensor.__init__(self, device, name, xiaomi_hub,
data_key, None)
def parse_data(self, data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
return False
if value == 'long_click_press':
self._state = True
click_type = 'long_click_press'
elif value == 'long_click_release':
self._state = False
click_type = 'hold'
elif value == 'click':
click_type = 'single'
elif value == 'double_click':
click_type = 'double'
elif value == 'both_click':
click_type = 'both'
else:
return False
self._hass.bus.fire('click', {
'entity_id': self.entity_id,
'click_type': click_type
})
if value in ['long_click_press', 'long_click_release']:
return True
return False
class XiaomiCube(XiaomiBinarySensor):
"""Representation of a Xiaomi Cube."""
def __init__(self, device, hass, xiaomi_hub):
"""Initialize the Xiaomi Cube."""
self._hass = hass
self._state = False
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
None, None)
def parse_data(self, data):
"""Parse data sent by gateway."""
if 'status' in data:
self._hass.bus.fire('cube_action', {
'entity_id': self.entity_id,
'action_type': data['status']
})
if 'rotate' in data:
self._hass.bus.fire('cube_action', {
'entity_id': self.entity_id,
'action_type': 'rotate',
'action_value': float(data['rotate'].replace(",", "."))
})
return False

View File

@@ -23,9 +23,7 @@ def get_device(values, **kwargs):
"""Create Z-Wave entity device."""
device_mapping = workaround.get_device_mapping(values.primary)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4
return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8)
return ZWaveTriggerSensor(values, "motion")
if workaround.get_device_component_mapping(values.primary) == DOMAIN:
return ZWaveBinarySensor(values, None)
@@ -62,15 +60,21 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor):
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, values, device_class, re_arm_sec=60):
def __init__(self, values, device_class):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(values, device_class)
self.re_arm_sec = re_arm_sec
# Set default off delay to 60 sec
self.re_arm_sec = 60
self.invalidate_after = None
def update_properties(self):
"""Handle value changes for this entity's node."""
self._state = self.values.primary.data
_LOGGER.debug('off_delay=%s', self.values.off_delay)
# Set re_arm_sec if off_delay is provided from the sensor
if self.values.off_delay:
_LOGGER.debug('off_delay.data=%s', self.values.off_delay.data)
self.re_arm_sec = self.values.off_delay.data * 8
# only allow this value to be true for re_arm secs
if not self.hass:
return

View File

@@ -23,6 +23,7 @@ from homeassistant.core import callback
from homeassistant.const import (ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE)
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -55,6 +56,7 @@ CAMERA_SERVICE_SCHEMA = vol.Schema({
})
@bind_hass
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
@@ -62,6 +64,7 @@ def enable_motion_detection(hass, entity_id=None):
DOMAIN, SERVICE_EN_MOTION, data))
@bind_hass
def disable_motion_detection(hass, entity_id=None):
"""Disable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None

View File

@@ -6,7 +6,6 @@ https://home-assistant.io/components/camera.foscam/
"""
import logging
import requests
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
@@ -16,11 +15,15 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyfoscam==1.2']
CONF_IP = 'ip'
DEFAULT_NAME = 'Foscam Camera'
DEFAULT_PORT = 88
FOSCAM_COMM_ERROR = -8
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IP): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
@@ -33,46 +36,60 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Foscam IP Camera."""
add_devices([FoscamCamera(config)])
add_devices([FoscamCam(config)])
class FoscamCamera(Camera):
class FoscamCam(Camera):
"""An implementation of a Foscam IP camera."""
def __init__(self, device_info):
"""Initialize a Foscam camera."""
super(FoscamCamera, self).__init__()
super(FoscamCam, self).__init__()
ip_address = device_info.get(CONF_IP)
port = device_info.get(CONF_PORT)
self._base_url = 'http://{}:{}/'.format(ip_address, port)
uri_template = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?' \
+ 'cmd=snapPicture2&usr={}&pwd={}'
self._username = device_info.get(CONF_USERNAME)
self._password = device_info.get(CONF_PASSWORD)
self._snap_picture_url = uri_template.format(
self._username,
self._password
)
self._name = device_info.get(CONF_NAME)
self._motion_status = False
_LOGGER.info("Using the following URL for %s: %s",
self._name, uri_template.format('***', '***'))
from foscam import FoscamCamera
self._foscam_session = FoscamCamera(ip_address, port, self._username,
self._password)
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
# Handle exception if host is not reachable or url failed
try:
response = requests.get(self._snap_picture_url, timeout=10)
except requests.exceptions.ConnectionError:
result, response = self._foscam_session.snap_picture_2()
if result == FOSCAM_COMM_ERROR:
return None
return response
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status
def enable_motion_detection(self):
"""Enable motion detection in camera."""
ret, err = self._foscam_session.enable_motion_detection()
if ret == FOSCAM_COMM_ERROR:
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
self._motion_status = True
else:
return response.content
self._motion_status = False
def disable_motion_detection(self):
"""Disable motion detection."""
ret, err = self._foscam_session.disable_motion_detection()
if ret == FOSCAM_COMM_ERROR:
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
self._motion_status = True
else:
self._motion_status = False
@property
def name(self):

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/camera.onvif/
"""
import asyncio
import logging
import os
import voluptuous as vol
@@ -54,6 +55,7 @@ class ONVIFCamera(Camera):
def __init__(self, hass, config):
"""Initialize a ONVIF camera."""
from onvif import ONVIFService
import onvif
super().__init__()
self._name = config.get(CONF_NAME)
@@ -63,7 +65,7 @@ class ONVIFCamera(Camera):
config.get(CONF_HOST), config.get(CONF_PORT)),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
'{}/deps/onvif/wsdl/media.wsdl'.format(hass.config.config_dir)
'{}/wsdl/media.wsdl'.format(os.path.dirname(onvif.__file__))
)
self._input = media.GetStreamUri().Uri
_LOGGER.debug("ONVIF Camera Using the following URL for %s: %s",

View File

@@ -14,6 +14,7 @@ from numbers import Number
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
@@ -85,13 +86,17 @@ SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
})
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
cv.has_at_least_one_key(
ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW),
{
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
}
))
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
@@ -114,6 +119,7 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
})
@bind_hass
def set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified climate devices away mode on."""
data = {
@@ -126,6 +132,7 @@ def set_away_mode(hass, away_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
@bind_hass
def set_hold_mode(hass, hold_mode, entity_id=None):
"""Set new hold mode."""
data = {
@@ -138,6 +145,7 @@ def set_hold_mode(hass, hold_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
@bind_hass
def set_aux_heat(hass, aux_heat, entity_id=None):
"""Turn all or specified climate devices auxillary heater on."""
data = {
@@ -150,6 +158,7 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
@bind_hass
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None,
operation_mode=None):
@@ -167,6 +176,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)
@bind_hass
def set_humidity(hass, humidity, entity_id=None):
"""Set new target humidity."""
data = {ATTR_HUMIDITY: humidity}
@@ -177,6 +187,7 @@ def set_humidity(hass, humidity, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
@bind_hass
def set_fan_mode(hass, fan, entity_id=None):
"""Set all or specified climate devices fan mode on."""
data = {ATTR_FAN_MODE: fan}
@@ -187,6 +198,7 @@ def set_fan_mode(hass, fan, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
@bind_hass
def set_operation_mode(hass, operation_mode, entity_id=None):
"""Set new target operation mode."""
data = {ATTR_OPERATION_MODE: operation_mode}
@@ -197,6 +209,7 @@ def set_operation_mode(hass, operation_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
@bind_hass
def set_swing_mode(hass, swing_mode, entity_id=None):
"""Set new target swing mode."""
data = {ATTR_SWING_MODE: swing_mode}

View File

@@ -75,7 +75,8 @@ def _setup_round(username, password, config, add_devices):
zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones):
add_devices(
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)],
True
)
except socket.error:
_LOGGER.error(
@@ -115,9 +116,9 @@ def _setup_us(username, password, config, add_devices):
class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""
def __init__(self, device, zone_id, master, away_temp):
def __init__(self, client, zone_id, master, away_temp):
"""Initialize the thermostat."""
self.device = device
self.client = client
self._current_temperature = None
self._target_temperature = None
self._name = 'round connected'
@@ -126,7 +127,6 @@ class RoundThermostat(ClimateDevice):
self._is_dhw = False
self._away_temp = away_temp
self._away = False
self.update()
@property
def name(self):
@@ -155,12 +155,12 @@ class RoundThermostat(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.device.set_temperature(self._name, temperature)
self.client.set_temperature(self._name, temperature)
@property
def current_operation(self: ClimateDevice) -> str:
"""Get the current operation of the system."""
return getattr(self.device, ATTR_SYSTEM_MODE, None)
return getattr(self.client, ATTR_SYSTEM_MODE, None)
@property
def is_away_mode_on(self):
@@ -169,8 +169,8 @@ class RoundThermostat(ClimateDevice):
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.device, ATTR_SYSTEM_MODE):
self.device.system_mode = operation_mode
if hasattr(self.client, ATTR_SYSTEM_MODE):
self.client.system_mode = operation_mode
def turn_away_mode_on(self):
"""Turn away on.
@@ -180,19 +180,19 @@ class RoundThermostat(ClimateDevice):
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
self.device.set_temperature(self._name, self._away_temp)
self.client.set_temperature(self._name, self._away_temp)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.device.cancel_temp_override(self._name)
self.client.cancel_temp_override(self._name)
def update(self):
"""Get the latest date."""
try:
# Only refresh if this is the "master" device,
# others will pick up the cache
for val in self.device.temperatures(force_refresh=self._master):
for val in self.client.temperatures(force_refresh=self._master):
if val['id'] == self._id:
data = val
@@ -210,6 +210,12 @@ class RoundThermostat(ClimateDevice):
self._name = data['name']
self._is_dhw = False
# The underlying library doesn't expose the thermostat's mode
# but we can pull it out of the big dictionary of information.
device = self.client.devices[self._id]
self.client.system_mode = device[
'thermostat']['changeableValues']['mode']
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""

View File

@@ -10,7 +10,6 @@ import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
@@ -140,7 +139,7 @@ class MaxCubeClimate(ClimateDevice):
def map_temperature_max_hass(temperature):
"""Map Temperature from MAX! to HASS."""
if temperature is None:
return STATE_UNKNOWN
return 0.0
return temperature

View File

@@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the NetAtmo Thermostat."""
netatmo = get_component('netatmo')
device = config.get(CONF_RELAY)
@@ -49,7 +49,7 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if config[CONF_THERMOSTAT] != [] and \
module_name not in config[CONF_THERMOSTAT]:
continue
add_callback_devices([NetatmoThermostat(data, module_name)])
add_devices([NetatmoThermostat(data, module_name)], True)
except lnetatmo.NoDevice:
return None
@@ -64,7 +64,6 @@ class NetatmoThermostat(ClimateDevice):
self._name = module_name
self._target_temperature = None
self._away = None
self.update()
@property
def name(self):

View File

@@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
host)
add_devices(tstats)
add_devices(tstats, True)
class RadioThermostat(ClimateDevice):
@@ -89,7 +89,6 @@ class RadioThermostat(ClimateDevice):
self._away = False
self._away_temps = away_temps
self._prev_temp = None
self.update()
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
@property

View File

@@ -273,31 +273,38 @@ class TadoClimate(ClimateDevice):
else:
self._device_is_active = True
overlay = False
overlay_data = None
termination = self._current_operation
cooling = False
fan_speed = CONST_MODE_OFF
if 'overlay' in data:
overlay_data = data['overlay']
overlay = overlay_data is not None
if overlay:
termination = overlay_data['termination']['type']
if 'setting' in overlay_data:
setting_data = overlay_data['setting']
setting = setting_data is not None
if setting:
if 'mode' in setting_data:
cooling = setting_data['mode'] == 'COOL'
if 'fanSpeed' in setting_data:
fan_speed = setting_data['fanSpeed']
if self._device_is_active:
overlay = False
overlay_data = None
termination = self._current_operation
cooling = False
fan_speed = CONST_MODE_OFF
if 'overlay' in data:
overlay_data = data['overlay']
overlay = overlay_data is not None
if overlay:
termination = overlay_data['termination']['type']
if 'setting' in overlay_data:
cooling = overlay_data['setting']['mode'] == 'COOL'
fan_speed = overlay_data['setting']['fanSpeed']
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
self._overlay_mode = termination
self._current_operation = termination
self._cooling = cooling
self._current_fan = fan_speed
self._cooling = cooling
self._current_fan = fan_speed
def _control_heating(self):
"""Send new target temperature to mytado."""

View File

@@ -1,12 +1,17 @@
"""Provide configuration end points for Z-Wave."""
import asyncio
import homeassistant.core as ha
from homeassistant.const import HTTP_NOT_FOUND
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY
from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'zwave_device_config.yaml'
OZW_LOG_FILENAME = 'OZW_Log.txt'
URL_API_OZW_LOG = '/api/zwave/ozwlog'
@asyncio.coroutine
@@ -16,4 +21,123 @@ def async_setup(hass):
'zwave', 'device_config', CONFIG_PATH, cv.entity_id,
DEVICE_CONFIG_SCHEMA_ENTRY
))
hass.http.register_view(ZWaveNodeValueView)
hass.http.register_view(ZWaveNodeGroupView)
hass.http.register_view(ZWaveNodeConfigView)
hass.http.register_view(ZWaveUserCodeView)
hass.http.register_static_path(
URL_API_OZW_LOG, hass.config.path(OZW_LOG_FILENAME), False)
return True
class ZWaveNodeValueView(HomeAssistantView):
"""View to return the node values."""
url = r"/api/zwave/values/{node_id:\d+}"
name = "api:zwave:values"
@ha.callback
def get(self, request, node_id):
"""Retrieve groups of node."""
nodeid = int(node_id)
hass = request.app['hass']
values_list = hass.data[const.DATA_ENTITY_VALUES]
values_data = {}
# Return a list of values for this node that are used as a
# primary value for an entity
for entity_values in values_list:
if entity_values.primary.node.node_id != nodeid:
continue
values_data[entity_values.primary.value_id] = {
'label': entity_values.primary.label,
'index': entity_values.primary.index,
'instance': entity_values.primary.instance,
}
return self.json(values_data)
class ZWaveNodeGroupView(HomeAssistantView):
"""View to return the nodes group configuration."""
url = r"/api/zwave/groups/{node_id:\d+}"
name = "api:zwave:groups"
@ha.callback
def get(self, request, node_id):
"""Retrieve groups of node."""
nodeid = int(node_id)
hass = request.app['hass']
network = hass.data.get(const.DATA_NETWORK)
node = network.nodes.get(nodeid)
if node is None:
return self.json_message('Node not found', HTTP_NOT_FOUND)
groupdata = node.groups
groups = {}
for key, value in groupdata.items():
groups[key] = {'associations': value.associations,
'association_instances':
value.associations_instances,
'label': value.label,
'max_associations': value.max_associations}
return self.json(groups)
class ZWaveNodeConfigView(HomeAssistantView):
"""View to return the nodes configuration options."""
url = r"/api/zwave/config/{node_id:\d+}"
name = "api:zwave:config"
@ha.callback
def get(self, request, node_id):
"""Retrieve configurations of node."""
nodeid = int(node_id)
hass = request.app['hass']
network = hass.data.get(const.DATA_NETWORK)
node = network.nodes.get(nodeid)
if node is None:
return self.json_message('Node not found', HTTP_NOT_FOUND)
config = {}
for value in (
node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION)
.values()):
config[value.index] = {'label': value.label,
'type': value.type,
'help': value.help,
'data_items': value.data_items,
'data': value.data,
'max': value.max,
'min': value.min}
return self.json(config)
class ZWaveUserCodeView(HomeAssistantView):
"""View to return the nodes usercode configuration."""
url = r"/api/zwave/usercodes/{node_id:\d+}"
name = "api:zwave:usercodes"
@ha.callback
def get(self, request, node_id):
"""Retrieve usercodes of node."""
nodeid = int(node_id)
hass = request.app['hass']
network = hass.data.get(const.DATA_NETWORK)
node = network.nodes.get(nodeid)
if node is None:
return self.json_message('Node not found', HTTP_NOT_FOUND)
usercodes = {}
if not node.has_command_class(const.COMMAND_CLASS_USER_CODE):
return self.json(usercodes)
for value in (
node.get_values(class_id=const.COMMAND_CLASS_USER_CODE)
.values()):
if value.genre != const.GENRE_USER:
continue
usercodes[value.index] = {'code': value.data,
'label': value.label,
'length': len(value.data)}
return self.json(usercodes)

View File

@@ -12,6 +12,7 @@ import logging
from homeassistant.core import callback as async_callback
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util.async import run_callback_threadsafe
@@ -37,6 +38,7 @@ STATE_CONFIGURE = 'configure'
STATE_CONFIGURED = 'configured'
@bind_hass
def request_config(
hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None,

View File

@@ -4,6 +4,7 @@ Support for functionality to have conversations with Home Assistant.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/conversation/
"""
import asyncio
import logging
import re
import warnings
@@ -11,16 +12,17 @@ import warnings
import voluptuous as vol
from homeassistant import core
from homeassistant.loader import bind_hass
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import script
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, HTTP_BAD_REQUEST)
from homeassistant.helpers import intent, config_validation as cv
from homeassistant.components import http
REQUIREMENTS = ['fuzzywuzzy==0.15.0']
REQUIREMENTS = ['fuzzywuzzy==0.15.1']
DEPENDENCIES = ['http']
ATTR_TEXT = 'text'
ATTR_SENTENCE = 'sentence'
DOMAIN = 'conversation'
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
@@ -28,79 +30,174 @@ REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
SERVICE_PROCESS = 'process'
SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
vol.Required(ATTR_TEXT): cv.string,
})
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
cv.string: vol.Schema({
vol.Required(ATTR_SENTENCE): cv.string,
vol.Required('action'): cv.SCRIPT_SCHEMA,
vol.Optional('intents'): vol.Schema({
cv.string: vol.All(cv.ensure_list, [cv.string])
})
})}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
@core.callback
@bind_hass
def async_register(hass, intent_type, utterances):
"""Register an intent.
Registrations don't require conversations to be loaded. They will become
active once the conversation component is loaded.
"""
intents = hass.data.get(DOMAIN)
if intents is None:
intents = hass.data[DOMAIN] = {}
conf = intents.get(intent_type)
if conf is None:
conf = intents[intent_type] = []
conf.extend(_create_matcher(utterance) for utterance in utterances)
@asyncio.coroutine
def async_setup(hass, config):
"""Register the process service."""
warnings.filterwarnings('ignore', module='fuzzywuzzy')
from fuzzywuzzy import process as fuzzyExtract
logger = logging.getLogger(__name__)
config = config.get(DOMAIN, {})
intents = hass.data.get(DOMAIN)
choices = {attrs[ATTR_SENTENCE]: script.Script(
hass,
attrs['action'],
name)
for name, attrs in config.items()}
if intents is None:
intents = hass.data[DOMAIN] = {}
for intent_type, utterances in config.get('intents', {}).items():
conf = intents.get(intent_type)
if conf is None:
conf = intents[intent_type] = []
conf.extend(_create_matcher(utterance) for utterance in utterances)
@asyncio.coroutine
def process(service):
"""Parse text into commands."""
# if actually configured
if choices:
text = service.data[ATTR_TEXT]
match = fuzzyExtract.extractOne(text, choices.keys())
scorelimit = 60 # arbitrary value
logging.info(
'matched up text %s and found %s',
text,
[match[0] if match[1] > scorelimit else 'nothing']
)
if match[1] > scorelimit:
choices[match[0]].run() # run respective script
return
text = service.data[ATTR_TEXT]
match = REGEX_TURN_COMMAND.match(text)
yield from _process(hass, text)
if not match:
logger.error("Unable to process: %s", text)
return
name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
if not entity_ids:
logger.error(
"Could not find entity id %s from text %s", name, text)
return
if command == 'on':
hass.services.call(core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
elif command == 'off':
hass.services.call(core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
else:
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(
hass.services.async_register(
DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA)
hass.http.register_view(ConversationProcessView)
return True
def _create_matcher(utterance):
"""Create a regex that matches the utterance."""
parts = re.split(r'({\w+})', utterance)
group_matcher = re.compile(r'{(\w+)}')
pattern = ['^']
for part in parts:
match = group_matcher.match(part)
if match is None:
pattern.append(part)
continue
pattern.append('(?P<{}>{})'.format(match.groups()[0], r'[\w ]+'))
pattern.append('$')
return re.compile(''.join(pattern), re.I)
@asyncio.coroutine
def _process(hass, text):
"""Process a line of text."""
intents = hass.data.get(DOMAIN, {})
for intent_type, matchers in intents.items():
for matcher in matchers:
match = matcher.match(text)
if not match:
continue
response = yield from intent.async_handle(
hass, DOMAIN, intent_type,
{key: {'value': value} for key, value
in match.groupdict().items()}, text)
return response
from fuzzywuzzy import process as fuzzyExtract
text = text.lower()
match = REGEX_TURN_COMMAND.match(text)
if not match:
_LOGGER.error("Unable to process: %s", text)
return None
name, command = match.groups()
entities = {state.entity_id: state.name for state
in hass.states.async_all()}
entity_ids = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
if not entity_ids:
_LOGGER.error(
"Could not find entity id %s from text %s", name, text)
return None
if command == 'on':
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
elif command == 'off':
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
else:
_LOGGER.error('Got unsupported command %s from text %s',
command, text)
return None
class ConversationProcessView(http.HomeAssistantView):
"""View to retrieve shopping list content."""
url = '/api/conversation/process'
name = "api:conversation:process"
@asyncio.coroutine
def post(self, request):
"""Send a request for processing."""
hass = request.app['hass']
try:
data = yield from request.json()
except ValueError:
return self.json_message('Invalid JSON specified',
HTTP_BAD_REQUEST)
text = data.get('text')
if text is None:
return self.json_message('Missing "text" key in JSON.',
HTTP_BAD_REQUEST)
intent_result = yield from _process(hass, text)
if intent_result is None:
intent_result = intent.IntentResponse()
intent_result.async_set_speech("Sorry, I didn't understand that")
return self.json(intent_result)

View File

@@ -13,6 +13,7 @@ import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@@ -22,7 +23,7 @@ from homeassistant.const import (
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID)
STATE_CLOSED, STATE_UNKNOWN, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
_LOGGER = logging.getLogger(__name__)
@@ -86,24 +87,28 @@ SERVICE_TO_METHOD = {
}
@bind_hass
def is_closed(hass, entity_id=None):
"""Return if the cover is closed based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_COVERS
return hass.states.is_state(entity_id, STATE_CLOSED)
@bind_hass
def open_cover(hass, entity_id=None):
"""Open all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN_COVER, data)
@bind_hass
def close_cover(hass, entity_id=None):
"""Close all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE_COVER, data)
@bind_hass
def set_cover_position(hass, position, entity_id=None):
"""Move to specific position all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -111,24 +116,28 @@ def set_cover_position(hass, position, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_COVER_POSITION, data)
@bind_hass
def stop_cover(hass, entity_id=None):
"""Stop all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_STOP_COVER, data)
@bind_hass
def open_cover_tilt(hass, entity_id=None):
"""Open all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN_COVER_TILT, data)
@bind_hass
def close_cover_tilt(hass, entity_id=None):
"""Close all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE_COVER_TILT, data)
@bind_hass
def set_cover_tilt_position(hass, tilt_position, entity_id=None):
"""Move to specific tilt position all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -136,6 +145,7 @@ def set_cover_tilt_position(hass, tilt_position, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data)
@bind_hass
def stop_cover_tilt(hass, entity_id=None):
"""Stop all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
@@ -215,6 +225,11 @@ class CoverDevice(Entity):
@property
def state(self):
"""Return the state of the cover."""
if self.is_opening:
return STATE_OPENING
if self.is_closing:
return STATE_CLOSING
closed = self.is_closed
if closed is None:
@@ -252,6 +267,16 @@ class CoverDevice(Entity):
return supported_features
@property
def is_opening(self):
"""Return if the cover is opening or not."""
pass
@property
def is_closing(self):
"""Return if the cover is closing or not."""
pass
@property
def is_closed(self):
"""Return if the cover is closed or not."""

View File

@@ -35,10 +35,12 @@ class DemoCover(CoverDevice):
self._set_position = None
self._set_tilt_position = None
self._tilt_position = tilt_position
self._closing = True
self._closing_tilt = True
self._requested_closing = True
self._requested_closing_tilt = True
self._unsub_listener_cover = None
self._unsub_listener_cover_tilt = None
self._is_opening = False
self._is_closing = False
if position is None:
self._closed = True
else:
@@ -69,6 +71,16 @@ class DemoCover(CoverDevice):
"""Return if the cover is closed."""
return self._closed
@property
def is_closing(self):
"""Return if the cover is closing."""
return self._is_closing
@property
def is_opening(self):
"""Return if the cover is opening."""
return self._is_opening
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
@@ -90,8 +102,10 @@ class DemoCover(CoverDevice):
self.schedule_update_ha_state()
return
self._is_closing = True
self._listen_cover()
self._closing = True
self._requested_closing = True
self.schedule_update_ha_state()
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
@@ -99,7 +113,7 @@ class DemoCover(CoverDevice):
return
self._listen_cover_tilt()
self._closing_tilt = True
self._requested_closing_tilt = True
def open_cover(self, **kwargs):
"""Open the cover."""
@@ -110,8 +124,10 @@ class DemoCover(CoverDevice):
self.schedule_update_ha_state()
return
self._is_opening = True
self._listen_cover()
self._closing = False
self._requested_closing = False
self.schedule_update_ha_state()
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
@@ -119,7 +135,7 @@ class DemoCover(CoverDevice):
return
self._listen_cover_tilt()
self._closing_tilt = False
self._requested_closing_tilt = False
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
@@ -128,7 +144,7 @@ class DemoCover(CoverDevice):
return
self._listen_cover()
self._closing = position < self._position
self._requested_closing = position < self._position
def set_cover_tilt_position(self, tilt_position, **kwargs):
"""Move the cover til to a specific position."""
@@ -137,10 +153,12 @@ class DemoCover(CoverDevice):
return
self._listen_cover_tilt()
self._closing_tilt = tilt_position < self._tilt_position
self._requested_closing_tilt = tilt_position < self._tilt_position
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._is_closing = False
self._is_opening = False
if self._position is None:
return
if self._unsub_listener_cover is not None:
@@ -166,7 +184,7 @@ class DemoCover(CoverDevice):
def _time_changed_cover(self, now):
"""Track time changes."""
if self._closing:
if self._requested_closing:
self._position -= 10
else:
self._position += 10
@@ -186,7 +204,7 @@ class DemoCover(CoverDevice):
def _time_changed_cover_tilt(self, now):
"""Track time changes."""
if self._closing_tilt:
if self._requested_closing_tilt:
self._tilt_position -= 10
else:
self._tilt_position += 10

View File

@@ -18,10 +18,10 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
ATTR_AVAILABLE = "available"
ATTR_SENSOR_STRENGTH = "sensor reflection rate"
ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)"
ATTR_TIME_IN_STATE = "time in state"
ATTR_AVAILABLE = 'available'
ATTR_SENSOR_STRENGTH = 'sensor_reflection_rate'
ATTR_SIGNAL_STRENGTH = 'wifi_signal_strength'
ATTR_TIME_IN_STATE = 'time_in_state'
DEFAULT_NAME = 'Garadget'

View File

@@ -8,11 +8,10 @@ import logging
from homeassistant.components.cover import (
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE)
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION)
from homeassistant.components.lutron_caseta import (
LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta']
@@ -23,7 +22,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Lutron Caseta Serena shades as a cover device."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
cover_devices = bridge.get_devices_by_types(["SerenaRollerShade"])
cover_devices = bridge.get_devices_by_types(["SerenaRollerShade",
"SerenaHoneycombShade"])
for cover_device in cover_devices:
dev = LutronCasetaCover(cover_device, bridge)
devs.append(dev)
@@ -37,13 +37,18 @@ class LutronCasetaCover(LutronCasetaDevice, CoverDevice):
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._state["current_state"] < 1
@property
def current_cover_position(self):
"""Return the current position of cover."""
return self._state["current_state"]
def close_cover(self):
"""Close the cover."""
self._smartbridge.set_value(self._device_id, 0)

View File

@@ -12,7 +12,6 @@ from homeassistant.components.cover import CoverDevice
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
REQUIREMENTS = ['pymyq==0.0.8']
@@ -37,7 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
brand = config.get(CONF_TYPE)
persistent_notification = loader.get_component('persistent_notification')
myq = pymyq(username, password, brand)
try:
@@ -52,8 +50,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
except (TypeError, KeyError, NameError, ValueError) as ex:
_LOGGER.error("%s", ex)
persistent_notification.create(
hass, 'Error: {}<br />'
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,

View File

@@ -24,9 +24,13 @@ CONF_RELAY_PIN = 'relay_pin'
CONF_RELAY_TIME = 'relay_time'
CONF_STATE_PIN = 'state_pin'
CONF_STATE_PULL_MODE = 'state_pull_mode'
CONF_INVERT_STATE = 'invert_state'
CONF_INVERT_RELAY = 'invert_relay'
DEFAULT_RELAY_TIME = .2
DEFAULT_STATE_PULL_MODE = 'UP'
DEFAULT_INVERT_STATE = False
DEFAULT_INVERT_RELAY = False
DEPENDENCIES = ['rpi_gpio']
_COVERS_SCHEMA = vol.All(
@@ -45,6 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE):
cv.string,
vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int,
vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean,
vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean,
})
@@ -53,13 +59,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the RPi cover platform."""
relay_time = config.get(CONF_RELAY_TIME)
state_pull_mode = config.get(CONF_STATE_PULL_MODE)
invert_state = config.get(CONF_INVERT_STATE)
invert_relay = config.get(CONF_INVERT_RELAY)
covers = []
covers_conf = config.get(CONF_COVERS)
for cover in covers_conf:
covers.append(RPiGPIOCover(
cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN],
state_pull_mode, relay_time))
state_pull_mode, relay_time, invert_state, invert_relay))
add_devices(covers)
@@ -67,7 +75,7 @@ class RPiGPIOCover(CoverDevice):
"""Representation of a Raspberry GPIO cover."""
def __init__(self, name, relay_pin, state_pin, state_pull_mode,
relay_time):
relay_time, invert_state, invert_relay):
"""Initialize the cover."""
self._name = name
self._state = False
@@ -75,9 +83,11 @@ class RPiGPIOCover(CoverDevice):
self._state_pin = state_pin
self._state_pull_mode = state_pull_mode
self._relay_time = relay_time
self._invert_state = invert_state
self._invert_relay = invert_relay
rpi_gpio.setup_output(self._relay_pin)
rpi_gpio.setup_input(self._state_pin, self._state_pull_mode)
rpi_gpio.write_output(self._relay_pin, True)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
@property
def unique_id(self):
@@ -96,13 +106,13 @@ class RPiGPIOCover(CoverDevice):
@property
def is_closed(self):
"""Return true if cover is closed."""
return self._state
return self._state != self._invert_state
def _trigger(self):
"""Trigger the cover."""
rpi_gpio.write_output(self._relay_pin, False)
rpi_gpio.write_output(self._relay_pin, self._invert_relay)
sleep(self._relay_time)
rpi_gpio.write_output(self._relay_pin, True)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
def close_cover(self):
"""Close the cover."""

View File

@@ -0,0 +1,160 @@
"""
Support for Velbus covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.velbus/
"""
import logging
import asyncio
import time
import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE,
SUPPORT_STOP)
from homeassistant.components.velbus import DOMAIN
from homeassistant.const import (CONF_COVERS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
COVER_SCHEMA = vol.Schema({
vol.Required('module'): cv.positive_int,
vol.Required('open_channel'): cv.positive_int,
vol.Required('close_channel'): cv.positive_int,
vol.Required(CONF_NAME): cv.string
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
DEPENDENCIES = ['velbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up cover controlled by Velbus."""
devices = config.get(CONF_COVERS, {})
covers = []
velbus = hass.data[DOMAIN]
for device_name, device_config in devices.items():
covers.append(
VelbusCover(
velbus,
device_config.get(CONF_NAME, device_name),
device_config.get('module'),
device_config.get('open_channel'),
device_config.get('close_channel')
)
)
if not covers:
_LOGGER.error("No covers added")
return False
add_devices(covers)
class VelbusCover(CoverDevice):
"""Representation a Velbus cover."""
def __init__(self, velbus, name, module, open_channel, close_channel):
"""Initialize the cover."""
self._velbus = velbus
self._name = name
self._close_channel_state = None
self._open_channel_state = None
self._module = module
self._open_channel = open_channel
self._close_channel = close_channel
@asyncio.coroutine
def async_added_to_hass(self):
"""Add listener for Velbus messages on bus."""
def _init_velbus():
"""Initialize Velbus on startup."""
self._velbus.subscribe(self._on_message)
self.get_status()
yield from self.hass.async_add_job(_init_velbus)
def _on_message(self, message):
import velbus
if isinstance(message, velbus.RelayStatusMessage):
if message.address == self._module:
if message.channel == self._close_channel:
self._close_channel_state = message.is_on()
self.schedule_update_ha_state()
if message.channel == self._open_channel:
self._open_channel_state = message.is_on()
self.schedule_update_ha_state()
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._close_channel_state
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown.
"""
return None
def _relay_off(self, channel):
import velbus
message = velbus.SwitchRelayOffMessage()
message.set_defaults(self._module)
message.relay_channels = [channel]
self._velbus.send(message)
def _relay_on(self, channel):
import velbus
message = velbus.SwitchRelayOnMessage()
message.set_defaults(self._module)
message.relay_channels = [channel]
self._velbus.send(message)
def open_cover(self, **kwargs):
"""Open the cover."""
self._relay_off(self._close_channel)
time.sleep(0.3)
self._relay_on(self._open_channel)
def close_cover(self, **kwargs):
"""Close the cover."""
self._relay_off(self._open_channel)
time.sleep(0.3)
self._relay_on(self._close_channel)
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._relay_off(self._open_channel)
time.sleep(0.3)
self._relay_off(self._close_channel)
def get_status(self):
"""Retrieve current status."""
import velbus
message = velbus.ModuleStatusRequestMessage()
message.set_defaults(self._module)
message.channels = [self._open_channel, self._close_channel]
self._velbus.send(message)

View File

@@ -0,0 +1,66 @@
"""Support for Xiaomi curtain."""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.xiaomi import (PY_XIAOMI_GATEWAY, XiaomiDevice)
_LOGGER = logging.getLogger(__name__)
ATTR_CURTAIN_LEVEL = 'curtain_level'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Xiaomi devices."""
devices = []
for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items():
for device in gateway.devices['cover']:
model = device['model']
if model == 'curtain':
devices.append(XiaomiGenericCover(device, "Curtain",
{'status': 'status',
'pos': 'curtain_level'},
gateway))
add_devices(devices)
class XiaomiGenericCover(XiaomiDevice, CoverDevice):
"""Representation of a XiaomiPlug."""
def __init__(self, device, name, data_key, xiaomi_hub):
"""Initialize the XiaomiPlug."""
self._data_key = data_key
self._pos = 0
XiaomiDevice.__init__(self, device, name, xiaomi_hub)
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return self._pos
@property
def is_closed(self):
"""Return if the cover is closed."""
return self.current_cover_position < 0
def close_cover(self, **kwargs):
"""Close the cover."""
self._write_to_hub(self._sid, self._data_key['status'], 'close')
def open_cover(self, **kwargs):
"""Open the cover."""
self._write_to_hub(self._sid, self._data_key['status'], 'open')
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._write_to_hub(self._sid, self._data_key['status'], 'stop')
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self._write_to_hub(self._sid, self._data_key['pos'], str(position))
def parse_data(self, data):
"""Parse data sent by gateway."""
if ATTR_CURTAIN_LEVEL in data:
self._pos = int(data[ATTR_CURTAIN_LEVEL])
return True
return False

View File

@@ -27,10 +27,12 @@ def get_device(hass, values, node_config, **kwargs):
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and values.primary.index == 0):
return ZwaveRollershutter(hass, values, invert_buttons)
elif (values.primary.command_class in [
zwave.const.COMMAND_CLASS_SWITCH_BINARY,
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]):
return ZwaveGarageDoor(values)
elif (values.primary.command_class ==
zwave.const.COMMAND_CLASS_SWITCH_BINARY):
return ZwaveGarageDoorSwitch(values)
elif (values.primary.command_class ==
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
return ZwaveGarageDoorBarrier(values)
return None
@@ -104,17 +106,33 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
self._network.manager.releaseButton(self._open_id)
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave garage door device."""
class ZwaveGarageDoorBase(zwave.ZWaveDeviceEntity, CoverDevice):
"""Base class for a Zwave garage door device."""
def __init__(self, values):
"""Initialize the zwave garage door."""
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._state = None
self.update_properties()
def update_properties(self):
"""Handle data changes for node values."""
self._state = self.values.primary.data
_LOGGER.debug("self._state=%s", self._state)
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'garage'
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_GARAGE
class ZwaveGarageDoorSwitch(ZwaveGarageDoorBase):
"""Representation of a switch based Zwave garage door device."""
@property
def is_closed(self):
@@ -129,12 +147,29 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
"""Open the garage door."""
self.values.primary.data = True
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'garage'
class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
"""Representation of a barrier operator Zwave garage door device."""
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_GARAGE
def is_opening(self):
"""Return true if cover is in an opening state."""
return self._state == "Opening"
@property
def is_closing(self):
"""Return true if cover is in an closing state."""
return self._state == "Closing"
@property
def is_closed(self):
"""Return the current position of Zwave garage door."""
return self._state == "Closed"
def close_cover(self):
"""Close the garage door."""
self.values.primary.data = "Closed"
def open_cover(self):
"""Open the garage door."""
self.values.primary.data = "Opened"

View File

@@ -9,7 +9,6 @@ import time
import homeassistant.bootstrap as bootstrap
import homeassistant.core as ha
import homeassistant.loader as loader
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
DEPENDENCIES = ['conversation', 'introduction', 'zone']
@@ -32,15 +31,16 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'sensor',
'switch',
'tts',
'mailbox',
]
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
persistent_notification = loader.get_component('persistent_notification')
group = hass.components.group
configurator = hass.components.configurator
persistent_notification = hass.components.persistent_notification
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
@@ -108,7 +108,7 @@ def async_setup(hass, config):
# Set up example persistent notification
persistent_notification.async_create(
hass, 'This is an example of a persistent notification.',
'This is an example of a persistent notification.',
title='Example Notification')
# Set up room groups
@@ -206,7 +206,7 @@ def async_setup(hass, config):
def setup_configurator():
"""Set up a configurator."""
request_id = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
"Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips "
"Hue with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",

View File

@@ -16,6 +16,7 @@ import voluptuous as vol
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file, async_log_exception
@@ -93,6 +94,7 @@ DISCOVERY_PLATFORMS = {
}
@bind_hass
def is_on(hass: HomeAssistantType, entity_id: str=None):
"""Return the state if any or a specified device is home."""
entity = entity_id or ENTITY_ID_ALL_DEVICES

View File

@@ -7,9 +7,7 @@ https://home-assistant.io/components/device_tracker.actiontec/
import logging
import re
import telnetlib
import threading
from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@@ -17,9 +15,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -54,7 +49,6 @@ class ActiontecDeviceScanner(DeviceScanner):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = []
data = self.get_actiontec_data()
self.success_init = data is not None
@@ -74,7 +68,6 @@ class ActiontecDeviceScanner(DeviceScanner):
return client.ip
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the router is up to date.
@@ -84,16 +77,15 @@ class ActiontecDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
now = dt_util.now()
actiontec_data = self.get_actiontec_data()
if not actiontec_data:
return False
self.last_results = [Device(data['mac'], name, now)
for name, data in actiontec_data.items()
if data['timevalid'] > -60]
_LOGGER.info("Scan successful")
return True
now = dt_util.now()
actiontec_data = self.get_actiontec_data()
if not actiontec_data:
return False
self.last_results = [Device(data['mac'], name, now)
for name, data in actiontec_data.items()
if data['timevalid'] > -60]
_LOGGER.info("Scan successful")
return True
def get_actiontec_data(self):
"""Retrieve data from Actiontec MI424WR and return parsed result."""

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.aruba/
"""
import logging
import re
import threading
from datetime import timedelta
import voluptuous as vol
@@ -15,14 +13,11 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pexpect==4.0.1']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_DEVICES_REGEX = re.compile(
r'(?P<name>([^\s]+))\s+' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
@@ -52,8 +47,6 @@ class ArubaDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible.
@@ -74,7 +67,6 @@ class ArubaDeviceScanner(DeviceScanner):
return client['name']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the Aruba Access Point is up to date.
@@ -83,13 +75,12 @@ class ArubaDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
data = self.get_aruba_data()
if not data:
return False
data = self.get_aruba_data()
if not data:
return False
self.last_results = data.values()
return True
self.last_results = data.values()
return True
def get_aruba_data(self):
"""Retrieve data from Aruba Access Point and return parsed result."""

View File

@@ -8,9 +8,7 @@ import logging
import re
import socket
import telnetlib
import threading
from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
@@ -18,7 +16,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pexpect==4.0.1']
@@ -32,8 +29,6 @@ CONF_SSH_KEY = 'ssh_key'
DEFAULT_SSH_PORT = 22
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
SECRET_GROUP = 'Password or SSH Key'
PLATFORM_SCHEMA = vol.All(
@@ -123,8 +118,6 @@ class AsusWrtDeviceScanner(DeviceScanner):
self.password,
self.mode == "ap")
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible.
@@ -145,7 +138,6 @@ class AsusWrtDeviceScanner(DeviceScanner):
return client['host']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the ASUSWRT router is up to date.
@@ -154,19 +146,18 @@ class AsusWrtDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info('Checking Devices')
data = self.get_asuswrt_data()
if not data:
return False
_LOGGER.info('Checking Devices')
data = self.get_asuswrt_data()
if not data:
return False
active_clients = [client for client in data.values() if
client['status'] == 'REACHABLE' or
client['status'] == 'DELAY' or
client['status'] == 'STALE' or
client['status'] == 'IN_ASSOCLIST']
self.last_results = active_clients
return True
active_clients = [client for client in data.values() if
client['status'] == 'REACHABLE' or
client['status'] == 'DELAY' or
client['status'] == 'STALE' or
client['status'] == 'IN_ASSOCLIST']
self.last_results = active_clients
return True
def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result."""

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.bt_home_hub_5/
"""
import logging
import re
import threading
from datetime import timedelta
import xml.etree.ElementTree as ET
import json
from urllib.parse import unquote
@@ -19,13 +17,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string
})
@@ -46,11 +41,7 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
"""Initialise the scanner."""
_LOGGER.info("Initialising BT Home Hub 5")
self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/nonAuth/home_status.xml'.format(self.host)
# Test the router is accessible
@@ -65,17 +56,15 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
if not self.last_results:
return None
if not self.last_results:
return None
return self.last_results.get(device)
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the BT Home Hub 5 is up to date.
@@ -84,18 +73,17 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
_LOGGER.info("Scanning")
data = _get_homehub_data(self.url)
data = _get_homehub_data(self.url)
if not data:
_LOGGER.warning("Error scanning devices")
return False
if not data:
_LOGGER.warning("Error scanning devices")
return False
self.last_results = data
self.last_results = data
return True
return True
def _get_homehub_data(url):

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.cisco_ios/
"""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -14,9 +13,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -65,7 +61,6 @@ class CiscoDeviceScanner(DeviceScanner):
return self.last_results
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensure the information from the Cisco router is up to date.

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.ddwrt/
"""
import logging
import re
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -16,9 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -50,8 +45,6 @@ class DdWrtDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
self.mac2name = {}
@@ -69,68 +62,65 @@ class DdWrtDeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
# If not initialised and not already scanned and not found.
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
# If not initialised and not already scanned and not found.
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
if not data:
return None
if not data:
return None
dhcp_leases = data.get('dhcp_leases', None)
dhcp_leases = data.get('dhcp_leases', None)
if not dhcp_leases:
return None
if not dhcp_leases:
return None
# Remove leading and trailing quotes and spaces
cleaned_str = dhcp_leases.replace(
"\"", "").replace("\'", "").replace(" ", "")
elements = cleaned_str.split(',')
num_clients = int(len(elements) / 5)
self.mac2name = {}
for idx in range(0, num_clients):
# The data is a single array
# every 5 elements represents one host, the MAC
# is the third element and the name is the first.
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
# Remove leading and trailing quotes and spaces
cleaned_str = dhcp_leases.replace(
"\"", "").replace("\'", "").replace(" ", "")
elements = cleaned_str.split(',')
num_clients = int(len(elements) / 5)
self.mac2name = {}
for idx in range(0, num_clients):
# The data is a single array
# every 5 elements represents one host, the MAC
# is the third element and the name is the first.
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device)
return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the DD-WRT router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
if not data:
return False
if not data:
return False
self.last_results = []
self.last_results = []
active_clients = data.get('active_wireless', None)
if not active_clients:
return False
active_clients = data.get('active_wireless', None)
if not active_clients:
return False
# The DD-WRT UI uses its own data format and then
# regex's out values so this is done here too
# Remove leading and trailing single quotes.
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
# The DD-WRT UI uses its own data format and then
# regex's out values so this is done here too
# Remove leading and trailing single quotes.
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))
return True
return True
def get_ddwrt_data(self, url):
"""Retrieve data from DD-WRT and return parsed result."""

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.fritz/
"""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -13,12 +12,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.6.3']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers.
@@ -88,7 +84,6 @@ class FritzBoxScanner(DeviceScanner):
return None
return ret
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Retrieve latest information from the FRITZ!Box."""
if not self.success_init:

View File

@@ -0,0 +1,145 @@
"""
Support for HUAWEI routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.huawei/
"""
import base64
import logging
import re
from collections import namedtuple
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return a HUAWEI scanner."""
scanner = HuaweiDeviceScanner(config[DOMAIN])
return scanner
Device = namedtuple('Device', ['name', 'ip', 'mac', 'state'])
class HuaweiDeviceScanner(DeviceScanner):
"""This class queries a router running HUAWEI firmware."""
ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);')
DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),')
DEVICE_ATTR_REGEX = re.compile(
'"(?P<Domain>.*?)","(?P<IpAddr>.*?)",'
'"(?P<MacAddr>.*?)","(?P<Port>.*?)",'
'"(?P<IpType>.*?)","(?P<DevType>.*?)",'
'"(?P<DevStatus>.*?)","(?P<PortType>.*?)",'
'"(?P<Time>.*?)","(?P<HostName>.*?)",'
'"(?P<IPv4Enabled>.*?)","(?P<IPv6Enabled>.*?)",'
'"(?P<DeviceType>.*?)"')
LOGIN_COOKIE = dict(Cookie='body:Language:portuguese:id=-1')
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = base64.b64encode(bytes(config[CONF_PASSWORD], 'utf-8'))
self.last_results = []
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [client.mac for client in self.last_results]
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
if not self.last_results:
return None
for client in self.last_results:
if client.mac == device:
return client.name
return None
def _update_info(self):
"""Ensure the information from the router is up to date.
Return boolean if scanning successful.
"""
data = self._get_data()
if not data:
return False
active_clients = [client for client in data if client.state]
self.last_results = active_clients
_LOGGER.debug("Active clients: " + "\n"
.join((client.mac + " " + client.name)
for client in active_clients))
return True
def _get_data(self):
"""Get the devices' data from the router.
Returns a list with all the devices known to the router DHCP server.
"""
array_regex_res = self.ARRAY_REGEX.search(self._get_devices_response())
devices = []
if array_regex_res:
device_regex_res = self.DEVICE_REGEX.findall(
array_regex_res.group(1))
for device in device_regex_res:
device_attrs_regex_res = self.DEVICE_ATTR_REGEX.search(device)
devices.append(Device(device_attrs_regex_res.group('HostName'),
device_attrs_regex_res.group('IpAddr'),
device_attrs_regex_res.group('MacAddr'),
device_attrs_regex_res.group(
'DevStatus') == "Online"))
return devices
def _get_devices_response(self):
"""Get the raw string with the devices from the router."""
cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host))
cnt_str = str(cnt.content, cnt.apparent_encoding, errors='replace')
_LOGGER.debug("Loggin in")
cookie = requests.post('http://{}/login.cgi'.format(self.host),
data=[('UserName', self.username),
('PassWord', self.password),
('x.X_HW_Token', cnt_str)],
cookies=self.LOGIN_COOKIE)
_LOGGER.debug("Requesting lan user info update")
# this request is needed or else some devices' state won't be updated
requests.get(
'http://{}/html/bbsp/common/lanuserinfo.asp'.format(self.host),
cookies=cookie.cookies)
_LOGGER.debug("Requesting lan user info data")
devices = requests.get(
'http://{}/html/bbsp/common/GetLanUserDevInfo.asp'.format(
self.host),
cookies=cookie.cookies)
# we need to decode() using the request encoding, then encode() and
# decode('unicode_escape') to replace \\xXX with \xXX
# (i.e. \\x2d -> \x2d)
return devices.content.decode(devices.apparent_encoding).encode().\
decode('unicode_escape')

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.linksys_ap/
"""
import base64
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -16,9 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL)
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
INTERFACES = 2
DEFAULT_TIMEOUT = 10
@@ -51,8 +47,6 @@ class LinksysAPDeviceScanner(object):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.verify_ssl = config[CONF_VERIFY_SSL]
self.lock = threading.Lock()
self.last_results = []
# Check if the access point is accessible
@@ -76,24 +70,22 @@ class LinksysAPDeviceScanner(object):
"""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Check for connected devices."""
from bs4 import BeautifulSoup as BS
with self.lock:
_LOGGER.info("Checking Linksys AP")
_LOGGER.info("Checking Linksys AP")
self.last_results = []
for interface in range(INTERFACES):
request = self._make_request(interface)
self.last_results.extend(
[x.find_all('td')[1].text
for x in BS(request.content, "html.parser")
.find_all(class_='section-row')]
)
self.last_results = []
for interface in range(INTERFACES):
request = self._make_request(interface)
self.last_results.extend(
[x.find_all('td')[1].text
for x in BS(request.content, "html.parser")
.find_all(class_='section-row')]
)
return True
return True
def _make_request(self, unit=0):
# No, the '&&' is not a typo - this is expected by the web interface.

View File

@@ -1,7 +1,5 @@
"""Support for Linksys Smart Wifi routers."""
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -10,9 +8,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_TIMEOUT = 10
_LOGGER = logging.getLogger(__name__)
@@ -36,8 +32,6 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.lock = threading.Lock()
self.last_results = {}
# Check if the access point is accessible
@@ -55,48 +49,46 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
"""Return the name (if known) of the device."""
return self.last_results.get(mac)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Check for connected devices."""
with self.lock:
_LOGGER.info("Checking Linksys Smart Wifi")
_LOGGER.info("Checking Linksys Smart Wifi")
self.last_results = {}
response = self._make_request()
if response.status_code != 200:
_LOGGER.error(
"Got HTTP status code %d when getting device list",
response.status_code)
return False
try:
data = response.json()
result = data["responses"][0]
devices = result["output"]["devices"]
for device in devices:
macs = device["knownMACAddresses"]
if not macs:
_LOGGER.warning(
"Skipping device without known MAC address")
continue
mac = macs[-1]
connections = device["connections"]
if not connections:
_LOGGER.debug("Device %s is not connected", mac)
continue
self.last_results = {}
response = self._make_request()
if response.status_code != 200:
_LOGGER.error(
"Got HTTP status code %d when getting device list",
response.status_code)
return False
try:
data = response.json()
result = data["responses"][0]
devices = result["output"]["devices"]
for device in devices:
macs = device["knownMACAddresses"]
if not macs:
_LOGGER.warning(
"Skipping device without known MAC address")
continue
mac = macs[-1]
connections = device["connections"]
if not connections:
_LOGGER.debug("Device %s is not connected", mac)
continue
name = None
for prop in device["properties"]:
if prop["name"] == "userDeviceName":
name = prop["value"]
if not name:
name = device.get("friendlyName", device["deviceID"])
name = None
for prop in device["properties"]:
if prop["name"] == "userDeviceName":
name = prop["value"]
if not name:
name = device.get("friendlyName", device["deviceID"])
_LOGGER.debug("Device %s is connected", mac)
self.last_results[mac] = name
except (KeyError, IndexError):
_LOGGER.exception("Router returned unexpected response")
return False
return True
_LOGGER.debug("Device %s is connected", mac)
self.last_results[mac] = name
except (KeyError, IndexError):
_LOGGER.exception("Router returned unexpected response")
return False
return True
def _make_request(self):
# Weirdly enough, this doesn't seem to require authentication

View File

@@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.luci/
import json
import logging
import re
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -18,9 +16,6 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -55,12 +50,8 @@ class LuciDeviceScanner(DeviceScanner):
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {}
self.refresh_token()
self.mac2name = None
self.success_init = self.token is not None
@@ -75,24 +66,22 @@ class LuciDeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.mac2name is None:
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
result = _req_json_rpc(url, 'get_all', 'dhcp',
params={'auth': self.token})
if result:
hosts = [x for x in result.values()
if x['.type'] == 'host' and
'mac' in x and 'name' in x]
mac2name_list = [
(x['mac'].upper(), x['name']) for x in hosts]
self.mac2name = dict(mac2name_list)
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
if self.mac2name is None:
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
result = _req_json_rpc(url, 'get_all', 'dhcp',
params={'auth': self.token})
if result:
hosts = [x for x in result.values()
if x['.type'] == 'host' and
'mac' in x and 'name' in x]
mac2name_list = [
(x['mac'].upper(), x['name']) for x in hosts]
self.mac2name = dict(mac2name_list)
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the Luci router is up to date.
@@ -101,31 +90,30 @@ class LuciDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info("Refreshing token")
self.refresh_token()
return False
if result:
self.last_results = []
for device_entry in result:
# Check if the Flags for each device contain
# NUD_REACHABLE and if so, add it to last_results
if int(device_entry['Flags'], 16) & 0x2:
self.last_results.append(device_entry['HW address'])
return True
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info("Refreshing token")
self.refresh_token()
return False
if result:
self.last_results = []
for device_entry in result:
# Check if the Flags for each device contain
# NUD_REACHABLE and if so, add it to last_results
if int(device_entry['Flags'], 16) & 0x2:
self.last_results.append(device_entry['HW address'])
return True
return False
def _req_json_rpc(url, method, *args, **kwargs):
"""Perform one JSON RPC operation."""

View File

@@ -5,25 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mikrotik/
"""
import logging
import threading
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (CONF_HOST,
CONF_PASSWORD,
CONF_USERNAME,
CONF_PORT)
from homeassistant.util import Throttle
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.2']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MTK_DEFAULT_API_PORT = '8728'
_LOGGER = logging.getLogger(__name__)
@@ -54,12 +46,9 @@ class MikrotikScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.connected = False
self.success_init = False
self.client = None
self.wireless_exist = None
self.success_init = self.connect_to_device()
@@ -118,51 +107,48 @@ class MikrotikScanner(DeviceScanner):
def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know."""
with self.lock:
return self.last_results.get(mac)
return self.last_results.get(mac)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Retrieve latest information from the Mikrotik box."""
with self.lock:
if self.wireless_exist:
devices_tracker = 'wireless'
else:
devices_tracker = 'ip'
if self.wireless_exist:
devices_tracker = 'wireless'
else:
devices_tracker = 'ip'
_LOGGER.info(
"Loading %s devices from Mikrotik (%s) ...",
devices_tracker,
self.host
_LOGGER.info(
"Loading %s devices from Mikrotik (%s) ...",
devices_tracker,
self.host
)
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if self.wireless_exist:
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)
else:
devices = device_names
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if self.wireless_exist:
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)
else:
devices = device_names
if device_names is None and devices is None:
return False
if device_names is None and devices is None:
return False
mac_names = {device.get('mac-address'): device.get('host-name')
for device in device_names
if device.get('mac-address')}
mac_names = {device.get('mac-address'): device.get('host-name')
for device in device_names
if device.get('mac-address')}
if self.wireless_exist:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in devices
}
else:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
if self.wireless_exist:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in devices
}
else:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
return True
return True

View File

@@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear/
"""
import logging
import threading
from datetime import timedelta
import voluptuous as vol
@@ -15,14 +13,11 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
REQUIREMENTS = ['pynetgear==0.3.3']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_HOST = 'routerlogin.net'
DEFAULT_USER = 'admin'
DEFAULT_PORT = 5000
@@ -56,8 +51,6 @@ class NetgearDeviceScanner(DeviceScanner):
import pynetgear
self.last_results = []
self.lock = threading.Lock()
self._api = pynetgear.Netgear(password, host, username, port)
_LOGGER.info("Logging in")
@@ -85,7 +78,6 @@ class NetgearDeviceScanner(DeviceScanner):
except StopIteration:
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Retrieve latest information from the Netgear router.
@@ -94,12 +86,11 @@ class NetgearDeviceScanner(DeviceScanner):
if not self.success_init:
return
with self.lock:
_LOGGER.info("Scanning")
_LOGGER.info("Scanning")
results = self._api.get_attached_devices()
results = self._api.get_attached_devices()
if results is None:
_LOGGER.warning("Error scanning devices")
if results is None:
_LOGGER.warning("Error scanning devices")
self.last_results = results or []
self.last_results = results or []

View File

@@ -4,11 +4,11 @@ Support for scanning a network with nmap.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_tracker/
"""
from datetime import timedelta
import logging
import re
import subprocess
from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
@@ -17,7 +17,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOSTS
from homeassistant.util import Throttle
REQUIREMENTS = ['python-nmap==0.6.1']
@@ -29,8 +28,6 @@ CONF_HOME_INTERVAL = 'home_interval'
CONF_OPTIONS = 'scan_options'
DEFAULT_OPTIONS = '-F --host-timeout 5s'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOSTS): cv.ensure_list,
@@ -97,7 +94,6 @@ class NmapDeviceScanner(DeviceScanner):
return filter_named[0]
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Scan the network for devices.

View File

@@ -21,7 +21,7 @@ from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.1']
REQUIREMENTS = ['libnacl==1.5.2']
_LOGGER = logging.getLogger(__name__)
@@ -353,12 +353,20 @@ def _parse_see_args(topic, data):
kwargs = {
'dev_id': dev_id,
'host_name': host_name,
'gps': (data[WAYPOINT_LAT_KEY], data[WAYPOINT_LON_KEY])
'gps': (data[WAYPOINT_LAT_KEY], data[WAYPOINT_LON_KEY]),
'attributes': {}
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
if 'batt' in data:
kwargs['battery'] = data['batt']
if 'vel' in data:
kwargs['attributes']['velocity'] = data['vel']
if 'tid' in data:
kwargs['attributes']['tid'] = data['tid']
if 'addr' in data:
kwargs['attributes']['address'] = data['addr']
return dev_id, kwargs

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.sky_hub/
"""
import logging
import re
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -16,13 +14,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string
})
@@ -43,11 +38,7 @@ class SkyHubDeviceScanner(DeviceScanner):
"""Initialise the scanner."""
_LOGGER.info("Initialising Sky Hub")
self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/'.format(self.host)
# Test the router is accessible
@@ -62,17 +53,15 @@ class SkyHubDeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
if not self.last_results:
return None
if not self.last_results:
return None
return self.last_results.get(device)
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the Sky Hub is up to date.
@@ -81,18 +70,17 @@ class SkyHubDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
_LOGGER.info("Scanning")
data = _get_skyhub_data(self.url)
data = _get_skyhub_data(self.url)
if not data:
_LOGGER.warning('Error scanning devices')
return False
if not data:
_LOGGER.warning('Error scanning devices')
return False
self.last_results = data
self.last_results = data
return True
return True
def _get_skyhub_data(url):

View File

@@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.snmp/
"""
import binascii
import logging
import threading
from datetime import timedelta
import voluptuous as vol
@@ -15,11 +13,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.8']
REQUIREMENTS = ['pysnmp==4.3.9']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
@@ -28,8 +25,6 @@ CONF_BASEOID = 'baseoid'
DEFAULT_COMMUNITY = 'public'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
@@ -68,9 +63,6 @@ class SnmpScanner(DeviceScanner):
privProtocol=cfg.usmAesCfb128Protocol
)
self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID])
self.lock = threading.Lock()
self.last_results = []
# Test the router is accessible
@@ -90,7 +82,6 @@ class SnmpScanner(DeviceScanner):
# We have no names
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the device is up to date.
@@ -99,13 +90,12 @@ class SnmpScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
data = self.get_snmp_data()
if not data:
return False
data = self.get_snmp_data()
if not data:
return False
self.last_results = data
return True
self.last_results = data
return True
def get_snmp_data(self):
"""Fetch MAC addresses from access point via SNMP."""

View File

@@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.swisscom/
"""
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -15,9 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -41,9 +36,6 @@ class SwisscomDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible.
@@ -64,7 +56,6 @@ class SwisscomDeviceScanner(DeviceScanner):
return client['host']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the Swisscom router is up to date.
@@ -73,16 +64,15 @@ class SwisscomDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Loading data from Swisscom Internet Box")
data = self.get_swisscom_data()
if not data:
return False
_LOGGER.info("Loading data from Swisscom Internet Box")
data = self.get_swisscom_data()
if not data:
return False
active_clients = [client for client in data.values() if
client['status']]
self.last_results = active_clients
return True
active_clients = [client for client in data.values() if
client['status']]
self.last_results = active_clients
return True
def get_swisscom_data(self):
"""Retrieve data from Swisscom and return parsed result."""

View File

@@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.thomson/
import logging
import re
import telnetlib
import threading
from datetime import timedelta
import voluptuous as vol
@@ -16,9 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
@@ -54,9 +49,6 @@ class ThomsonDeviceScanner(DeviceScanner):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible.
@@ -77,7 +69,6 @@ class ThomsonDeviceScanner(DeviceScanner):
return client['host']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the THOMSON router is up to date.
@@ -86,17 +77,16 @@ class ThomsonDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
data = self.get_thomson_data()
if not data:
return False
_LOGGER.info("Checking ARP")
data = self.get_thomson_data()
if not data:
return False
# Flag C stands for CONNECTED
active_clients = [client for client in data.values() if
client['status'].find('C') != -1]
self.last_results = active_clients
return True
# Flag C stands for CONNECTED
active_clients = [client for client in data.values() if
client['status'].find('C') != -1]
self.last_results = active_clients
return True
def get_thomson_data(self):
"""Retrieve data from THOMSON and return parsed result."""

View File

@@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.tomato/
import json
import logging
import re
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -17,9 +15,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
CONF_HTTP_ID = 'http_id'
@@ -54,8 +49,6 @@ class TomatoDeviceScanner(DeviceScanner):
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.logger = logging.getLogger("{}.{}".format(__name__, "Tomato"))
self.lock = threading.Lock()
self.last_results = {"wldev": [], "dhcpd_lease": []}
self.success_init = self._update_tomato_info()
@@ -76,50 +69,48 @@ class TomatoDeviceScanner(DeviceScanner):
return filter_named[0]
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self):
"""Ensure the information from the Tomato router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
self.logger.info("Scanning")
self.logger.info("Scanning")
try:
response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values.
if response.status_code == 200:
try:
response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values.
if response.status_code == 200:
for param, value in \
self.parse_api_pattern.findall(response.text):
for param, value in \
self.parse_api_pattern.findall(response.text):
if param == 'wldev' or param == 'dhcpd_lease':
self.last_results[param] = \
json.loads(value.replace("'", '"'))
return True
if param == 'wldev' or param == 'dhcpd_lease':
self.last_results[param] = \
json.loads(value.replace("'", '"'))
return True
elif response.status_code == 401:
# Authentication error
self.logger.exception((
"Failed to authenticate, "
"please check your username and password"))
return False
except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or
# an invalid http_id was supplied.
self.logger.exception("Failed to connect to the router or "
"invalid http_id supplied")
elif response.status_code == 401:
# Authentication error
self.logger.exception((
"Failed to authenticate, "
"please check your username and password"))
return False
except requests.exceptions.Timeout:
# We get this if we could not connect to the router or
# an invalid http_id was supplied.
self.logger.exception("Connection to the router timed out")
return False
except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or
# an invalid http_id was supplied.
self.logger.exception("Failed to connect to the router or "
"invalid http_id supplied")
return False
except ValueError:
# If JSON decoder could not parse the response.
self.logger.exception("Failed to parse response from router")
return False
except requests.exceptions.Timeout:
# We get this if we could not connect to the router or
# an invalid http_id was supplied.
self.logger.exception("Connection to the router timed out")
return False
except ValueError:
# If JSON decoder could not parse the response.
self.logger.exception("Failed to parse response from router")
return False

View File

@@ -8,8 +8,7 @@ import base64
import hashlib
import logging
import re
import threading
from datetime import timedelta, datetime
from datetime import datetime
import requests
import voluptuous as vol
@@ -18,9 +17,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -59,7 +55,6 @@ class TplinkDeviceScanner(DeviceScanner):
self.password = password
self.last_results = {}
self.lock = threading.Lock()
self.success_init = self._update_info()
def scan_devices(self):
@@ -72,28 +67,26 @@ class TplinkDeviceScanner(DeviceScanner):
"""Get firmware doesn't save the name of the wireless device."""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host)
referer = 'http://{}'.format(self.host)
page = requests.get(
url, auth=(self.username, self.password),
headers={'referer': referer}, timeout=4)
url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host)
referer = 'http://{}'.format(self.host)
page = requests.get(
url, auth=(self.username, self.password),
headers={'referer': referer}, timeout=4)
result = self.parse_macs.findall(page.text)
result = self.parse_macs.findall(page.text)
if result:
self.last_results = [mac.replace("-", ":") for mac in result]
return True
if result:
self.last_results = [mac.replace("-", ":") for mac in result]
return True
return False
return False
class Tplink2DeviceScanner(TplinkDeviceScanner):
@@ -109,48 +102,46 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
"""Get firmware doesn't save the name of the wireless device."""
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host)
referer = 'http://{}'.format(self.host)
url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host)
referer = 'http://{}'.format(self.host)
# Router uses Authorization cookie instead of header
# Let's create the cookie
username_password = '{}:{}'.format(self.username, self.password)
b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii')
).decode('ascii')
cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password)
# Router uses Authorization cookie instead of header
# Let's create the cookie
username_password = '{}:{}'.format(self.username, self.password)
b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii')
).decode('ascii')
cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password)
response = requests.post(
url, headers={'referer': referer, 'cookie': cookie},
timeout=4)
try:
result = response.json().get('data')
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False
if result:
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
return True
response = requests.post(
url, headers={'referer': referer, 'cookie': cookie},
timeout=4)
try:
result = response.json().get('data')
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False
if result:
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
return True
return False
class Tplink3DeviceScanner(TplinkDeviceScanner):
"""This class queries the Archer C9 router with version 150811 or high."""
@@ -202,70 +193,67 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
response.text)
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens()
if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,
params={'operation': 'load'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth},
timeout=5)
response = requests.post(url,
params={'operation': 'load'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth},
timeout=5)
try:
json_response = response.json()
try:
json_response = response.json()
if json_response.get('success'):
result = response.json().get('data')
else:
if json_response.get('errorcode') == 'timeout':
_LOGGER.info("Token timed out. Relogging on next scan")
self.stok = ''
self.sysauth = ''
return False
_LOGGER.error(
"An unknown error happened while fetching data")
if json_response.get('success'):
result = response.json().get('data')
else:
if json_response.get('errorcode') == 'timeout':
_LOGGER.info("Token timed out. Relogging on next scan")
self.stok = ''
self.sysauth = ''
return False
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct")
_LOGGER.error(
"An unknown error happened while fetching data")
return False
if result:
self.last_results = {
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct")
return False
if result:
self.last_results = {
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
return False
def _log_out(self):
with self.lock:
_LOGGER.info("Logging out of router admin interface...")
_LOGGER.info("Logging out of router admin interface...")
url = ('http://{}/cgi-bin/luci/;stok={}/admin/system?'
'form=logout').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
url = ('http://{}/cgi-bin/luci/;stok={}/admin/system?'
'form=logout').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
requests.post(url,
params={'operation': 'write'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth})
self.stok = ''
self.sysauth = ''
requests.post(url,
params={'operation': 'write'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth})
self.stok = ''
self.sysauth = ''
class Tplink4DeviceScanner(TplinkDeviceScanner):
@@ -318,38 +306,36 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
_LOGGER.error("Couldn't fetch auth tokens")
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
if (self.credentials == '') or (self.token == ''):
self._get_auth_tokens()
if (self.credentials == '') or (self.token == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
mac_results = []
mac_results = []
# Check both the 2.4GHz and 5GHz client list URLs
for clients_url in ('WlanStationRpm.htm', 'WlanStationRpm_5g.htm'):
url = 'http://{}/{}/userRpm/{}' \
.format(self.host, self.token, clients_url)
referer = 'http://{}'.format(self.host)
cookie = 'Authorization=Basic {}'.format(self.credentials)
# Check both the 2.4GHz and 5GHz client list URLs
for clients_url in ('WlanStationRpm.htm', 'WlanStationRpm_5g.htm'):
url = 'http://{}/{}/userRpm/{}' \
.format(self.host, self.token, clients_url)
referer = 'http://{}'.format(self.host)
cookie = 'Authorization=Basic {}'.format(self.credentials)
page = requests.get(url, headers={
'cookie': cookie,
'referer': referer
})
mac_results.extend(self.parse_macs.findall(page.text))
page = requests.get(url, headers={
'cookie': cookie,
'referer': referer
})
mac_results.extend(self.parse_macs.findall(page.text))
if not mac_results:
return False
if not mac_results:
return False
self.last_results = [mac.replace("-", ":") for mac in mac_results]
return True
self.last_results = [mac.replace("-", ":") for mac in mac_results]
return True
class Tplink5DeviceScanner(TplinkDeviceScanner):
@@ -365,68 +351,67 @@ class Tplink5DeviceScanner(TplinkDeviceScanner):
"""Get firmware doesn't save the name of the wireless device."""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link AP is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
base_url = 'http://{}'.format(self.host)
base_url = 'http://{}'.format(self.host)
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;"
" rv:53.0) Gecko/20100101 Firefox/53.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; "
"charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": "http://" + self.host + "/",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
}
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;"
" rv:53.0) Gecko/20100101 Firefox/53.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; "
"charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": "http://" + self.host + "/",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
}
password_md5 = hashlib.md5(self.password).hexdigest().upper()
password_md5 = hashlib.md5(
self.password.encode('utf')).hexdigest().upper()
# create a session to handle cookie easier
session = requests.session()
session.get(base_url, headers=header)
# create a session to handle cookie easier
session = requests.session()
session.get(base_url, headers=header)
login_data = {"username": self.username, "password": password_md5}
session.post(base_url, login_data, headers=header)
login_data = {"username": self.username, "password": password_md5}
session.post(base_url, login_data, headers=header)
# a timestamp is required to be sent as get parameter
timestamp = int(datetime.now().timestamp() * 1e3)
# a timestamp is required to be sent as get parameter
timestamp = int(datetime.now().timestamp() * 1e3)
client_list_url = '{}/data/monitor.client.client.json'.format(
base_url)
client_list_url = '{}/data/monitor.client.client.json'.format(
base_url)
get_params = {
'operation': 'load',
'_': timestamp
}
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
get_params = {
'operation': 'load',
'_': timestamp
}
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
return False

View File

@@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.ubus/
import json
import logging
import re
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -17,12 +15,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
from homeassistant.exceptions import HomeAssistantError
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -70,7 +64,6 @@ class UbusDeviceScanner(DeviceScanner):
self.password = config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/ubus'.format(host)
@@ -87,35 +80,33 @@ class UbusDeviceScanner(DeviceScanner):
return self.last_results
@_refresh_on_acccess_denied
def get_device_name(self, device):
def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.leasefile is None:
result = _req_json_rpc(
self.url, self.session_id, 'call', 'uci', 'get',
config="dhcp", type="dnsmasq")
if result:
values = result["values"].values()
self.leasefile = next(iter(values))["leasefile"]
else:
return
if self.leasefile is None:
result = _req_json_rpc(
self.url, self.session_id, 'call', 'uci', 'get',
config="dhcp", type="dnsmasq")
if result:
values = result["values"].values()
self.leasefile = next(iter(values))["leasefile"]
else:
return
if self.mac2name is None:
result = _req_json_rpc(
self.url, self.session_id, 'call', 'file', 'read',
path=self.leasefile)
if result:
self.mac2name = dict()
for line in result["data"].splitlines():
hosts = line.split(" ")
self.mac2name[hosts[1].upper()] = hosts[3]
else:
# Error, handled in the _req_json_rpc
return
if self.mac2name is None:
result = _req_json_rpc(
self.url, self.session_id, 'call', 'file', 'read',
path=self.leasefile)
if result:
self.mac2name = dict()
for line in result["data"].splitlines():
hosts = line.split(" ")
self.mac2name[hosts[1].upper()] = hosts[3]
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
return self.mac2name.get(mac.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
@_refresh_on_acccess_denied
def _update_info(self):
"""Ensure the information from the Luci router is up to date.
@@ -125,25 +116,24 @@ class UbusDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
if not self.hostapd:
hostapd = _req_json_rpc(
self.url, self.session_id, 'list', 'hostapd.*', '')
self.hostapd.extend(hostapd.keys())
if not self.hostapd:
hostapd = _req_json_rpc(
self.url, self.session_id, 'list', 'hostapd.*', '')
self.hostapd.extend(hostapd.keys())
self.last_results = []
results = 0
for hostapd in self.hostapd:
result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients')
self.last_results = []
results = 0
for hostapd in self.hostapd:
result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients')
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
return bool(results)
return bool(results)
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):

View File

@@ -8,7 +8,6 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
@@ -48,14 +47,13 @@ def get_scanner(hass, config):
port = config[DOMAIN].get(CONF_PORT)
verify_ssl = config[DOMAIN].get(CONF_VERIFY_SSL)
persistent_notification = loader.get_component('persistent_notification')
try:
ctrl = Controller(host, username, password, port, version='v4',
site_id=site_id, ssl_verify=verify_ssl)
except APIError as ex:
_LOGGER.error("Failed to connect to Unifi: %s", ex)
persistent_notification.create(
hass, 'Failed to connect to Unifi. '
hass.components.persistent_notification.create(
'Failed to connect to Unifi. '
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),

View File

@@ -9,8 +9,7 @@ import logging
from homeassistant.util import slugify
from homeassistant.helpers.dispatcher import (
dispatcher_connect, dispatcher_send)
from homeassistant.components.volvooncall import (
DATA_KEY, SIGNAL_VEHICLE_SEEN)
from homeassistant.components.volvooncall import DATA_KEY, SIGNAL_VEHICLE_SEEN
_LOGGER = logging.getLogger(__name__)

View File

@@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.xiaomi/
"""
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
@@ -15,12 +13,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME, default='admin'): cv.string,
@@ -47,8 +42,6 @@ class XiaomiDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
self.token = _get_token(self.host, self.username, self.password)
@@ -62,21 +55,19 @@ class XiaomiDeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.mac2name is None:
result = self._retrieve_list_with_retry()
if result:
hosts = [x for x in result
if 'mac' in x and 'name' in x]
mac2name_list = [
(x['mac'].upper(), x['name']) for x in hosts]
self.mac2name = dict(mac2name_list)
else:
# Error, handled in the _retrieve_list_with_retry
return
return self.mac2name.get(device.upper(), None)
if self.mac2name is None:
result = self._retrieve_list_with_retry()
if result:
hosts = [x for x in result
if 'mac' in x and 'name' in x]
mac2name_list = [
(x['mac'].upper(), x['name']) for x in hosts]
self.mac2name = dict(mac2name_list)
else:
# Error, handled in the _retrieve_list_with_retry
return
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the informations from the router are up to date.
@@ -85,12 +76,11 @@ class XiaomiDeviceScanner(DeviceScanner):
if not self.success_init:
return False
with self.lock:
result = self._retrieve_list_with_retry()
if result:
self._store_result(result)
return True
return False
result = self._retrieve_list_with_retry()
if result:
self._store_result(result)
return True
return False
def _retrieve_list_with_retry(self):
"""Retrieve the device list with a retry if token is invalid.

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==1.0.1']
REQUIREMENTS = ['netdisco==1.1.0']
DOMAIN = 'discovery'
@@ -33,6 +33,7 @@ SERVICE_IKEA_TRADFRI = 'ikea_tradfri'
SERVICE_HASSIO = 'hassio'
SERVICE_AXIS = 'axis'
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_WINK = 'wink'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@@ -42,6 +43,7 @@ SERVICE_HANDLERS = {
SERVICE_HASSIO: ('hassio', None),
SERVICE_AXIS: ('axis', None),
SERVICE_APPLE_TV: ('apple_tv', None),
SERVICE_WINK: ('wink', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
@@ -57,7 +59,9 @@ SERVICE_HANDLERS = {
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'harmony': ('remote', 'harmony'),
'sabnzbd': ('sensor', 'sabnzbd'),
'bose_soundtouch': ('media_player', 'soundtouch'),
'bluesound': ('media_player', 'bluesound'),
}
CONF_IGNORE = 'ignore'

View File

@@ -13,7 +13,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
CONF_DEVICES
REQUIREMENTS = ['libpurecoollink==0.2.0']
REQUIREMENTS = ['libpurecoollink==0.4.2']
_LOGGER = logging.getLogger(__name__)
@@ -69,14 +69,17 @@ def setup(hass, config):
dyson_device = next((d for d in dyson_devices if
d.serial == device["device_id"]), None)
if dyson_device:
connected = dyson_device.connect(None, device["device_ip"],
timeout, retry)
if connected:
_LOGGER.info("Connected to device %s", dyson_device)
hass.data[DYSON_DEVICES].append(dyson_device)
else:
_LOGGER.warning("Unable to connect to device %s",
dyson_device)
try:
connected = dyson_device.connect(device["device_ip"])
if connected:
_LOGGER.info("Connected to device %s", dyson_device)
hass.data[DYSON_DEVICES].append(dyson_device)
else:
_LOGGER.warning("Unable to connect to device %s",
dyson_device)
except OSError as ose:
_LOGGER.error("Unable to connect to device %s: %s",
str(dyson_device.network_device), str(ose))
else:
_LOGGER.warning(
"Unable to find device %s in Dyson account",
@@ -86,7 +89,7 @@ def setup(hass, config):
for device in dyson_devices:
_LOGGER.info("Trying to connect to device %s with timeout=%i "
"and retry=%i", device, timeout, retry)
connected = device.connect(None, None, timeout, retry)
connected = device.auto_connect(timeout, retry)
if connected:
_LOGGER.info("Connected to device %s", device)
hass.data[DYSON_DEVICES].append(device)
@@ -98,5 +101,6 @@ def setup(hass, config):
_LOGGER.debug("Starting sensor/fan components")
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
discovery.load_platform(hass, "fan", DOMAIN, {}, config)
discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
return True

View File

@@ -193,7 +193,9 @@ class Config(object):
if entity_id == ent_id:
return number
number = str(len(self.numbers) + 1)
number = '1'
if self.numbers:
number = str(max(int(k) for k in self.numbers) + 1)
self.numbers[number] = entity_id
self._save_numbers_json()
return number

View File

@@ -17,6 +17,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE,
SERVICE_TURN_OFF, ATTR_ENTITY_ID,
STATE_UNKNOWN)
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@@ -118,6 +119,7 @@ SERVICE_TO_METHOD = {
}
@bind_hass
def is_on(hass, entity_id: str=None) -> bool:
"""Return if the fans are on based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_FANS
@@ -125,6 +127,7 @@ def is_on(hass, entity_id: str=None) -> bool:
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, STATE_UNKNOWN]
@bind_hass
def turn_on(hass, entity_id: str=None, speed: str=None) -> None:
"""Turn all or specified fan on."""
data = {
@@ -137,6 +140,7 @@ def turn_on(hass, entity_id: str=None, speed: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
@bind_hass
def turn_off(hass, entity_id: str=None) -> None:
"""Turn all or specified fan off."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -144,6 +148,7 @@ def turn_off(hass, entity_id: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
@bind_hass
def toggle(hass, entity_id: str=None) -> None:
"""Toggle all or specified fans."""
data = {
@@ -153,6 +158,7 @@ def toggle(hass, entity_id: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
@bind_hass
def oscillate(hass, entity_id: str=None, should_oscillate: bool=True) -> None:
"""Set oscillation on all or specified fan."""
data = {
@@ -165,6 +171,7 @@ def oscillate(hass, entity_id: str=None, should_oscillate: bool=True) -> None:
hass.services.call(DOMAIN, SERVICE_OSCILLATE, data)
@bind_hass
def set_speed(hass, entity_id: str=None, speed: str=None) -> None:
"""Set speed for all or specified fan."""
data = {
@@ -177,6 +184,7 @@ def set_speed(hass, entity_id: str=None, speed: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_SET_SPEED, data)
@bind_hass
def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
"""Set direction for all or specified fan."""
data = {

View File

@@ -36,7 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[DYSON_FAN_DEVICES] = []
# Get Dyson Devices from parent component
for device in hass.data[DYSON_DEVICES]:
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
for device in [d for d in hass.data[DYSON_DEVICES] if
isinstance(d, DysonPureCoolLink)]:
dyson_entity = DysonPureCoolLinkDevice(hass, device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
@@ -83,8 +85,8 @@ class DysonPureCoolLinkDevice(FanEntity):
def on_message(self, message):
"""Called when new messages received from the fan."""
from libpurecoollink.dyson import DysonState
if isinstance(message, DysonState):
from libpurecoollink.dyson_pure_state import DysonPureCoolState
if isinstance(message, DysonPureCoolState):
_LOGGER.debug("Message received for fan device %s : %s", self.name,
message)
self.schedule_update_ha_state()

View File

@@ -0,0 +1,187 @@
"""
Support for Velbus platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.velbus/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.fan import (
SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, FanEntity, SUPPORT_SET_SPEED,
PLATFORM_SCHEMA)
from homeassistant.components.velbus import DOMAIN
from homeassistant.const import CONF_NAME, CONF_DEVICES, STATE_OFF
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['velbus']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [
{
vol.Required('module'): cv.positive_int,
vol.Required('channel_low'): cv.positive_int,
vol.Required('channel_medium'): cv.positive_int,
vol.Required('channel_high'): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
}
])
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Fans."""
velbus = hass.data[DOMAIN]
add_devices(VelbusFan(fan, velbus) for fan in config[CONF_DEVICES])
class VelbusFan(FanEntity):
"""Representation of a Velbus Fan."""
def __init__(self, fan, velbus):
"""Initialize a Velbus light."""
self._velbus = velbus
self._name = fan[CONF_NAME]
self._module = fan['module']
self._channel_low = fan['channel_low']
self._channel_medium = fan['channel_medium']
self._channel_high = fan['channel_high']
self._channels = [self._channel_low, self._channel_medium,
self._channel_high]
self._channels_state = [False, False, False]
self._speed = STATE_OFF
@asyncio.coroutine
def async_added_to_hass(self):
"""Add listener for Velbus messages on bus."""
def _init_velbus():
"""Initialize Velbus on startup."""
self._velbus.subscribe(self._on_message)
self.get_status()
yield from self.hass.async_add_job(_init_velbus)
def _on_message(self, message):
import velbus
if isinstance(message, velbus.RelayStatusMessage) and \
message.address == self._module and \
message.channel in self._channels:
if message.channel == self._channel_low:
self._channels_state[0] = message.is_on()
elif message.channel == self._channel_medium:
self._channels_state[1] = message.is_on()
elif message.channel == self._channel_high:
self._channels_state[2] = message.is_on()
self._calculate_speed()
self.schedule_update_ha_state()
def _calculate_speed(self):
if self._is_off():
self._speed = STATE_OFF
elif self._is_low():
self._speed = SPEED_LOW
elif self._is_medium():
self._speed = SPEED_MEDIUM
elif self._is_high():
self._speed = SPEED_HIGH
def _is_off(self):
return self._channels_state[0] is False and \
self._channels_state[1] is False and \
self._channels_state[2] is False
def _is_low(self):
return self._channels_state[0] is True and \
self._channels_state[1] is False and \
self._channels_state[2] is False
def _is_medium(self):
return self._channels_state[0] is True and \
self._channels_state[1] is True and \
self._channels_state[2] is False
def _is_high(self):
return self._channels_state[0] is True and \
self._channels_state[1] is False and \
self._channels_state[2] is True
@property
def name(self):
"""Return the display name of this light."""
return self._name
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def speed(self):
"""Return the current speed."""
return self._speed
@property
def speed_list(self):
"""Get the list of available speeds."""
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed, **kwargs):
"""Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
self.set_speed(speed)
def turn_off(self):
"""Turn off the entity."""
self.set_speed(STATE_OFF)
def set_speed(self, speed):
"""Set the speed of the fan."""
channels_off = []
channels_on = []
if speed == STATE_OFF:
channels_off = self._channels
elif speed == SPEED_LOW:
channels_off = [self._channel_medium, self._channel_high]
channels_on = [self._channel_low]
elif speed == SPEED_MEDIUM:
channels_off = [self._channel_high]
channels_on = [self._channel_low, self._channel_medium]
elif speed == SPEED_HIGH:
channels_off = [self._channel_medium]
channels_on = [self._channel_low, self._channel_high]
for channel in channels_off:
self._relay_off(channel)
for channel in channels_on:
self._relay_on(channel)
self.schedule_update_ha_state()
def _relay_on(self, channel):
import velbus
message = velbus.SwitchRelayOnMessage()
message.set_defaults(self._module)
message.relay_channels = [channel]
self._velbus.send(message)
def _relay_off(self, channel):
import velbus
message = velbus.SwitchRelayOffMessage()
message.set_defaults(self._module)
message.relay_channels = [channel]
self._velbus.send(message)
def get_status(self):
"""Retrieve current status."""
import velbus
message = velbus.ModuleStatusRequestMessage()
message.set_defaults(self._module)
message.channels = self._channels
self._velbus.send(message)
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_SET_SPEED

View File

@@ -9,7 +9,8 @@ import logging
from homeassistant.components.fan import (FanEntity, SPEED_HIGH,
SPEED_LOW, SPEED_MEDIUM,
STATE_UNKNOWN)
STATE_UNKNOWN, SUPPORT_SET_SPEED,
SUPPORT_DIRECTION)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.wink import WinkDevice, DOMAIN
@@ -20,6 +21,8 @@ _LOGGER = logging.getLogger(__name__)
SPEED_LOWEST = 'lowest'
SPEED_AUTO = 'auto'
SUPPORTED_FEATURES = SUPPORT_DIRECTION + SUPPORT_SET_SPEED
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Wink platform."""
@@ -44,11 +47,11 @@ class WinkFanDevice(WinkDevice, FanEntity):
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
self.wink.set_fan_speed(speed)
self.wink.set_state(True, speed)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
self.wink.set_state(True)
self.wink.set_state(True, speed)
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn off the fan."""
@@ -96,3 +99,8 @@ class WinkFanDevice(WinkDevice, FanEntity):
if SPEED_HIGH in wink_supported_speeds:
supported_speeds.append(SPEED_HIGH)
return supported_speeds
@property
def supported_features(self: ToggleEntity) -> int:
"""Flag supported features."""
return SUPPORTED_FEATURES

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