Compare commits

..

272 Commits
0.46 ... 0.48

Author SHA1 Message Date
Paulus Schoutsen
7461c57542 Merge pull request #8270 from home-assistant/release-0-48
0.48
2017-07-01 16:58:10 -07:00
Paulus Schoutsen
632f9a21b6 Update frontend 2017-07-01 16:53:50 -07:00
Mike Megally
da44f80b32 Create an index on the states table to help hass startup time (#8255) 2017-07-01 14:10:39 -07:00
Michael Fester
8fb49e8687 Snips ASR and NLU component (#8156)
* Snips ASR and NLU component

* Fix warning

* Fix warnings

* Fix lint issues

* Add tests

* Fix tabs

* Fix newline

* Fix quotes

* Fix docstrings

* Update tests

* Remove logs

* Fix lint warning

* Update API

* Fix Snips
2017-07-01 13:58:35 -07:00
Will W
5f8dc8af20 components.knx - KNXMultiAddressDevice corrections (#8275)
1. The has_attributes was comparing names to addresses
2. Some errors outside of an except block were using
_LOGGER.except. This will cause an exception itself because there is no
trance context available to the logger
3. Added alias names for the address and state addresses so that they
can be accessed with the same
4. Added return values for the set_int_value and set_percentage methods
to allow error checking similar to the set_value method
5. Added the name of the configured object to the log messages to make
them more meaningful (otherwise multiple similar log messages are
received without any hint as to the target device)
2017-07-01 13:31:01 -07:00
lrmate
d267fc608f Update modbus.py (#8256)
* Update modbus.py

Prevents Modbus binary sensors showing up as "unnamed_device".
Originally proposed here https://community.home-assistant.io/t/modbus-sensor/6751/11 by user Pjeter

* Update modbus.py
2017-06-30 23:56:46 -07:00
Paulus Schoutsen
d3bc8519c0 Update frontend 2017-06-30 22:34:55 -07:00
viswa-swami
d3adc6ddfb Camera services arm disarm including Netgear Arlo (#7961)
* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Fixed the spaces and indentation related issues that houndci found

* Fixed the spaces and indentation related issues that houndci found

* Missed the const file which has the macros defined.

* Fixed the CI build error

* Fixed the CI build error because of unused variable in exception case

* Updating the arlo code based on comment from @balloob. Changed the arm and disarm to enable_motion_detection and disable_motion_detection respectively. Similarly fixed the AttributeError handling. Added dummy code to the demo camera also. Moved out the definitions in const.py into the camera __init__ file

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot for demo.py: expected 2 lines, found 1

* Updated code in camera __init__.py to use the get function instead of directly calling the member in the structure.

* Updated code in camera __init__.py

* Posting the updated code for PR based on @balloob's suggestions/recommendations

* Removed the arlo reference from demo code. Copy-paste error

* Removed the unused import found by hound bot

* Expected 2 lines before function, but found only 1.

* Based on @balloob's comments, moved these constants to the camera/arlo.py

* Added test_demo.py to test the motion enabled and motion disabled in camera component

* Fixing issues found by houndci-bot

* Fixing issues found by houndci-bot

* Fixing the code as per @balloob's suggestions

* Fixing the code as per @balloob's suggestions

* Fixing the test_demo failure. Tried to rewrite a base function to enable the motion in __init__.py and missed to add it to as a job.

* Fixing the hound bot comment

* Update arlo.py

* Update arlo.py
2017-06-30 22:24:36 -07:00
Josh
a3f586d097 Adding done_message to alert (#8116)
* Adding done_message to alert

Adding an optional entry to the config that will send a notification when an
alarm goes from on to off.

* Update test_alert.py

* Update test_alert.py
2017-06-30 22:24:36 -07:00
Paulus Schoutsen
f8c7fd212f Revert "Make Android app shortcut use 'Home Assistant' as name." (#8271)
* Revert "Version bump to 0.49.0.dev0 (#8266)"

This reverts commit 8e4394f173.

* Revert "Adding done_message to alert (#8116)"

This reverts commit 5e56bc7464.

* Revert "Camera services arm disarm including Netgear Arlo (#7961)"

This reverts commit ed20f7e359.

* Revert "Make Android app shortcut use 'Home Assistant' as name instead of just 'Assistant'. (#8261)"

This reverts commit 0bcb7839fb.
2017-06-30 22:24:36 -07:00
Paulus Schoutsen
b1f3492fd0 Notify.smtp: default to STARTTLS 2017-06-30 22:15:41 -07:00
Fabian Affolter
74acc5cf41 Merge branch 'master' into dev 2017-06-30 18:56:26 +02:00
Michaël Arnauts
0bcb7839fb Make Android app shortcut use 'Home Assistant' as name instead of just 'Assistant'. (#8261) 2017-06-30 18:51:07 +02:00
PhracturedBlue
17237e9d3f Implement templates for covers (#8100)
* Implement templates for covers

* Fix a few remaining pylint warnings

* Fix hound line-length warnings

* Fix one more hound line-length warning

* Fix quadruple-quotes an line length code-quality issues

* Irrelevant change to retrigger travis due to timeout

* Use volutuous Exclusive to check for mutex condition

* Fix incorrect state check
2017-06-30 08:24:29 -07:00
Michaël Arnauts
a663dbada0 Docker cleanup. (#8226) 2017-06-30 08:07:33 -07:00
Fabian Affolter
96e1d5524a Upgrade libnacl to 1.5.1 (#8259) 2017-06-30 11:12:21 +02:00
JudgeDredd
33fd2250fd further document add_node_secure (#8229)
added documentation to *attempt* explanation that add_node_secure will also function for adding unsecure nodes.
2017-06-30 10:00:38 +03:00
Per Sandström
31f17a91e6 verisure component names (#8251) 2017-06-30 08:53:14 +02:00
Andrey
d0720ac699 Add PlatformNotReady support for Sensibo (#8252) 2017-06-30 08:50:25 +02:00
Fabian Affolter
05acf1c10a Use constant and update ordering (#8246) 2017-06-30 08:46:22 +02:00
Fabian Affolter
27c92937f2 Use 'hass.data' instead of global (#8245) 2017-06-30 08:46:03 +02:00
Anders Melchiorsen
a328df6014 LIFX: Small code cleanups (#8228) 2017-06-30 02:10:28 +02:00
Eugenio Panadero
1fb4eefc2c better logging to debug when a message is not sent (#8248) 2017-06-29 21:13:46 +02:00
Fabian Affolter
0f12b4c955 Do not call update() in constructor (#8247)
Add an optional extended description…
2017-06-29 16:21:29 +02:00
Fabian Affolter
a9f14b67a8 Update docstrings (#8244) 2017-06-29 11:44:35 +02:00
Eugenio Panadero
445065700c update i2csense requirement (#8242) 2017-06-29 11:03:52 +02:00
Fabian Affolter
4bd96fd437 Upgrade python-digitalocean to 1.12 (#8241) 2017-06-29 10:52:12 +02:00
Michaël Arnauts
5dde0c2201 Comfoconnect fan component (#8073)
* Comfoconnect fan component.

* Fix linter. Don't store hass object when not needed.

* More code style.

* Rebase to dev and add to coverage ignore list.

* Use published package from pypi.
2017-06-28 18:04:54 +02:00
Open Home Automation
6846a76c46 KNX Cover tilt control (#8159)
* Added invert flag for position for actuators that uses 100% for fully closed position

* Implementation of tilt functionality

* Bugfix check tilt

* Formatting

* Formatting fixes

* Formatting

* Bugfix set_tilt

* Minor modifications in configuration section

* Formatting

* Update knx.py
2017-06-28 14:08:07 +02:00
Fabian Affolter
fa6e93f0c7 Do not call update() in constructor (#8148)
* Do not call update() in constructor

* Move handling to update and re-add throttle

* Fix indent

* Fix interval
2017-06-27 10:56:25 +02:00
Paulus Schoutsen
5ef274adce Cleanup automations yaml (#8223) 2017-06-27 10:36:26 +02:00
Eugenio Panadero
e39f7d3ef5 Fix homeassistant.start trigger (#8220)
* Fix homeassistant.start trigger

* ooops

* set sleep(0) just before changing to running state, revert async_block_till_done changes
2017-06-27 10:36:00 +02:00
Will W
88b9503962 add percentage (DPT_Scaling) KNX sensors (#8168)
* add percentage (DPT_Scaling) KNX sensors

1. moved basic functionality to KNXSensorBaseClass instead of
KNXSensorFloatClass
2. added "if" clause in setup for a "percentage" sensor type and added KNXSensorDPTScalingClass

* support-knx-percentage-sensor: lint correction

Updated convert method base sensor class to avoid lint warning
(R201 - Method could be a function)

* added PLATFORM_SCHEMA for configuration

1. added SCHEMA extension for defined keywords
2. moved fixed data for internal settings out of sensor logic
3. moved everything into standard KNXSensor object
4. added parsing of extra config parameters in __init__

* correct lint errors on support-knx-percentage-sensor
2017-06-26 22:25:54 -07:00
Eugenio Panadero
596093d564 telegram_bot platform to only send messages (#8186)
* add new telegram_bot platform to only send messages

* Fix async
2017-06-26 22:22:33 -07:00
natemason
23400c4b0a Fixed mqtt subscription filter on sys $ topics (#8166)
* Fixed mqtt subscription filter on sys $ topics

* fixed linting issue

* added unit tests for $ topics and changed fix to use re.escape

* merge upstream/dev mqtt unit tests

* Update test_init.py
2017-06-26 22:17:55 -07:00
Anders Melchiorsen
af54311718 LIFX: Move light effects to external library (#8222)
* LIFX: Move light effects to external library

This moves the LIFX light effects to the external library aiolifx_effects.

To get the light state synchronized between that library and HA, the LIFX
platform no longer maintains the light state itself. Instead, it uses the
cached state that aiolifx maintains.

The reorganization also includes the addition of a cleanup handler.

* Fix style
2017-06-26 22:05:32 -07:00
Pascal Vizeli
442dcd584b Improve executor pool size / speedup python 3.5 (#8215)
* Improve executor pool size / speedup python36

* fix style

* Add comment
2017-06-26 18:18:42 -07:00
Eugenio Panadero
1e4aec63ed guess the content_type in local_file cameras (#8217)
* guess the content_type in local_file cameras

* add unittest to check content_type of local_file cameras
2017-06-26 22:36:35 +02:00
Per Sandström
80c187f8ea WIP: Verisure app api (#7394)
update to verisure app api
2017-06-26 22:30:25 +02:00
Paulus Schoutsen
d73b695e73 EntityComponent to retry platforms that are not ready yet (#8209)
* Add PlatformNotReady Exception

* lint

* Remove cap, adjust algorithm
2017-06-26 09:41:48 -07:00
Adam Baxter
f02d169864 Fix Plex component to use port number in discovery. (#8197)
* Fix Plex component to use port number in discovery.

* Break line

* Correctly save port to config

* Handle port with fewer code changes

* This is stuck configuring and I'm not sure why

* Changes suggested by @dale3h
2017-06-25 18:06:15 -05:00
Pascal Vizeli
2dd7f0616e Add security layer to send file output things (#8189)
* Add security layer to send file output things

* Make telegram secure

* fix lint

* fix handling

* invert check

* resolve relative paths

* add test for relative paths

* fix lint

* fix tests

* Address paulus comments

* fix style

* fix tests

* Add more tests

* fix tests

* fix tests

* fix test p2

* fix lint

* fix tests

* Make it available for windows

* Change name / address comments

* fix set

* fix test

* fix tests

* fix test

* fix lint
2017-06-26 00:10:30 +02:00
Wim Haanstra
2f2952e0ec Openhardwaremonitor (#8056)
* Open Hardware Monitor sensor

Platform which is able to connect to the JSON API of Open Hardware Monitor and adds sensors for the devices.

* Remove copyright in header, not needed.

* - Removed old code
- Fixed typo’s in comments
- Removed log spamming
- Removed code that was unnecessary
- Use requests instead of urllib
- Moved sensor update functionality to data handler, to remove unwanted constructor parameters

* Fixed typo in comment
Added tests

* Added default fixture, to stabilize tests

* - Fix for values deeper than 4 levels, no longer relies on fixed level
- Fixed tests

* Removed timer in preference of helper methods

* Moved update functionality back to Entity….
Updated SCAN INTERVAL

* Added timeout to request
Removed retry when Open Hardware Monitor API is not reachable
Fixed naming of sensors
Flow optimalisations
Fixed tests to use states

* Remove unused import
2017-06-25 13:48:05 -07:00
Adam Mills
8358542ce0 Remove unnecessary thread_ident assignment (#8194)
* Remove mocking of _thread_ident

* Re-add run_loop thread_ident assignment
2017-06-25 16:39:05 -04:00
Eugenio Panadero
4ca5ed25bc add option to set content_type in camera.generic to support 'svg cameras' (#8188)
* add custom content_type to support 'generic svg cameras'

* add unittest to check content_type for svg generic camera

* Tweak tests
2017-06-25 12:25:14 -07:00
Paulus Schoutsen
7bf6ceafec Split mock_service (#8198) 2017-06-25 10:53:15 -07:00
Paulus Schoutsen
1cfed4f015 Fix plants calling async methods from sync context (#8200) 2017-06-25 10:07:28 -07:00
Paulus Schoutsen
a082ffca1d Fix MySensors climate (#8193) 2017-06-24 18:11:34 -07:00
Oliver
1b563b0640 Pushed to version 0.5.1 of the library (#8190) 2017-06-24 12:14:57 -07:00
Adam Mills
1fe189e9cb Switch to new zwave entity ids by default (#8192) 2017-06-24 15:01:57 -04:00
Marc Plano-Lesay
edeb92ea42 Add offset option to sensor.gtfs (#7980)
* Add offset option to sensor.gtfs

* Fix long lines in sensor.gtfs

* Expose GTFS offset as an attribute
2017-06-24 17:45:14 +02:00
Alex Mekkering
c1095665e9 added optional node_id to MQTT discovery (#8096) 2017-06-24 00:46:41 -07:00
Kane610
2a1f8af10a Axis service vapix call (#7794)
* Initial commit for an Axis service to do Vapix calls to device

* Added check to see if metadatastream initiated properly

* Make sure to configure the correct IP address when setting up registered devices on system start

* Manage reconnection when device is discovered with a different IP

* Cleaned up setting new IP

* Better naming of event for new IP

* New version of dependency axis

* Fix flake8 failing

* Break out service default strings to constants

* Use the dispatcher and not the core event bus for internal communication
2017-06-24 00:14:57 -07:00
Bas Schipper
6234f2d73f Added buienradar precipitation forecast average & total sensors (#8171)
* Added precipitation forecast average & total sensors

* Fixed some code style issues

* Fixed some code style issues

* Minor fix default timeframe

* Update buienradar.py

* Update buienradar.py
2017-06-24 00:12:52 -07:00
Paulus Schoutsen
b488663f2c Update Dockerfile 2017-06-23 23:13:38 -07:00
Sean Dague
a55d8776ff Throw exception if _convert_for_display called on non Number (#8178)
In trying to come up for some reason behind issue #6365 (which only
happens on some platforms) the best guess is that some components are
managing to get a string value all the way up to the Polymer UI for
temperature, which then an increment of +0.5 is treating as a string
concat operation instead of addition. So 20 + 0.5 becomes 200.5 hits
the max thermostat value.

This will throw an exception if the climate temp value isn't a
number. That's going to turn a soft fail into a hard fail on
potentially a number of platforms. Mysensors is one of the platforms
that was reported as having the issue. So put some explicit float
casts where that might be coming from as well.
2017-06-23 23:03:37 -07:00
Ryan Nowakowski
5ceb4c404d Fix radiotherm model CT50 (#8181)
Model CT50 has an "Auto" mode.  When mode is set to auto we need to ask
what the current state is: cool or heat.  Then we can query the
appropriate target temperature.

Without this fix, the target temperature shows up blank in the UI and
setting the mode fails.
2017-06-23 22:53:10 -07:00
lrmate
0061cece0c Update buienradar.py (#8173)
Swapped unit of measurement 'winddirection' vs 'windazimuth'
2017-06-23 22:51:45 -07:00
Morten Lied Johansen
0099168ff8 Add device tracker for Linksys Smart Wifi devices (#8144)
* Add device tracker for Linksys Smart Wifi devices

* Fixing code style
2017-06-23 22:36:04 -07:00
Paulus Schoutsen
87c89752ab Revert "Add libboost-python1.62-dev (fixes #7851)" (#8182)
* Revert "Uninstall enum34 in python3.6 docker image (#8103)"

This reverts commit 45f6f4443a.

* Revert "Add libboost-python1.62-dev (fixes #7851) (#7868)"

This reverts commit f1290d3135.
2017-06-23 22:33:33 -07:00
Jean Regisser
45f6f4443a Uninstall enum34 in python3.6 docker image (#8103)
* Uninstall enum34 in python3.6 docker image

This is a short term fix for #7733

What's happening is the following dependencies are pulling enum34:
- pygatt
- libsoundtouch
- yeelight
However, enum34 is not meant to be installed in Python versions 3.4+
and causing the `AttributeError: module 'enum' has no attribute 'IntFlag'``

I've submitted patches to these projects so we don't have to do this
manual uninstall in the future.

* Update Dockerfile
2017-06-23 22:29:39 -07:00
Fabian Affolter
f1290d3135 Add libboost-python1.62-dev (fixes #7851) (#7868) 2017-06-23 22:16:19 -07:00
Omar Usman
746aae51ec Add ClickSend notify service. (#8135)
* Add ClickSend notify service.

* PR #8135 changes.

- Some code spacing fixes.
- Add timeout to requests.
- Change doc url.
- Use const.py as much as possible.
- Check credentials to determine if setup fails or not.
- Add docstrings.
- Use string formatting.

* PR #8135 changes.

- Remove unused variables.
- Continuation line under-indented for visual indent.

* PR #8135 changes.

- Format code based on PEP8.

* PR #8135 changes.

- Remove unused base64 dependency.

* PR #8135 changes.

- Fix: D205: 1 blank line required between summary line and description (found 0)
- Fix: standard import "import json" comes before "import requests"

* PR #8135 changes.

- Add files to .coveragerc

* Remove obvious comments and set constant
2017-06-23 22:51:41 +02:00
Wolfgang Malgadey
da9430ed12 Tado climate device (#8041)
* added default parameter

* zone overlay can be set with or without a temperature and with or without a duration. Duration is not supported by hass

* Fixes issue #7059 with missing sensorDataPoints

* Fixes issue #6943 added ac_mode

* ac_mode cases
* added fan modes
* changed handling of device state OFF

* fixed an error initializing a dictionary (#6943)

* changed pytado version

* activated pytado debugging

* Changed pyTado version

* mytado.com changed authentication challenge

* Fixed linelength and whitespace issues

* requirements to pytado changed
2017-06-23 18:45:44 +02:00
Tim Wilde
bef22076ea Use version 1.3 of radiotherm (#8164)
Add an optional extended description…
2017-06-23 11:08:23 +02:00
Bas Schipper
fe93b51017 Fixed rfxtrx binary_sensor off command (#8160)
* Fixed applying rfxtrx binary off command

* Fixed some deprecation issues
2017-06-22 23:00:44 +02:00
Eugenio Panadero
07293e8d1e add telegram_bot service: delete_message (#8153)
* add telegram_bot service: delete_message

* better validating for `last` message_id option
2017-06-22 15:03:11 +02:00
Open Home Automation
ca71d34076 Added invert flag for position for actuators that uses 100% for fully closed position (#8147) 2017-06-22 13:42:13 +02:00
Anton Lundin
548417761e ubus: Refresh session on Access denied (#8111)
When a openwrt router reboots, all the session ids gets invalidated.
In that case we need to log in again and get a new session id.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2017-06-22 13:34:57 +02:00
Andrey
7b8ad1d365 Switch rachiopy to pypi (#8040)
* Switch rachiopy t pypi

* Update rachio.py

* Update requirements_all.txt
2017-06-22 13:34:00 +02:00
Fabian Affolter
61cb6ec3dc Upgrade libsoundtouch to 0.6.2 (#8149) 2017-06-22 13:27:42 +02:00
Fabian Affolter
349746f5f2 Upgrade python-telegram-bot to 6.1.0 (#8151) 2017-06-22 13:27:02 +02:00
Oliver
2e3b279873 Add support of Zone2 and Zone3 (#8025)
* Add support of Zone2 and Zone3

* Changes from balloobs feedback
2017-06-21 22:54:10 -07:00
Yannick POLLART
f26861976d Rfxtrx binary sensor (#6794)
* Added rfxtrx binary sensors to a new branch

* binary_sensor/rfxtrx: added support for automatic_add

* Fixed pylint warnings

* off_delay is set wit clearer time specifiers (cv.time_period)

* fire_event config attribute is now supported by rfxtrx binary sensors.

* Cosmetic ordering

* Fix lint errors for PR requirements.

* Fixed indents, line length and comment problems.

* Yet another line too long fix...

* Using existing attributes and config constants.

* Cosmetic fix (ATTR_DATABITS -> ATTR_DATA_BITS)

* Removed unused attribute

* FIX masked device id logging message

* FIX line too long

* FIX trailing white space

* FIX: rfxtrx binary_sensor manages its own devices only.

* Added a basic config helper for pt2262 devices

* Make pylint happy

* Fixed most houndci-bot-detected issues

* Fix TOX complaint about blank line after function docstring

* Fix data bit value calculation

* Fixed line too long

* Removed unnecessary code.

* remove trailing whitespace

* Added hass property to device object.
2017-06-21 22:48:45 -07:00
Anthony Hughes
6bfeac7f80 Harmony auto discovery via netdisco (#7741)
* Use netdisco to automatically discover harmony hubs.

* Allow some settings in configuration.yml to override even on discovered hubs.

* Global is not required as no assignment on variable

* Use `set` instead of list for perf

* Store cache of discovered devices against `hass.data` rather than in global

* Handle case if the device cache is empty

* Fix indentation issue
2017-06-21 22:43:35 -07:00
Miha Lunar
a95fe588ca LimitlessLED: Configurable fade-out behavior (#7369)
* Configurable fade-out behavior

Adds a per-group "fade" option with values of "out" (default) or "none".
By default, the lights are faded out when turned off, but this can cause usability issues when manually switching wall switches, since the bulbs turn back on at minimum brightness.

* Changed fade value from enum to boolean

* No need to fall back to default since voluptuous takes care of that.
2017-06-21 22:22:24 -07:00
Eugenio Panadero
e5d11dd1a5 Add new BH1750 light level sensor (#8050)
* new sensor platform
* requirements_all and .coveragerc update
2017-06-22 07:09:08 +02:00
Eugenio Panadero
435e5c8a91 Add I2c HTU21D temperature and humidity sensor for Raspberry Pi (#8049)
* Add new HTU21D temperature and humidity sensor

* new sensor platform
* requirements_all and .coveragerc update

* fix lint

* review changes: move sensor code to external module

* remove debug log msg

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3

* Add new HTU21D temperature and humidity sensor

* new sensor platform
* requirements_all and .coveragerc update

* fix lint

* review changes: move sensor code to external module

* remove debug log msg

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3

* change style for hass

* fix requirements
2017-06-22 07:05:58 +02:00
Jose Juan Montes
8d0553d9e6 Adds CPU temp monitoring, and allow startup when endpoint is not yet available. (#8093)
* Adds CPU temp monitoring, and allow startup when endpoint is not yet available.

* Added support for available() to glances sensor.
2017-06-21 22:45:15 +02:00
Charles Blonde
9a239d1afb Upgrade libsoundtouch to prevent Python3.6 errors with enum. #7733 #8103 (#8143) 2017-06-21 22:20:30 +02:00
Paulus Schoutsen
9252854f99 Merge pull request #8141 from home-assistant/release-0-47-1
0.47.1
2017-06-21 09:24:12 -07:00
Paulus Schoutsen
8d76e2679d Allow iteration in python_script (#8134)
* Allow iteration in python_script

* Add tests
2017-06-21 09:09:08 -07:00
Alan Fischer
4b1dcad7ae Fixed iTach command parsing with empty data (#8104)
* Fixed iTach command parsing with empty data

* Switched to using format
2017-06-21 09:09:08 -07:00
Phil Hawthorne
b45c386fd6 Update InfluxDB to handle datetime objects and multiple decimal points (#8080)
* Update InfluxDB to handle datetime objects

Updates the InfluxDB regex to ignore datetime objects being coverted
into float values.

Adds tests to the component to ensure datetime objects are corectly
handled.

* Fix Hound errors

Fixes errors from Hound bot

* Update InfluxDB to handle multiple decimal points

Changes the way InfluxDB handles values such as 1.2.3.4 to be 1.234 so
it stores in InfluxDB as a valid float value

* Fix lint issues

Reduce the size of a line for the linter

* Update InfluxDB to pass on unknown variable

If we get an error trying to convert a variable to a float, let's ignore
it completely

* Make InfluxDB Regex constants

Makes the Regex's used by InfluxDB constants so they don't need to be
compiled each time

* cleanup

* fix lint

* Update regex

* fix tests

* Fix JSON body missing new line character

* fix exceptions
2017-06-21 09:09:08 -07:00
Charles Blonde
cb5fa79835 Fix Dyson async_add_job (#8113) 2017-06-21 09:09:08 -07:00
Tsvi Mostovicz
b74217bec2 Fix lights issue #8098 (#8101)
* Fix lights issue #8098

* Don't check self._color to decide whether to calll get_color()

self._color is None on init, so get_color() will never be called.
2017-06-21 09:09:08 -07:00
Paulus Schoutsen
fcf60e740d Version bump to 0.47.1 2017-06-21 09:08:20 -07:00
Eugenio Panadero
bb05600010 Add I2c BME280 temperature, humidity and pressure sensor for Raspberry Pi (#7989)
* Add new BME280 temperature, humidity and pressure sensor

* Add BME280 sensor to optional requirements and .coveragerc

* move validation to sensor handler, async fix in setup

* fix Invalid attribute name

* review changes: move sensor code to external module

* async fix

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3, round prec to 1 dec

* change style for hass

* fix lint

* fix lint part 2
2017-06-21 17:24:39 +02:00
Thibault Cohen
d3bb6d3988 Decora light: Fix brightness level in UI (#8139) 2017-06-21 16:37:27 +02:00
Thibault Cohen
f3945147a4 Add current balance to hydroquebec sensor (#8138) 2017-06-21 16:36:20 +02:00
Paulus Schoutsen
6398e92836 Allow iteration in python_script (#8134)
* Allow iteration in python_script

* Add tests
2017-06-21 13:32:50 +02:00
Steven Conaway
4d2b79156d Change Error Message when Turning off ISY994 Light (#8131) 2017-06-21 09:38:12 +02:00
Fabian Affolter
b6d335f993 Do not call update() in constructor (#8120) 2017-06-21 09:35:44 +02:00
lunar-consultancy
4b82c34b8f Added RFXTRX UV badge (#8129) 2017-06-21 08:55:51 +02:00
Paulus Schoutsen
66fc852363 Update frontend 2017-06-20 21:10:53 -07:00
Paulus Schoutsen
87274879a8 Upgrade RestrictedPython dependency (#8132) 2017-06-20 19:30:01 -07:00
Fabian Affolter
e4dbf8033c Upgrade aiohttp to 2.2.0 (#8121) 2017-06-21 00:35:49 +02:00
Fabian Affolter
43db94d62d Upgrade sqlalchemy to 1.1.11 (#8124) 2017-06-21 00:32:49 +02:00
Fabian Affolter
6d5fca2db1 Upgrade paho-mqtt to 1.3.0 (#8125) 2017-06-21 00:32:04 +02:00
Fabian Affolter
d5e55448ef Upgrade mutagen to 1.38 (#8126) 2017-06-21 00:29:35 +02:00
Alan Fischer
4ad998378f Fixed iTach command parsing with empty data (#8104)
* Fixed iTach command parsing with empty data

* Switched to using format
2017-06-20 15:26:18 +02:00
Fabian Affolter
d46607c0d0 Add option to specify the location of the API (fixes #8115) (#8118) 2017-06-20 14:09:54 +02:00
Luar Roji
04920fa0bf Only mark active DHCP clients as present (#8110)
We only want to know which of the DHCP clients are indeed active.

For example: I've a table of static DHCP leases with most of the IPs of my network, so this module is always detecting them as present. With my patch only the active ones will be detected as present.

I already mentioned here: https://github.com/home-assistant/home-assistant/pull/7366#issuecomment-302950139
2017-06-20 12:16:56 +02:00
Fabian Affolter
1928da1fae Remove config details (see docs) (#8119) 2017-06-20 12:09:42 +02:00
Phil Hawthorne
06b051c53d Update InfluxDB to handle datetime objects and multiple decimal points (#8080)
* Update InfluxDB to handle datetime objects

Updates the InfluxDB regex to ignore datetime objects being coverted
into float values.

Adds tests to the component to ensure datetime objects are corectly
handled.

* Fix Hound errors

Fixes errors from Hound bot

* Update InfluxDB to handle multiple decimal points

Changes the way InfluxDB handles values such as 1.2.3.4 to be 1.234 so
it stores in InfluxDB as a valid float value

* Fix lint issues

Reduce the size of a line for the linter

* Update InfluxDB to pass on unknown variable

If we get an error trying to convert a variable to a float, let's ignore
it completely

* Make InfluxDB Regex constants

Makes the Regex's used by InfluxDB constants so they don't need to be
compiled each time

* cleanup

* fix lint

* Update regex

* fix tests

* Fix JSON body missing new line character

* fix exceptions
2017-06-20 07:53:13 +02:00
Charles Blonde
473d765bb9 Fix Dyson async_add_job (#8113) 2017-06-19 23:50:27 +02:00
sn0oz
8e34c27b63 Added SMTP SSL/TLS support (#7960)
* Added SMTP SSL/TLS support

* added new encryption option

* validation of encryption option

* Fix lint issues

* Rename var
2017-06-19 14:19:31 +02:00
Eugenio Panadero
77aa2e940d increase timeout for setWebhook to 10s (#8102)
Add an optional extended description…
2017-06-19 12:03:58 +02:00
Tsvi Mostovicz
3bbaf37193 Fix lights issue #8098 (#8101)
* Fix lights issue #8098

* Don't check self._color to decide whether to calll get_color()

self._color is None on init, so get_color() will never be called.
2017-06-19 09:54:13 +02:00
Andrey
b2d6ff9783 More updates to zwave services.yaml file (#8083) 2017-06-18 22:38:14 -07:00
Will W
4fdde4f0e2 add knx cover support (#7997)
* add knx cover

also corrected bugs in device config

1. overwriting of addresses in KNXMultiAddressDevice
2. setting and getting int values
3. added percentage scaling

* Update __init__.py
2017-06-18 22:30:39 -07:00
Jeff Wilson
756768e745 Add support for Insteon FanLinc fan (#6959)
* Add support for Insteon FanLinc fan

* Upgrade insteonlocal dependency to 0.49

* Lint/flake fixes

* Remove configurator

* Make Hound fixes

* Revert "Make Hound fixes" and "Remove configurator"

This reverts commit 04d1f7fdb1.
This reverts commit 7b8278d7cf.
2017-06-18 21:43:10 -07:00
Diogo Gomes
83b791489b Upnp properties (#8067)
* make port mapping optional

* dependencies + improvements

* Added bytes and packets sensors from IGD

* flake8 check

* new sensor with upnp counters

* checks

* whitespaces in blank line

* requirements update

* added sensor.upnp to .coveragerc

* downgrade miniupnpc

Latest version of miniupnpc is 2.0, but pypi only has 1.9

Fortunately it is enough

* revert to non async

miniupnpc will do network calls, so this component can’t be moved to
coroutine

* hof hof

forgot to remove import ot asyncio
2017-06-18 21:32:39 -07:00
Eugenio Panadero
35132f9836 media player Kodi: handle TransportError exceptions when calling JSONRPC API methods (#8047)
* handle TransportError exceptions when calling JSONRPC API

* use double quotes for log messages; show TransportErrors as in async_ws_connect

* fix spaces around keyword / parameter

* fix logging message

* review changes
2017-06-18 23:00:02 +02:00
Paulus Schoutsen
0e08785373 Update frontend 2017-06-18 11:37:15 -07:00
Per Osbäck
bf0dbdfd6a update pywebpush to 1.0.5 (#8084) 2017-06-18 10:51:37 -07:00
Michaël Arnauts
04407b8623 Cleanup .coveragerc (#8088) 2017-06-18 10:50:35 -07:00
Michael Auchter
fb0ee34f10 mpd: implement support for seek, shuffle, and clear playlist (#8090)
* mpd: add shuffle and clear_playlist support

* mpd: implement seek
2017-06-18 18:31:45 +02:00
Myles Eftos
ef63cfe8e4 Stopping the logfile spam by piping STDERR to /dev/null (#8081) 2017-06-18 11:44:41 +02:00
Paulus Schoutsen
e40f72e773 Merge branch 'master' into dev 2017-06-17 12:13:59 -07:00
Paulus Schoutsen
cec8ccb1a4 Version bump to 0.48.0.dev0 2017-06-17 12:13:46 -07:00
Paulus Schoutsen
9b1ed4e79b Merge pull request #8055 from home-assistant/release-0-47
0.47
2017-06-17 12:07:58 -07:00
happyleavesaoc
8fffaebe50 bump ups (#8075) 2017-06-17 11:12:50 -07:00
happyleavesaoc
84aab1c973 bump usps version (#8074) 2017-06-17 11:12:50 -07:00
Caleb
a2fbc0d2ef Update pyunifi component to use APIError passed from pyunifi 2.13. Better accommodate login failures with wrapper in pyunifi 2.13. (#7899)
* Pyunifi update

* Update pyunifi_test

* Import API Error

* Adjust test_unifi.py to import APIError

* Remove urllib import

* Remove urllib import from test

* Try fix mock

* Remove automations.yaml

* Lint
2017-06-17 11:09:44 -07:00
Caleb
6a017efc0e Update pyunifi component to use APIError passed from pyunifi 2.13. Better accommodate login failures with wrapper in pyunifi 2.13. (#7899)
* Pyunifi update

* Update pyunifi_test

* Import API Error

* Adjust test_unifi.py to import APIError

* Remove urllib import

* Remove urllib import from test

* Try fix mock

* Remove automations.yaml

* Lint
2017-06-17 11:09:27 -07:00
Paulus Schoutsen
363a429c41 Fix EntityComponent handle entities without a name (#8065)
* Fix EntityComponent handle entities without a name

* Implement solution by Anders
2017-06-17 10:59:18 -07:00
Lev Aronsky
9fc22ee47a Added 'all_plants' group and support for plant groups state. (#8063)
* Added 'all_plants' group and support for plant groups state.

* Reversed the group states.
2017-06-17 10:59:18 -07:00
Pascal Vizeli
a250f583eb Fix attribute entity (#8066)
* Bugfix entity attribute setter

* Fix tests

* Fix tests part 2

* Change filter only None

* Fix tests part 3

* Update entity.py

* Fix tests
2017-06-17 10:59:18 -07:00
Andrey
bf495edbb5 Add to zwave services descriptions (#8072) 2017-06-17 10:59:18 -07:00
Paulus Schoutsen
3ea7dee83d Always enable monkey patch (#8054) 2017-06-17 10:59:18 -07:00
pezinek
d796e8db5c No update in MQTT Binary Sensor #7478 (#8057) 2017-06-17 10:59:18 -07:00
Pascal Vizeli
d24b45054a Update numpy 1.13.0 (#8059) 2017-06-17 10:59:18 -07:00
Paulus Schoutsen
18935440ed Fix EntityComponent handle entities without a name (#8065)
* Fix EntityComponent handle entities without a name

* Implement solution by Anders
2017-06-17 10:50:59 -07:00
Lev Aronsky
2ba6b3a2ab Added 'all_plants' group and support for plant groups state. (#8063)
* Added 'all_plants' group and support for plant groups state.

* Reversed the group states.
2017-06-17 10:22:23 -07:00
Pascal Vizeli
2438c6b7c2 Fix attribute entity (#8066)
* Bugfix entity attribute setter

* Fix tests

* Fix tests part 2

* Change filter only None

* Fix tests part 3

* Update entity.py

* Fix tests
2017-06-17 10:03:49 -07:00
Andrey
32a84f1466 Add to zwave services descriptions (#8072) 2017-06-17 10:02:37 -07:00
happyleavesaoc
0002a895ca bump usps version (#8074) 2017-06-17 18:42:56 +02:00
happyleavesaoc
d0b43b187a bump ups (#8075) 2017-06-17 18:42:12 +02:00
John Mihalic
33d381731f Bump pyEmby version to account for API changes (#8070) 2017-06-17 18:41:35 +02:00
Eugenio Panadero
18f81d7824 Add option to set language of openweathermap sensor, and handle updating errors (#8046)
* Add option to set language of openweathermap sensor messages

* handle error updating openweathermap sensor
2017-06-17 12:37:34 +02:00
Fabian Affolter
844c8149d7 Add initial support for Shiftr.io (#7974)
* Add initial support for Shiftr.io

* Fix lint issue

* Use paho-mqtt instead of internal MQTT object

* remove async flavor while paho is not async
2017-06-17 12:34:12 +02:00
pezinek
7617864ba5 Failed to parse response from WU API: 'record' (and 'recordyear') #7747 (#8058) 2017-06-17 12:32:22 +02:00
jshore1296
58c234466c Allow config of latitude and longitude (#8068)
This will allow for dynamically updating weather states, for instance if
you wanted to use the latitude and longitude of a phone or other device
to get the weather for your current location.
2017-06-17 10:41:11 +02:00
Phil Hawthorne
9071946e87 Remove % sign from Vera Battery Levels (#8069)
Vera devices are reporting battery levels as a sting by appending a
percentage sign (%) on the end.

To make the Vera component act like other Home Assistant components,
let's remove the percentage sign from the battery report levels so that
we only display the battery level.

This may be a "breaking change" if people are relying on the Vera
battery levels to be a string instead of an int. However, this will make
the battery level reports compatible with everything else.
2017-06-17 10:38:15 +02:00
Paulus Schoutsen
b24aa24f6a Always enable monkey patch (#8054) 2017-06-16 17:17:18 -07:00
Andrey
1fde234c78 Fix some warnings found by quantifiedcode (#8027)
* Cleanup of warnings by quantifiedcode

* Fix lint

* Fix test

* Delete insteon_hub component

* Also update .coveragerc
2017-06-16 22:44:14 +03:00
Adam Mills
d67f3b8060 Use standard entity_ids for zwave entities (#7786)
* Use standard entity_ids for zwave entities

* Include temporary opt-in for new entity ids

* Update link to blog post

* Update tests

* Add old entity_id as state attribute

* Expose ZWave value details

* Update tests

* Also show new_entity_id

* Just can't win with this one
2017-06-16 13:25:12 -04:00
Adam Mills
afb9cba806 Use standard entity_ids for zwave entities (#7786)
* Use standard entity_ids for zwave entities

* Include temporary opt-in for new entity ids

* Update link to blog post

* Update tests

* Add old entity_id as state attribute

* Expose ZWave value details

* Update tests

* Also show new_entity_id

* Just can't win with this one
2017-06-16 13:07:17 -04:00
pezinek
1c2f4866e2 No update in MQTT Binary Sensor #7478 (#8057) 2017-06-16 14:55:59 +02:00
Pascal Vizeli
e90ae2fb75 Update numpy 1.13.0 (#8059) 2017-06-16 11:47:48 +02:00
Paulus Schoutsen
4339e9aab1 version bump to 0.47 2017-06-15 22:51:13 -07:00
Paulus Schoutsen
9b640f6a81 Add comment to default config 2017-06-15 22:31:22 -07:00
Alex Harvey
437ddb8dea Updater improvements to send option component information (#7720)
* Setup to send component data is option is enabled

* testcases, as well as moved to a single boolean, passed to the function

* fixed pep8 failures

* Clarify config option.
2017-06-15 22:29:18 -07:00
Erik Eriksson
a119bd0056 Provide entity_id to avoid sensor mixup (fixes #7636). Use async_dispatcher. Provide icon. (#7946)
* Avoid sensor mixup. Fixes #7636. Also provide icon. Plus some smaller
fixes.

* fix async p1

* Create volvooncall.py
2017-06-15 22:28:30 -07:00
matt2005
0eaad46d93 Added ONVIF camera component (#7979)
* Added ONVIF camera component

* added requirements

* corrected long lines

* fixed indenting

* fixed indenting

* removed bad whitespace

* updated coveragerc

* Added ONVIF camera component

* added requirements

* corrected long lines

* fixed indenting

* fixed indenting

* removed bad whitespace

* updated requirements

* updated requirements

* Added ONVIF camera component

* added requirements

* corrected long lines

* fixed indenting

* fixed pylink error indenting

* Added ONVIF camera component

* added requirements

* corrected long lines

* fixed indenting

* fixed indenting

* removed bad whitespace

* updated requirements

* fixed indenting

* removed bad whitespace

* updated requirements

* fixed pylink error indenting

* rebased and fixed requirements

* Removed Debug logging

* Added info logging to show URL being used.

* corrected spacing

* Tidied up and renamed input to host

* fixed typo

* corrected line lengths

* added default to ffmpeg_arguments

* removed unecessary ffmpeg arguements

* changed to use .format instead of +

* fixed indenting

* cleanup & make it more readable
2017-06-15 22:28:17 -07:00
Jean-Michel Ruiz
8af6bacfd0 media_player.firetv - Adding support for https. (#8022)
* Adding support for https.

This change allows to access a firetv-server instance that runs over https (via a reverse proxy for exemple).
Default stays http, but if `ssl: true` is set in the configuration the connection goes over https.

Successfully tested.

* respecting the 79 characters line limit
2017-06-15 22:23:10 -07:00
Giuseppe
09ca440c20 Fixed the Wind sensor following new release of netatmo-api-python (#8030)
* Fixed the Wind sensor following new release of netatmo-api-python

The NetAtmo PR was at:
https://github.com/jabesq/netatmo-api-python/pull/5

Essentially, this commit adds a protection when adding an incorrect
monitored conditions to avoid to fail the entire NetAtmo component,
plus for consistency reasons all conditions are now in lower case.

* Fixes following the CI tests
2017-06-15 22:14:46 -07:00
Paulus Schoutsen
74cc675a38 Restrict Python Script (#8053) 2017-06-15 22:13:10 -07:00
boojew
c478f2c7d0 Added host variable to Splunk.py and updated tox tests (#8052)
* Added host variable to Splunk.py and updated tox tests

* Update splunk.py

* Update splunk.py
2017-06-15 20:41:19 -07:00
Martin Tremblay
a3a702b269 Adding ssocr to docker to support Seven Segments Display (#8028)
* Adding ssocr to docker to support Seven Segments Display

* Adding cleanup
2017-06-15 20:31:30 -07:00
Paulus Schoutsen
92a6f21cc2 Update frontend 2017-06-15 20:29:11 -07:00
Pascal Vizeli
814834512a Group service / dynamic handling (#7971)
* Add Service to group

* Finish service

* Add service functions

* fix lint

* Address paulus comments

* fix lint & cleanup

* fix lint

* fix lint

* fix lint p3

* add test for check group

* add more tests

* fix lint

* Update service.yaml

* Fix order for tests

* Fix comment

* Fix test

* Fix tests

* Fix name in tests

* Fix view

* Fix default value

* Fix lint

* Fix key error

* add name

* migrate component entity

* fix tests

* fix import

* migrate device tracker

* fix lint

* fix bug

* fix logic

* fix lint

* fix tests

* fix generator

* fix group

* fix other tests.

* Not need to load group on first stage anymore.

* fix service

* add more group depency

* fix tests

* Revert "fix tests"

This reverts commit 35a922b3a8.

* Real fix

* fix test p2

* fix test p3

* fix test p4

* fix test p5

* fix test p6

* fix lint

* fix test p7

* Rename attribute

* fix group test

* fix bug

* fix flagy tests

* fix service.yaml

* fix lint
2017-06-16 00:52:28 +02:00
Alan Fischer
46f3088a70 Vera fix for dimmable vs rgb lights (#8007)
* Differentiate between dimmable & rgb lights

* Updated requirements

* Cache _has_color for supported_features

* simplify & cleanup code

* Create vera.py
2017-06-16 00:28:24 +02:00
Fabian Affolter
deed760008 Upgrade zeroconf to 0.19.1 (#8043) 2017-06-15 21:25:19 +02:00
Fabian Affolter
d1da53615f Upgrade pysnmp to 4.3.8 (#8044) 2017-06-15 21:24:31 +02:00
Fabian Affolter
69c919183a Do not call update() in constructor (#8048) 2017-06-15 21:23:55 +02:00
Anders Melchiorsen
8eb29787a5 LIFX: add multiple modes to pulse effect (#8016)
* blink: the existing 50/50 flashing between base color and effect color
* breathe: a lifx_effect_breathe replacement
* ping: mostly base color with a short flash at the end of the cycle
* strobe: dark base color and short cycles by default
* solid: temporary color change, base color never visible

Adding a service call for each mode is a bit extravagant so instead
lifx_effect_breathe has been folded in as an option and that service
call is deprecated.
2017-06-15 07:59:11 +02:00
Adam Mills
ae3973144c Discover Z-Wave values by index (#7853)
* Discover Z-Wave values by index

* Add URLs for enums (Some Assembly Required)

* URLs on one line

* Move lint suppression to single line
2017-06-14 08:41:20 -04:00
Andrey
02f7eb9675 Allow device_tracker platforms to specify picture and icon upon discovery (#8018)
* Allow device tracker platforms to specify picture

* Allow device tracker to specify icon during discovery

* Clean up and add tests

* Fix lint

* Fix test
2017-06-14 14:39:18 +02:00
Charles Blonde
8c0967a190 Add Dyson Pure Cool Link support (#7795)
* Add Dyson Pure Cool Link support

* Code review

* Improve auto/night mode

* Move night_mode to Dyson fan component

* Code review

* fix asynchrone/sync

* Create dyson.py
2017-06-14 13:56:03 +02:00
Tom Matheussen
bf2fe60cb5 Take in account Spotify account permissions (#8012)
* only show Spotify actions when Premium account is used

* Fix indentation, stupid autoformat
2017-06-14 00:45:00 +02:00
Phil Hawthorne
1ddcab5e26 Make percentage string values as floats/ints in InfluxDB (#7879)
* Make percentage string values as floats in InfluxDB

Currently Z-wave and other compontents report an attributes battery
level as an integer, for example

```yaml
{
"is_awake": false,
"battery_level": 61,
}
```

However, some other components like Vera add the battery level as a
string

```yaml
{
"Vera Device Id": 25,
"device_armed": "False",
"battery_level": "63%",
"device_tripped": "False",
}
```

By removing any % signs in the field, this will send the value to
InfluxDB as an int, which can then be used to plot the data in graphs
correctly, like other percentage fields.

* Add tests and remove all trailing non digits

Adds tests and now removes all trailing non-numeric characters for
better use

* Update variable name for InfluxDB digit checks

Updates the variable used for the regex to remove trailing non digits

* Fix linting errors for InfluxDB component

Fixes a small linting error on the InfluxDB component
2017-06-14 00:42:55 +02:00
Thiago Oliveira
09fec29537 entity_id for service fan.turn_off is optional (#7982)
* entity id is optional

* use a simple if/else to set the data for the fan.turn_off service
2017-06-13 17:28:05 +02:00
Fabian Affolter
9189cbdc8b Remove globally disabled pylint issues (#8005) 2017-06-13 11:10:32 +02:00
Marco Sirabella
7fae8cd0f1 Configure conversation for custom actions with keywords (#7734)
* - Simple keyword to action config

* - Added more fuzzy stuff

* - Logging & a bit of commenting

* - pep8?

* - pep8 and quick formatting fixes

* - Changed configuration a bit

* - Backwards compatibility tests

* - Fallback or

* - Added custom configuration for conversation

* - Moved imports inside function

* - pep8

* - Pass tests better

* - Removed unused imports

* - Moved warning ignore to above import for fuzzy

* - Moved return for consistent return types

* - Fallback if no choices to listen for

* - Fixed linting errors

* - Better logging and fixed linting errors(?)

* - Fixed continuation

* - Added one blank line after class docstring

* Create conversation.py

* Create test_conversation.py

* Create test_conversation.py

* Update test_conversation.py
2017-06-12 23:34:20 -07:00
Fabian Affolter
843f8ce9ee Allow put as method (#8004) 2017-06-12 22:27:25 -07:00
Nolan Gilley
2bf781185f update pyripple (#8015) 2017-06-13 07:22:46 +02:00
Sabesto
1e1d4c2013 Add Flexit AC climate platform (#7871)
* Add Flexit AC climate platform

* Protocol extracted to third party lib
2017-06-12 22:06:47 -07:00
Fabian Affolter
bde711a9ff Make it more flexible (fixes #7954) (#8001)
* Make it more flexible (fixes #7954)

* Fix var name
2017-06-12 09:13:03 +02:00
cribbstechnologies
dc45ed38e7 fixing potential null issue with optional param being parsed as a script (#7928)
* fixing potential null issue with optional param being parsed as a script

* Create template.py
2017-06-11 22:58:20 -07:00
Sören Oldag
03f916ed10 Fixed bug in spotify component. (#7976) 2017-06-11 22:24:01 -07:00
happyleavesaoc
6e33c12008 Update mailgun (#7984)
* add mailgun component

* add to coveragerc
2017-06-11 22:19:10 -07:00
Adam Mills
401309c3b2 Additional demo fan with only speed support (#7985)
* Additional demo fan with only speed support

* Update demo fan tests
2017-06-11 22:12:56 -07:00
sander76
1c06b51968 Fixing Client connection error (#7991) 2017-06-11 21:42:35 -07:00
Fabian Affolter
e7de1fb9ae Add Gitter.im sensor (#7998) 2017-06-11 21:40:06 -07:00
tedstriker
de0f6b781e dismiss service for persistent notifications (#7996)
* dismiss service for persistent notifications

Unnecessary notifications can now be removed automatically. Added a
dismiss service to remove persistent notifications via script and/or
automation.

* removed unnecessary loop

loop removed
2017-06-11 22:54:10 +02:00
Anders Melchiorsen
314bce1073 LIFX: add support for setting infrared level (#8000)
* LIFX: update aiolifx

This adds support for infrared and multizone.

* LIFX: add support for infrared
2017-06-11 22:38:07 +02:00
Anders Melchiorsen
9e16be3173 LIFX: clean up internal color conversions (#7964)
* Add color_util.color_hsv_to_RGB

* Use helper functions for LIFX conversions

The LIFX API uses 16 bits for saturation/brightness while HA uses 8 bits.
Using helper functions makes the conversion a bit nicer and less prone
to off-by-one issues.

The colorsys library uses 0.0-1.0 but we can avoid that by using the HA
color_util converters instead.
2017-06-11 21:19:58 +02:00
Fabian Affolter
1b1619fbf1 Upgrade py-cpuinfo to 3.3.0 (#7992) 2017-06-11 12:03:02 +02:00
Oliver
1f226cffe9 Bugfixing with version 0.4.4 of denonavr (#7995) 2017-06-11 12:02:32 +02:00
Eugenio Panadero
b9ee5fb867 make last_name field optional (#7988) 2017-06-10 22:19:13 +02:00
Thiago Oliveira
ba80d5e52a test that all lights turn off when no entity id is given (#7981) 2017-06-10 10:13:52 +02:00
Erik Eriksson
f2feabcf0b Update eliqonline.py (#7977)
Print error
2017-06-10 10:12:30 +02:00
Daniel Perna
a19e7ba3f1 HomeMatic optimizations and code cleanup (#7986)
* Cleanup and optimizations

* Cleanup

* Typo -.-

* Linting
2017-06-10 10:08:36 +02:00
mwsluis
49d642741d Nadtcp component (#7955)
* initial commit

* class name and requirements_all.txt

* removed mentions of D7050

* changed default name

* catch oserror in update, travis errors.

* use nad_receiver pip version

* update coveragerc
2017-06-09 14:53:07 -04:00
Paulus Schoutsen
db0efc647d New component: Python Script (#7950)
* Add initial version

* Fix requirements

* Prefer logging over printing

* Set executor thread name on >Py36 only

* Add tests

* Lint

* Add restrictedpython to test dependencies

* Create python_script.py

From doc:
```
However, an empty dict ({}) is treated as is. If you want to specify a list that can contain anything, specify it as dict:
>>> schema = Schema({}, extra=ALLOW_EXTRA)  # don't do this
>>> try:
...   schema({'extra': 1})
...   raise AssertionError('MultipleInvalid not raised')
... except MultipleInvalid as e:
...   exc = e
>>> str(exc) == "not a valid value"
True
>>> schema({})
{}
>>> schema = Schema(dict)  # do this instead
>>> schema({})
{}
>>> schema({'extra': 1})
{'extra': 1}

```
2017-06-09 12:38:40 +02:00
Paulus Schoutsen
640c692e1f Fix platforms being able to block startup (#7970) 2017-06-09 12:11:58 +02:00
Paulus Schoutsen
4aef0b68bc Merge branch 'master' into dev 2017-06-08 22:21:25 -07:00
Paulus Schoutsen
c2b7c93375 Merge pull request #7968 from home-assistant/release-0-46-1
0.46.1
2017-06-08 22:20:32 -07:00
Paulus Schoutsen
8cc759ea4b Prevent Roku doing I/O in event loop (#7969) 2017-06-08 22:18:48 -07:00
Paulus Schoutsen
a223efb840 Prevent Roku doing I/O in event loop (#7969) 2017-06-08 22:18:33 -07:00
Jacob Mansfield
c32807803e Create metoffice.py (#7965)
Fix met office sensor
2017-06-08 21:44:33 -07:00
Jacob Mansfield
24a172163a Create metoffice.py (#7965)
Fix met office sensor
2017-06-08 21:44:24 -07:00
Barry Williams
372169a03a Fixed metadata issue (#7932) 2017-06-08 21:41:42 -07:00
Barry Williams
e4d100d54d Fixed metadata issue (#7932) 2017-06-08 21:41:24 -07:00
cribbstechnologies
bfd9623d8b Mqtt cover modifications (#7841)
* adding set position ability
removing command_topic being required

* flaking

* flaking test

* updating docs

* requested updates

* Revert "updating docs"

This reverts commit 9cfc5ed7a8.

* forgot to update constructor calls in tests
2017-06-08 21:35:38 -07:00
mje-nz
3464454662 Fix typos in Wunderground component (Percipitation -> Precipitation) (#7901) 2017-06-08 21:35:26 -07:00
Johan Bloemberg
533bb5565b Dsmr5 revert (#7900)
* Revert "Update to dsmr_parser supporting v5 arguments."

This reverts commit 3567de4b90.

* Revert "Using dev branch until released upstream."

This reverts commit 53e8de112c.

* Revert "Give good example."

This reverts commit 4f90fc4be6.

* Revert "Allow configuring DSMR5 protocol."

This reverts commit 9fa0e14187.
2017-06-08 21:35:26 -07:00
Adam Mills
a8709a6988 Support for renaming ZWave values (#7780)
* Support for renaming ZWave values

* Improve test
2017-06-08 21:35:26 -07:00
Paulus Schoutsen
4b767b088e Version bump to 0.46.1 2017-06-08 21:34:39 -07:00
Albert Lee
c52b18d7c8 lock.sesame: Update pysesame, add state attributes (#7953)
* Update pysesame requirement to 0.1.0 to support caching

* Set `available` property based on API enabled status

* Add state attributes for device ID and battery level
2017-06-09 00:21:56 +02:00
Fabian Affolter
aaaf9637eb Add configuration check and use default var names (#7963) 2017-06-09 00:21:06 +02:00
Riccardo Canta
055db05946 Osram lightify, removed double set to the lightify bridge in case of brightness changes (#7662)
* osram lightify removed duplicated set in case of brightness changes

* lightify component: anticipate brightness evaluation to handle unconsidered scenario described in the PR request comments (light turned on with color/temperature)

* Correction for travis ci error:
undefined name 'transition'
2017-06-08 20:17:28 +02:00
Fabian Affolter
0863d50210 Fix typos (#7957)
Add an optional extended description…
2017-06-08 15:53:12 +02:00
Alan Fischer
1e352d37d0 Vera colored light support (#7942)
* Added support for color to vera lights

* Updated requirements
2017-06-08 12:28:03 +02:00
Boris K
620197b276 Fix the negative values bug in history_stats (#7934) 2017-06-08 12:27:43 +02:00
Michael Heinemann
727a22f925 test connection without needing admin rights (#7947)
SHOW DIAGNOSTICS always needs admin privileges on influxdb. For
the purposes of home-assistant this is too much.
Use 'SHOW SERIES' to have a relatively lightweight query which
only needs READ privileges.
2017-06-08 12:26:37 +02:00
Fabian Affolter
9bea7d7d8b Upgrade coinmarketcap to 3.0.1 (#7951) 2017-06-08 12:15:46 +02:00
Teagan Glenn
97f62cfb78 [WIP] Fix opencv (#7864)
* Updates to opencv image processor

* Remove opencv hub

* Requirements

* Remove extra line

* Fix linting errors

* Indentation

* Requirements

* Linting

* Check for import on platform setup

* Remove opencv requirement

* Linting

* fix style

* fix lint
2017-06-08 11:26:24 +02:00
Oliver
482db94372 Add option to display all input sources / Add support for favourite channels / Treat Marantz SR5008 as Denon AVR-X device (#7949)
* add option to display all sources / pushed to version 0.4.3 of library

* Add show_all_sources option for auto discovery too

* change code style for hass

* fix lint
2017-06-08 09:46:26 +02:00
vrs01
8a4e993183 Update ping.py (#7944) 2017-06-08 07:30:51 +02:00
joopert
790610525b update to 006 (#7945) 2017-06-08 07:30:07 +02:00
Daniel Perna
7e668ef9e3 Merge pull request #7948 from danielperna84/HomeMatic
HomeMatic: Updated dependency
2017-06-08 00:09:53 +02:00
Daniel Perna
4dbf7be267 Updated dependency 2017-06-07 23:55:42 +02:00
Juggels
36eb0ceff3 [media_player.sonos] Send media_stop on turn_off (#7940) 2017-06-07 13:15:29 +01:00
Fabian Affolter
d38acfbd39 Add Yahoo! weather platform (#7939) 2017-06-07 10:49:54 +02:00
Nolan Gilley
b87e31617a add ripple sensor (#7935) 2017-06-07 10:24:07 +02:00
Jacob Minnis
bb6fe822f9 Added 'change' field to statistics sensor (#7820)
* Added 'change' field to statistics sensor

* Updated statistics sensor test

* Updated statistics sensor test complaint
2017-06-07 09:38:00 +02:00
Stephan Auerhahn
5504a511e3 Add service_url config option to volvooncall (#7919)
* Add service_url config option to volvooncall

* Import default value from volvooncall lib
2017-06-07 08:52:36 +02:00
Fabian Affolter
5c96936eb4 Do not call update() in constructor (#7931) 2017-06-06 19:15:03 +02:00
Fabian Affolter
cbbb15fa48 Fix changes introduced with #7917 (#7930) 2017-06-06 19:14:41 +02:00
Fabian Affolter
760138ac52 Do not call update() in constructor (#7917) 2017-06-05 21:28:13 +02:00
Per Osbäck
b1f538b622 update to pywebpush 1.0.4 which allows install on system with openssl-1.1.0 (cryptography dep) (#7915) 2017-06-05 17:46:51 +02:00
John Mihalic
ac8592587f Bump pyEight version to fix 0hr session errors (#7916) 2017-06-05 17:44:13 +02:00
Jesse Hills
aee25a020d Add juicenet platform (#7668)
* Add juicenet platform

* Update missing variable
Add missing blank lines

* Remove unnecessary override

* Update juicenet.py

* Remove whitespace
Add missing docstring

* Remove unused services
Use the hass built in unique_id

* Fix lint issues

* Update python-juicenet library version

* Update python-juicenet library version

* Remove unnecessary code

* Remove unused import

* Remove super call
2017-06-05 08:39:31 -07:00
Fabian Affolter
13df925795 Do not call update() in constructor (#7912)
* Do not call update() in constructor

* Do not call update() in constructor

* Remove unused import
2017-06-05 17:35:26 +02:00
PhracturedBlue
2b850f417e Minor cleanup - Define 'CONF_ICON_TEMPLATE' constant centrally (#7910)
* Add 'icon_template' to switch templates (similar to sensor template)

* Add test for template switch 'icon_template'

* Define 'CONF_ICON_TEMPLATE' constant centrally

* Missed a redundant definition
2017-06-05 17:33:57 +02:00
Fabian Affolter
f303f6a191 Move consts to 'const.py' (#7909) 2017-06-05 16:59:59 +02:00
Fabian Affolter
f8cfa15152 Sync crypto-currency platforms (#7906) 2017-06-05 13:36:39 +02:00
Fabian Affolter
12f731b32c Fix docstring (#7907) 2017-06-05 13:16:53 +02:00
PhracturedBlue
11dcbd4449 Add 'icon_template' to switch templates (similar to sensor template) (#7862)
* Add 'icon_template' to switch templates (similar to sensor template)

* Add test for template switch 'icon_template'
2017-06-05 11:27:48 +02:00
Paulus Schoutsen
fa6a089fb3 Lint 2017-06-05 00:10:57 -07:00
florincosta
87da2ff1d7 Add raspihats switch (#7665) 2017-06-04 23:56:21 -07:00
Paulus Schoutsen
b576df53e9 Update .coveragerc 2017-06-04 23:54:15 -07:00
Martin Berg
b90964faad Add support for Vanderbilt SPC alarm panels and attached sensors (#7663)
* Add support for Vanderbilt SPC alarm panels.

 * Arm/disarm + read state

 * Autodiscover and add motion sensors

* Fix code formatting.

* Use asyncio.async for Python < 3.4.4.

* Fix for moved aiohttp exceptions.

* Add docstrings.

* Fix tests and add docstrings.
2017-06-04 23:53:25 -07:00
mjj4791
549133a062 Added buienradar sensor and weather (#7592)
* Added buienradar sensor and weather

* used external library for parsing

* used external library for parsing

* updated buienradar lib to 0.4

* Make sure you import 3rd party libraries inside methods.

* Make sure you import 3rd party libraries inside methods.

* clean up code; optimized

* imports, sensor name and attributes

* updated requirements to match imports

* use asyncio for http get
2017-06-04 23:48:11 -07:00
Matthew Schick
c29553517f Add service to set nest away/home modes (#7619)
* Add service to set nest away/home modes

* New service `nest.set_mode`
* Update the NestDevice object to export the local structures

* Validation and structure cleanup
2017-06-04 23:45:24 -07:00
Trevor
2e27c0d5ec Add Radarr sensor (#7318)
* Add radarr.py

* Update radarr.py

* Update radarr.py

* Add test_radarr.py

* Update test_radarr.py

* Update test_radarr.py

* Update radarr.py

* Update .coveragerc

* Fix hound.
2017-06-04 23:44:24 -07:00
cribbstechnologies
774f584ba8 Mqtt cover modifications (#7841)
* adding set position ability
removing command_topic being required

* flaking

* flaking test

* updating docs

* requested updates

* Revert "updating docs"

This reverts commit 9cfc5ed7a8.

* forgot to update constructor calls in tests
2017-06-04 22:55:06 -07:00
Nolan Gilley
81b1446aad blockchain.info sensor (#7856)
* blockchain sensor

* Update blockchain.py

* Update blockchain.py

* add validation of btc addresses
2017-06-04 22:48:38 -07:00
Nolan Gilley
6bfd52ada8 Etherscan.io sensor (#7855)
* etherscan sensor

* Update etherscan.py
2017-06-04 22:48:04 -07:00
Per Osbäck
0646d01152 Add support for the expirationTime parameter. (#7895)
Enabled by default in Chrome 60.
Only accepts the param, doesn't act on the actual expiration date. Chrome will always pass NULL for now.

https://github.com/w3c/push-api/pull/248
https://www.chromestatus.com/feature/4929396687241216
https://bugs.chromium.org/p/chromium/issues/detail?id=718837
2017-06-04 22:46:18 -07:00
mje-nz
da5f5335eb Fix typos in Wunderground component (Percipitation -> Precipitation) (#7901) 2017-06-04 22:37:16 -07:00
Johan Bloemberg
c9d55cff23 Dsmr5 revert (#7900)
* Revert "Update to dsmr_parser supporting v5 arguments."

This reverts commit 3567de4b90.

* Revert "Using dev branch until released upstream."

This reverts commit 53e8de112c.

* Revert "Give good example."

This reverts commit 4f90fc4be6.

* Revert "Allow configuring DSMR5 protocol."

This reverts commit 9fa0e14187.
2017-06-04 22:36:19 -07:00
Albert Lee
aeb1d3d3fe lock.sesame: New lock platform for Sesame smart locks (#7873)
* Manage Sesame devices through CANDY HOUSE's cloud API
* Add dependency on new pysesame library
2017-06-04 22:06:18 -07:00
Barry Williams
a1c119adb6 Added a Taps Aff binary sensor (#7880)
* Added a Taps Aff binary sensor

* PR Review updates

* Added a Taps Aff binary sensor

* PR Review updates

* Improved error handling

* Cosmetic changes (ordering, docstings, etc.)
2017-06-04 13:35:19 +02:00
Paulus Schoutsen
84fe4f75df Fix MQTT camera test (#7878) 2017-06-03 18:51:29 -07:00
Fabian Affolter
c07bf551d9 Upgrade python-telegram-bot to 6.0.3 (#7885) 2017-06-03 22:36:41 +02:00
Fabian Affolter
a745bf83ef Upgrade sendgrid to 4.2.0 (#7886) 2017-06-03 22:34:17 +02:00
Fabian Affolter
1432ae649a Upgrade pyasn1-modules to 0.0.9 (#7887) 2017-06-03 22:33:43 +02:00
Fabian Affolter
cf1a27bd7c Use constants (#7888) 2017-06-03 22:33:12 +02:00
Andrey
3d8b7a4122 Switch pymyq to pypi (#7884) 2017-06-03 17:12:36 +02:00
joopert
e50588afe1 Change nad_receiver to pypi (#7852)
* Change to pypi

* add requirements
2017-06-03 17:01:51 +03:00
Anders Melchiorsen
423e809e45 [light.lifx] Update aiolifx (#7882)
This makes LIFX Gen3 lights work with the current firmware.
2017-06-03 13:20:55 +01:00
Paulus Schoutsen
8461cf2717 Fix telegram_bot (#7877) 2017-06-03 10:50:37 +01:00
Adam Mills
9c9f5068b7 Support for renaming ZWave values (#7780)
* Support for renaming ZWave values

* Improve test
2017-06-02 23:03:00 -07:00
twendt
6d41024e76 Enocean Binary Sensor: Handle click of both rockers (#7770) 2017-06-02 22:12:41 -07:00
Kevin
7d24efc690 Added effects to Yeelight bulbs (#7152)
* Added effects to Yeelight bulbs

* Fix Typo and Use randint instead of randrange

* Added Effects

* updated requirements_all.txt

* fix empty line

* minor fixes

* fix passing effects as parameter
2017-06-02 21:35:32 -07:00
Paulus Schoutsen
7d4adbbef5 Fix html5 unsub (#7874)
* Fix #7758 subscription expiration/removal

Removes a subscription after receiving an HTTP 410 response when trying to send a new message.

* Fix tests failing due to additional call

* Fix code style

* Lint
2017-06-02 20:56:16 -07:00
Erik Eriksson
e11ec88482 Update squeezebox.py (#7617)
Do not fail in case no players are connected, in which case squeezeserver will return a result without player_loop.
2017-06-02 00:26:54 -07:00
Paulus Schoutsen
e39bdf8763 Version bump to 0.47.0dev0 2017-06-02 00:24:40 -07:00
354 changed files with 13949 additions and 5100 deletions

View File

@@ -35,23 +35,35 @@ omit =
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
homeassistant/components/eight_sleep.py
homeassistant/components/*/eight_sleep.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/google.py
homeassistant/components/*/google.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
@@ -65,24 +77,48 @@ omit =
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
homeassistant/components/joaoapps_join.py
homeassistant/components/*/joaoapps_join.py
homeassistant/components/juicenet.py
homeassistant/components/*/juicenet.py
homeassistant/components/kira.py
homeassistant/components/*/kira.py
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
homeassistant/components/lutron_caseta.py
homeassistant/components/*/lutron_caseta.py
homeassistant/components/mailgun.py
homeassistant/components/*/mailgun.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/nest.py
homeassistant/components/*/nest.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py
@@ -110,6 +146,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
@@ -142,45 +181,18 @@ omit =
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/zha/__init__.py
homeassistant/components/zha/const.py
homeassistant/components/*/zha.py
homeassistant/components/eight_sleep.py
homeassistant/components/*/eight_sleep.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -197,6 +209,7 @@ omit =
homeassistant/components/binary_sensor/pilight.py
homeassistant/components/binary_sensor/ping.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
@@ -204,8 +217,10 @@ omit =
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/onvif.py
homeassistant/components/camera/synology.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/flexit.py
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
homeassistant/components/climate/knx.py
@@ -215,11 +230,11 @@ omit =
homeassistant/components/climate/sensibo.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/knx.py
homeassistant/components/cover/myq.py
homeassistant/components/cover/opengarage.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/scsgate.py
homeassistant/components/cover/wink.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
@@ -233,6 +248,7 @@ omit =
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/linksys_ap.py
homeassistant/components/device_tracker/linksys_smart.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/mikrotik.py
homeassistant/components/device_tracker/netgear.py
@@ -254,12 +270,10 @@ omit =
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
homeassistant/components/image_processing/seven_segments.py
homeassistant/components/joaoapps_join.py
homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/light/avion.py
@@ -269,7 +283,7 @@ omit =
homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx/*.py
homeassistant/components/light/lifx.py
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/mystrom.py
@@ -286,6 +300,7 @@ omit =
homeassistant/components/lirc.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
@@ -302,7 +317,6 @@ omit =
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/hdmi_cec.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
@@ -310,6 +324,7 @@ omit =
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
homeassistant/components/media_player/nadtcp.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/openhome.py
homeassistant/components/media_player/panasonic_viera.py
@@ -331,17 +346,16 @@ omit =
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/mailgun.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
@@ -369,9 +383,13 @@ omit =
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
homeassistant/components/sensor/bh1750.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/blockchain.py
homeassistant/components/sensor/bme280.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cert_expiry.py
@@ -391,6 +409,7 @@ omit =
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/envirophat.py
homeassistant/components/sensor/etherscan.py
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/fido.py
@@ -398,6 +417,7 @@ omit =
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
@@ -405,6 +425,7 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hddtemp.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/hydroquebec.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
@@ -435,6 +456,8 @@ omit =
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
@@ -457,6 +480,7 @@ omit =
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/usps.py
homeassistant/components/sensor/vasttrafik.py
@@ -464,6 +488,8 @@ omit =
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py
homeassistant/components/shiftr.py
homeassistant/components/spc.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
@@ -472,7 +498,6 @@ omit =
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/fritzdect.py
homeassistant/components/switch/hdmi_cec.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py
homeassistant/components/switch/kankun.py
@@ -492,8 +517,10 @@ omit =
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/yweather.py
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
homeassistant/components/zwave/util.py

View File

@@ -1,2 +1,14 @@
.tox
# General files
.git
.github
config
# Test related files
.tox
# Other virtualization methods
venv
.vagrant
# Temporary files
**/__pycache__

View File

@@ -1,3 +1,7 @@
# Notice:
# When updating this file, please also update virtualization/Docker/Dockerfile.dev
# This way, the development image and the production image are kept in sync.
FROM python:3.6
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
@@ -8,6 +12,7 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP_CLIENT no
#ENV INSTALL_SSOCR no
VOLUME /config
@@ -20,8 +25,12 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
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 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet && \
pip3 uninstall -y enum34
# Copy source
COPY . .

View File

@@ -31,50 +31,8 @@ def attempt_use_uvloop():
pass
def monkey_patch_asyncio():
"""Replace weakref.WeakSet to address Python 3 bug.
Under heavy threading operations that schedule calls into
the asyncio event loop, Task objects are created. Due to
a bug in Python, GC may have an issue when switching between
the threads and objects with __del__ (which various components
in HASS have).
This monkey-patch removes the weakref.Weakset, and replaces it
with an object that ignores the only call utilizing it (the
Task.__init__ which calls _all_tasks.add(self)). It also removes
the __del__ which could trigger the future objects __del__ at
unpredictable times.
The side-effect of this manipulation of the Task is that
Task.all_tasks() is no longer accurate, and there will be no
warning emitted if a Task is GC'd while in use.
On Python 3.6, after the bug is fixed, this monkey-patch can be
disabled.
See https://bugs.python.org/issue26617 for details of the Python
bug.
"""
# pylint: disable=no-self-use, protected-access, bare-except
import asyncio.tasks
class IgnoreCalls:
"""Ignore add calls."""
def add(self, other):
"""No-op add."""
return
asyncio.tasks.Task._all_tasks = IgnoreCalls()
try:
del asyncio.tasks.Task.__del__
except:
pass
def validate_python() -> None:
"""Validate we're running the right Python version."""
"""Validate that the right Python version is running."""
if sys.platform == "win32" and \
sys.version_info[:3] < REQUIRED_PYTHON_VER_WIN:
print("Home Assistant requires at least Python {}.{}.{}".format(
@@ -215,7 +173,7 @@ def daemonize() -> None:
def check_pid(pid_file: str) -> None:
"""Check that HA is not already running."""
"""Check that Home Assistant is not already running."""
# Check pid file
try:
pid = int(open(pid_file, 'r').readline())
@@ -329,7 +287,7 @@ def setup_and_run_hass(config_dir: str,
def try_to_restart() -> None:
"""Attempt to clean up state and start a new homeassistant instance."""
"""Attempt to clean up state and start a new Home Assistant instance."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
sys.stderr.write('Home Assistant attempting to restart.\n')
@@ -361,11 +319,11 @@ def try_to_restart() -> None:
else:
os.closerange(3, max_fd)
# Now launch into a new instance of Home-Assistant. If this fails we
# Now launch into a new instance of Home Assistant. If this fails we
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
# systemd will restart us when RestartForceExitStatus=100 is set in the
# systemd.service file.
sys.stderr.write("Restarting Home-Assistant\n")
sys.stderr.write("Restarting Home Assistant\n")
args = cmdline()
os.execv(args[0], args)
@@ -374,18 +332,13 @@ def main() -> int:
"""Start Home Assistant."""
validate_python()
if os.environ.get('HASS_MONKEYPATCH_ASYNCIO') == '1':
if sys.version_info[:3] >= (3, 6):
if os.environ.get('HASS_NO_MONKEY') != '1':
if sys.version_info[:2] >= (3, 6):
monkey_patch.disable_c_asyncio()
monkey_patch.patch_weakref_tasks()
elif sys.version_info[:3] < (3, 5, 3):
monkey_patch.patch_weakref_tasks()
attempt_use_uvloop()
if sys.version_info[:3] < (3, 5, 3):
monkey_patch_asyncio()
args = get_arguments()
if args.script is not None:

View File

@@ -0,0 +1,96 @@
"""
Support for Vanderbilt (formerly Siemens) SPC alarm systems.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.spc/
"""
import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.spc import (
SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED,
'1': STATE_ALARM_ARMED_HOME,
'3': STATE_ALARM_ARMED_AWAY}
def _get_alarm_state(spc_mode):
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the SPC alarm control panel platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_AREAS] is None):
return
entities = [SpcAlarm(hass=hass,
area_id=area['id'],
name=area['name'],
state=_get_alarm_state(area['mode']))
for area in discovery_info[ATTR_DISCOVER_AREAS]]
async_add_entities(entities)
class SpcAlarm(alarm.AlarmControlPanel):
"""Represents the SPC alarm panel."""
def __init__(self, hass, area_id, name, state):
"""Initialize the SPC alarm panel."""
self._hass = hass
self._area_id = area_id
self._name = name
self._state = state
self._api = hass.data[DATA_API]
hass.data[DATA_REGISTRY].register_alarm_device(area_id, self)
@asyncio.coroutine
def async_update_from_spc(self, state):
"""Update the alarm panel with a new state."""
self._state = state
yield from self.async_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
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."""
return self._state
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_UNSET)
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_PART_SET)
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
yield from self._api.send_area_command(
self._area_id, SpcWebGateway.AREA_COMMAND_SET)

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.verisure/
"""
import logging
from time import sleep
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import HUB as hub
@@ -20,20 +21,29 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Verisure platform."""
alarms = []
if int(hub.config.get(CONF_ALARM, 1)):
hub.update_alarms()
alarms.extend([
VerisureAlarm(value.id)
for value in hub.alarm_status.values()
])
hub.update_overview()
alarms.append(VerisureAlarm())
add_devices(alarms)
def set_arm_state(state, code=None):
"""Send set arm state command."""
transaction_id = hub.session.set_arm_state(code, state)[
'armStateChangeTransactionId']
_LOGGER.info('verisure set arm state %s', state)
transaction = {}
while 'result' not in transaction:
sleep(0.5)
transaction = hub.session.get_arm_state_transaction(transaction_id)
# pylint: disable=unexpected-keyword-arg
hub.update_overview(no_throttle=True)
class VerisureAlarm(alarm.AlarmControlPanel):
"""Representation of a Verisure alarm status."""
def __init__(self, device_id):
"""Initialize the Verisure alarm panel."""
self._id = device_id
def __init__(self):
"""Initalize the Verisure alarm panel."""
self._state = STATE_UNKNOWN
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
@@ -41,18 +51,13 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def name(self):
"""Return the name of the device."""
return 'Alarm {}'.format(self._id)
return '{} alarm'.format(hub.session.installations[0]['alias'])
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def available(self):
"""Return True if entity is available."""
return hub.available
@property
def code_format(self):
"""Return the code format as regex."""
@@ -65,33 +70,26 @@ class VerisureAlarm(alarm.AlarmControlPanel):
def update(self):
"""Update alarm status."""
hub.update_alarms()
if hub.alarm_status[self._id].status == 'unarmed':
hub.update_overview()
status = hub.get_first("$.armState.statusType")
if status == 'DISARMED':
self._state = STATE_ALARM_DISARMED
elif hub.alarm_status[self._id].status == 'armedhome':
elif status == 'ARMED_HOME':
self._state = STATE_ALARM_ARMED_HOME
elif hub.alarm_status[self._id].status == 'armed':
elif status == 'ARMED_AWAY':
self._state = STATE_ALARM_ARMED_AWAY
elif hub.alarm_status[self._id].status != 'pending':
_LOGGER.error(
"Unknown alarm state %s", hub.alarm_status[self._id].status)
self._changed_by = hub.alarm_status[self._id].name
elif status != 'PENDING':
_LOGGER.error('Unknown alarm state %s', status)
self._changed_by = hub.get_first("$.armState.name")
def alarm_disarm(self, code=None):
"""Send disarm command."""
hub.my_pages.alarm.set(code, 'DISARMED')
_LOGGER.info("Verisure alarm disarming")
hub.my_pages.alarm.wait_while_pending()
set_arm_state('DISARMED', code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
hub.my_pages.alarm.set(code, 'ARMED_HOME')
_LOGGER.info("Verisure alarm arming home")
hub.my_pages.alarm.wait_while_pending()
set_arm_state('ARMED_HOME', code)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
hub.my_pages.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info("Verisure alarm arming away")
hub.my_pages.alarm.wait_while_pending()
set_arm_state('ARMED_AWAY', code)

View File

@@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'alert'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_DONE_MESSAGE = 'done_message'
CONF_CAN_ACK = 'can_acknowledge'
CONF_NOTIFIERS = 'notifiers'
CONF_REPEAT = 'repeat'
@@ -35,6 +36,7 @@ DEFAULT_SKIP_FIRST = False
ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_DONE_MESSAGE, default=None): cv.string,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_STATE, default=STATE_ON): cv.string,
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
@@ -121,10 +123,10 @@ def async_setup(hass, config):
# Setup alerts
for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_ENTITY_ID],
alert[CONF_STATE], alert[CONF_REPEAT],
alert[CONF_SKIP_FIRST], alert[CONF_NOTIFIERS],
alert[CONF_CAN_ACK])
alert[CONF_NAME], alert[CONF_DONE_MESSAGE],
alert[CONF_ENTITY_ID], alert[CONF_STATE],
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])
all_alerts[entity.entity_id] = entity
# Read descriptions
@@ -154,8 +156,8 @@ def async_setup(hass, config):
class Alert(ToggleEntity):
"""Representation of an alert."""
def __init__(self, hass, entity_id, name, watched_entity_id, state,
repeat, skip_first, notifiers, can_ack):
def __init__(self, hass, entity_id, name, done_message, watched_entity_id,
state, repeat, skip_first, notifiers, can_ack):
"""Initialize the alert."""
self.hass = hass
self._name = name
@@ -163,6 +165,7 @@ class Alert(ToggleEntity):
self._skip_first = skip_first
self._notifiers = notifiers
self._can_ack = can_ack
self._done_message = done_message
self._delay = [timedelta(minutes=val) for val in repeat]
self._next_delay = 0
@@ -170,6 +173,7 @@ class Alert(ToggleEntity):
self._firing = False
self._ack = False
self._cancel = None
self._send_done_message = False
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
event.async_track_state_change(
@@ -230,6 +234,8 @@ class Alert(ToggleEntity):
self._cancel()
self._ack = False
self._firing = False
if self._done_message and self._send_done_message:
yield from self._notify_done_message()
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
@@ -249,11 +255,21 @@ class Alert(ToggleEntity):
if not self._ack:
_LOGGER.info("Alerting: %s", self._name)
self._send_done_message = True
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._name})
yield from self._schedule_notify()
@asyncio.coroutine
def _notify_done_message(self, *args):
"""Send notification of complete alert."""
_LOGGER.info("Alerting: %s", self._done_message)
self._send_done_message = False
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._done_message})
@asyncio.coroutine
def async_turn_on(self):
"""Async Unacknowledge alert."""

View File

@@ -1,27 +1,27 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
For more details about this platform, please refer to the documentation at
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/
"""
import logging
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.loader as loader
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
REQUIREMENTS = ['pyarlo==0.0.4']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = 'Data provided by arlo.netgear.com'
DOMAIN = 'arlo'
CONF_ATTRIBUTION = "Data provided by arlo.netgear.com"
DATA_ARLO = 'data_arlo'
DEFAULT_BRAND = 'Netgear Arlo'
DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Camera Setup'
@@ -47,7 +47,7 @@ def setup(hass, config):
arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected:
return False
hass.data['arlo'] = arlo
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
persistent_notification.create(

View File

@@ -29,6 +29,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.frontend import register_built_in_panel
DOMAIN = 'automation'
DEPENDENCIES = ['group']
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'

View File

@@ -11,6 +11,7 @@ import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
CONF_HOST, CONF_INCLUDE, CONF_NAME,
CONF_PASSWORD, CONF_TRIGGER_TIME,
@@ -18,11 +19,12 @@ from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
from homeassistant.components.discovery import SERVICE_AXIS
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==7']
REQUIREMENTS = ['axis==8']
_LOGGER = logging.getLogger(__name__)
@@ -59,6 +61,21 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_VAPIX_CALL = 'vapix_call'
SERVICE_VAPIX_CALL_RESPONSE = 'vapix_call_response'
SERVICE_CGI = 'cgi'
SERVICE_ACTION = 'action'
SERVICE_PARAM = 'param'
SERVICE_DEFAULT_CGI = 'param.cgi'
SERVICE_DEFAULT_ACTION = 'update'
SERVICE_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(SERVICE_PARAM): cv.string,
vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string,
vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string,
})
def request_configuration(hass, name, host, serialnumber):
"""Request configuration steps from the user."""
@@ -135,23 +152,34 @@ def setup(hass, base_config):
def axis_device_discovered(service, discovery_info):
"""Called when axis devices has been found."""
host = discovery_info['host']
host = discovery_info[CONF_HOST]
name = discovery_info['hostname']
serialnumber = discovery_info['properties']['macaddress']
if serialnumber not in AXIS_DEVICES:
config_file = _read_config(hass)
if serialnumber in config_file:
# Device config saved to file
try:
config = DEVICE_SCHEMA(config_file[serialnumber])
config[CONF_HOST] = host
except vol.Invalid as err:
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
return False
if not setup_device(hass, config):
_LOGGER.error("Couldn\'t set up %s", config['name'])
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
else:
# New device, create configuration request for UI
request_configuration(hass, name, host, serialnumber)
else:
# Device already registered, but on a different IP
device = AXIS_DEVICES[serialnumber]
device.url = host
async_dispatcher_send(hass,
DOMAIN + '_' + device.name + '_new_ip',
host)
# Register discovery service
discovery.listen(hass, SERVICE_AXIS, axis_device_discovered)
if DOMAIN in base_config:
@@ -160,7 +188,30 @@ def setup(hass, base_config):
if CONF_NAME not in config:
config[CONF_NAME] = device
if not setup_device(hass, config):
_LOGGER.error("Couldn\'t set up %s", config['name'])
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
# Services to communicate with device.
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def vapix_service(call):
"""Service to send a message."""
for _, device in AXIS_DEVICES.items():
if device.name == call.data[CONF_NAME]:
response = device.do_request(call.data[SERVICE_CGI],
call.data[SERVICE_ACTION],
call.data[SERVICE_PARAM])
hass.bus.async_fire(SERVICE_VAPIX_CALL_RESPONSE, response)
return True
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME])
return False
# Register service with Home Assistant.
hass.services.register(DOMAIN,
SERVICE_VAPIX_CALL,
vapix_service,
descriptions[DOMAIN][SERVICE_VAPIX_CALL],
schema=SERVICE_SCHEMA)
return True
@@ -190,8 +241,16 @@ def setup_device(hass, config):
if enable_metadatastream:
device.initialize_new_event = event_initialized
device.initiate_metadatastream()
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')
AXIS_DEVICES[device.serial_number] = device
return True
@@ -311,4 +370,4 @@ REMAP = [{'type': 'motion',
'class': 'input',
'topic': 'tns1:Device/tnsaxis:IO/Port',
'subscribe': 'onvif:Device/axis:IO/Port',
'platform': 'sensor'}, ]
'platform': 'binary_sensor'}, ]

View File

@@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([ArestBinarySensor(
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
device_class, pin)])
device_class, pin)], True)
class ArestBinarySensor(BinarySensorDevice):
@@ -64,7 +64,6 @@ class ArestBinarySensor(BinarySensorDevice):
self._name = name
self._device_class = device_class
self._pin = pin
self.update()
if self._pin is not None:
request = requests.get(

View File

@@ -8,19 +8,18 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
ATTR_REGION, ATTR_VCPUS, DATA_DIGITAL_OCEAN)
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'motion'
DEFAULT_SENSOR_CLASS = 'moving'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -30,19 +29,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Digital Ocean droplet sensor."""
digital_ocean = get_component('digital_ocean')
digital = hass.data.get(DATA_DIGITAL_OCEAN)
if not digital:
return False
droplets = config.get(CONF_DROPLETS)
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
droplet_id = digital.get_droplet_id(droplet)
if droplet_id is None:
_LOGGER.error("Droplet %s is not available", droplet)
return False
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
dev.append(DigitalOceanBinarySensor(digital, droplet_id))
add_devices(dev)
add_devices(dev, True)
class DigitalOceanBinarySensor(BinarySensorDevice):
@@ -53,7 +54,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
self._digital_ocean = do
self._droplet_id = droplet_id
self._state = None
self.update()
self.data = None
@property
def name(self):

View File

@@ -80,6 +80,12 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
elif value2 == 0x10:
self.which = 1
self.onoff = 1
elif value2 == 0x37:
self.which = 10
self.onoff = 0
elif value2 == 0x15:
self.which = 10
self.onoff = 1
self.hass.bus.fire('button_pressed', {'id': self.dev_id,
'pushed': value,
'which': self.which,

View File

@@ -1,5 +1,5 @@
"""
Support for Homematic binary sensors.
Support for HomeMatic binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematic/
@@ -29,7 +29,7 @@ SENSOR_TYPES_CLASS = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Homematic binary sensor platform."""
"""Set up the HomeMatic binary sensor platform."""
if discovery_info is None:
return
@@ -43,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HMBinarySensor(HMDevice, BinarySensorDevice):
"""Representation of a binary Homematic device."""
"""Representation of a binary HomeMatic device."""
@property
def is_on(self):
@@ -54,16 +54,14 @@ class HMBinarySensor(HMDevice, BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
# If state is MOTION (RemoteMotion works only)
"""Return the class of this sensor from DEVICE_CLASSES."""
# If state is MOTION (Only RemoteMotion working)
if self._state == 'MOTION':
return 'motion'
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
def _init_data_struct(self):
"""Generate a data struct (self._data) from the Homematic metadata."""
# add state to data struct
"""Generate the data dictionary (self._data) from metadata."""
# Add state to data struct
if self._state:
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})

View File

@@ -50,6 +50,10 @@ class ModbusCoilSensor(BinarySensorDevice):
self._coil = int(coil)
self._value = None
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the sensor."""

View File

@@ -0,0 +1,233 @@
"""
Support for RFXtrx binary sensors.
Lighting4 devices (sensors based on PT2262 encoder) are supported and
tested. Other types may need some work.
"""
import logging
import voluptuous as vol
from homeassistant.components import rfxtrx
from homeassistant.util import slugify
from homeassistant.util import dt as dt_util
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import event as evt
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rfxtrx import (
ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_OFF_DELAY, ATTR_FIREEVENT,
ATTR_DATA_BITS, CONF_DEVICES
)
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF
)
DEPENDENCIES = ["rfxtrx"]
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({
vol.Required("platform"): rfxtrx.DOMAIN,
vol.Optional(CONF_DEVICES, default={}): vol.All(
dict, rfxtrx.valid_binary_sensor),
vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean,
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Binary Sensor platform to rfxtrx."""
import RFXtrx as rfxtrxmod
sensors = []
for packet_id, entity in config['devices'].items():
event = rfxtrx.get_rfx_object(packet_id)
device_id = slugify(event.device.id_string.lower())
if device_id in rfxtrx.RFX_DEVICES:
continue
if entity[ATTR_DATA_BITS] is not None:
_LOGGER.info("Masked device id: %s",
rfxtrx.get_pt2262_deviceid(device_id,
entity[ATTR_DATA_BITS]))
_LOGGER.info("Add %s rfxtrx.binary_sensor (class %s)",
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
entity[CONF_DEVICE_CLASS],
entity[ATTR_FIREEVENT],
entity[ATTR_OFF_DELAY],
entity[ATTR_DATA_BITS],
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device.hass = hass
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device
add_devices_callback(sensors)
# pylint: disable=too-many-branches
def binary_sensor_update(event):
"""Callback for control updates from the RFXtrx gateway."""
if not isinstance(event, rfxtrxmod.ControlEvent):
return
device_id = slugify(event.device.id_string.lower())
if device_id in rfxtrx.RFX_DEVICES:
sensor = rfxtrx.RFX_DEVICES[device_id]
else:
sensor = rfxtrx.get_pt2262_device(device_id)
if sensor is None:
# Add the entity if not exists and automatic_add is True
if not config[ATTR_AUTOMATIC_ADD]:
return
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.info("Found possible matching deviceid %s.",
poss_id)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id)
rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor])
_LOGGER.info("Added binary sensor %s "
"(Device_id: %s Class: %s Sub: %s)",
pkt_id,
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
elif not isinstance(sensor, RfxtrxBinarySensor):
return
else:
_LOGGER.info("Binary sensor update "
"(Device_id: %s Class: %s Sub: %s)",
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))
else:
rfxtrx.apply_received_command(event)
if (sensor.is_on and sensor.off_delay is not None and
sensor.delay_listener is None):
def off_delay_listener(now):
"""Switch device off after a delay."""
sensor.delay_listener = None
sensor.update_state(False)
sensor.delay_listener = evt.track_point_in_time(
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay
)
# Subscribe to main rfxtrx events
if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
# pylint: disable=too-many-instance-attributes,too-many-arguments
class RfxtrxBinarySensor(BinarySensorDevice):
"""An Rfxtrx binary sensor."""
def __init__(self, event, name, device_class=None,
should_fire=False, off_delay=None, data_bits=None,
cmd_on=None, cmd_off=None):
"""Initialize the sensor."""
self.event = event
self._name = name
self._should_fire_event = should_fire
self._device_class = device_class
self._off_delay = off_delay
self._state = False
self.delay_listener = None
self._data_bits = data_bits
self._cmd_on = cmd_on
self._cmd_off = cmd_off
if data_bits is not None:
self._masked_id = rfxtrx.get_pt2262_deviceid(
event.device.id_string.lower(),
data_bits)
def __str__(self):
"""Return the name of the sensor."""
return self._name
@property
def name(self):
"""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)."""
return self._masked_id
@property
def data_bits(self):
"""Return the number of data bits."""
return self._data_bits
@property
def cmd_on(self):
"""Return the value of the 'On' command."""
return self._cmd_on
@property
def cmd_off(self):
"""Return the value of the 'Off' command."""
return self._cmd_off
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def should_fire_event(self):
"""Return is the device must fire event."""
return self._should_fire_event
@property
def device_class(self):
"""Return the sensor class."""
return self._device_class
@property
def off_delay(self):
"""Return the off_delay attribute value."""
return self._off_delay
@property
def is_on(self):
"""Return true if the sensor state is True."""
return self._state
def apply_cmd(self, cmd):
"""Apply a command for updating the state."""
if cmd == self.cmd_on:
self.update_state(True)
elif cmd == self.cmd_off:
self.update_state(False)
def update_state(self, state):
"""Update the state of the device."""
self._state = state
self.schedule_update_ha_state()

View File

@@ -0,0 +1,99 @@
"""
Support for Vanderbilt (formerly Siemens) SPC alarm systems.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.spc/
"""
import logging
import asyncio
from homeassistant.components.spc import (
ATTR_DISCOVER_DEVICES, DATA_REGISTRY)
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (STATE_UNAVAILABLE, STATE_ON, STATE_OFF)
_LOGGER = logging.getLogger(__name__)
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion',
'1': 'opening',
'3': 'smoke'}
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF,
'1': STATE_ON}
def _get_device_class(spc_type):
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
def _get_sensor_state(spc_input):
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
def _create_sensor(hass, zone):
return SpcBinarySensor(zone_id=zone['id'],
name=zone['zone_name'],
state=_get_sensor_state(zone['input']),
device_class=_get_device_class(zone['type']),
spc_registry=hass.data[DATA_REGISTRY])
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Initialize the platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return
async_add_entities(
_create_sensor(hass, zone)
for zone in discovery_info[ATTR_DISCOVER_DEVICES]
if _get_device_class(zone['type']))
class SpcBinarySensor(BinarySensorDevice):
"""Represents a sensor based on an SPC zone."""
def __init__(self, zone_id, name, state, device_class, spc_registry):
"""Initialize the sensor device."""
self._zone_id = zone_id
self._name = name
self._state = state
self._device_class = device_class
spc_registry.register_sensor_device(zone_id, self)
@asyncio.coroutine
def async_update_from_spc(self, state):
"""Update the state of the device."""
self._state = state
yield from self.async_update_ha_state()
@property
def name(self):
"""The name of the device."""
return self._name
@property
def is_on(self):
"""Whether the device is switched on."""
return self._state == STATE_ON
@property
def hidden(self) -> bool:
"""Whether the device is hidden by default."""
# these type of sensors are probably mainly used for automations
return True
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_class(self):
"""The device class."""
return self._device_class

View File

@@ -0,0 +1,86 @@
"""
Support for Taps Affs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.tapsaff/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME)
REQUIREMENTS = ['tapsaff==0.1.3']
_LOGGER = logging.getLogger(__name__)
CONF_LOCATION = 'location'
DEFAULT_NAME = 'Taps Aff'
SCAN_INTERVAL = timedelta(minutes=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_LOCATION): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Taps Aff binary sensor."""
name = config.get(CONF_NAME)
location = config.get(CONF_LOCATION)
taps_aff_data = TapsAffData(location)
add_devices([TapsAffSensor(taps_aff_data, name)], True)
class TapsAffSensor(BinarySensorDevice):
"""Implementation of a Taps Aff binary sensor."""
def __init__(self, taps_aff_data, name):
"""Initialize the Taps Aff sensor."""
self.data = taps_aff_data
self._name = name
@property
def name(self):
"""Return the name of the sensor."""
return '{}'.format(self._name)
@property
def is_on(self):
"""Return true if taps aff."""
return self.data.is_taps_aff
def update(self):
"""Get the latest data."""
self.data.update()
class TapsAffData(object):
"""Class for handling the data retrieval for pins."""
def __init__(self, location):
"""Initialize the sensor."""
from tapsaff import TapsAff
self._is_taps_aff = None
self.taps_aff = TapsAff(location)
@property
def is_taps_aff(self):
"""Return true if taps aff."""
return self._is_taps_aff
def update(self):
"""Get the latest data from the Taps Aff API and updates the states."""
try:
self._is_taps_aff = self.taps_aff.is_taps_aff
except RuntimeError:
_LOGGER.error("Update failed. Check configured location")

View File

@@ -0,0 +1,59 @@
"""
Interfaces with Verisure sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.verisure/
"""
import logging
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.verisure import CONF_DOOR_WINDOW
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Verisure binary sensors."""
sensors = []
hub.update_overview()
if int(hub.config.get(CONF_DOOR_WINDOW, 1)):
sensors.extend([
VerisureDoorWindowSensor(device_label)
for device_label in hub.get(
"$.doorWindow.doorWindowDevice[*].deviceLabel")])
add_devices(sensors)
class VerisureDoorWindowSensor(BinarySensorDevice):
"""Verisure door window sensor."""
def __init__(self, device_label):
"""Initialize the modbus coil sensor."""
self._device_label = device_label
@property
def name(self):
"""Return the name of the binary sensor."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area",
self._device_label)
@property
def is_on(self):
"""Return the state of the sensor."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state",
self._device_label) == "OPEN"
@property
def available(self):
"""Return True if entity is available."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
self._device_label) is not None
def update(self):
"""Update the state of the sensor."""
hub.update_overview()

View File

@@ -30,7 +30,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
if disc_info is None:
return
if not any([data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]]):
if not any(data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]):
return
calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE))
@@ -39,7 +39,6 @@ def setup_platform(hass, config, add_devices, disc_info=None):
for data in disc_info[CONF_ENTITIES] if data[CONF_TRACK]])
# pylint: disable=too-many-instance-attributes
class GoogleCalendarEventDevice(CalendarEventDevice):
"""A calendar event device."""

View File

@@ -12,13 +12,16 @@ from datetime import timedelta
import logging
import hashlib
from random import SystemRandom
import os
import aiohttp
from aiohttp import web
import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import ATTR_ENTITY_PICTURE
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.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
@@ -26,9 +29,12 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.helpers.event import async_track_time_interval
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
SERVICE_EN_MOTION = 'enable_motion_detection'
SERVICE_DISEN_MOTION = 'disable_motion_detection'
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SCAN_INTERVAL = timedelta(seconds=30)
@@ -38,11 +44,30 @@ STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
DEFAULT_CONTENT_TYPE = 'image/jpeg'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
_RND = SystemRandom()
CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_EN_MOTION, data))
def disable_motion_detection(hass, entity_id=None):
"""Disable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_DISEN_MOTION, data))
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
@@ -92,6 +117,44 @@ def async_setup(hass, config):
hass.async_add_job(entity.async_update_ha_state())
async_track_time_interval(hass, update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine
def async_handle_camera_service(service):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
for camera in target_cameras:
if service.service == SERVICE_EN_MOTION:
yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISEN_MOTION:
yield from camera.async_disable_motion_detection()
update_tasks = []
for camera in target_cameras:
if not camera.should_poll:
continue
update_coro = hass.async_add_job(
camera.async_update_ha_state(True))
if hasattr(camera, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
DOMAIN, SERVICE_EN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_EN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_DISEN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_DISEN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
return True
@@ -101,6 +164,7 @@ class Camera(Entity):
def __init__(self):
"""Initialize a camera."""
self.is_streaming = False
self.content_type = DEFAULT_CONTENT_TYPE
self.access_tokens = collections.deque([], 2)
self.async_update_token()
@@ -124,6 +188,11 @@ class Camera(Entity):
"""Return the camera brand."""
return None
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return None
@property
def model(self):
"""Return the camera model."""
@@ -149,16 +218,17 @@ class Camera(Entity):
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
'boundary=--frameboundary')
yield from response.prepare(request)
def write(img_bytes):
"""Write image to stream."""
response.write(bytes(
'--jpegboundary\r\n'
'Content-Type: image/jpeg\r\n'
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n')
self.content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
last_image = None
@@ -199,6 +269,22 @@ class Camera(Entity):
else:
return STATE_IDLE
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
raise NotImplementedError()
def async_enable_motion_detection(self):
"""Call the job and enable motion detection."""
return self.hass.async_add_job(self.enable_motion_detection)
def disable_motion_detection(self):
"""Disable motion detection in camera."""
raise NotImplementedError()
def async_disable_motion_detection(self):
"""Call the job and disable motion detection."""
return self.hass.async_add_job(self.disable_motion_detection)
@property
def state_attributes(self):
"""Return the camera state attributes."""
@@ -212,6 +298,9 @@ class Camera(Entity):
if self.brand:
attr['brand'] = self.brand
if self.motion_detection_enabled:
attr['motion_detection'] = self.motion_detection_enabled
return attr
@callback
@@ -269,7 +358,8 @@ class CameraImageView(CameraView):
image = yield from camera.async_camera_image()
if image:
return web.Response(body=image, content_type='image/jpeg')
return web.Response(body=image,
content_type=camera.content_type)
return web.Response(status=500)

View File

@@ -6,32 +6,32 @@ https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
DEPENDENCIES = ['arlo', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up an Arlo IP Camera."""
arlo = hass.data.get('arlo')
arlo = hass.data.get(DATA_ARLO)
if not arlo:
return False
@@ -40,7 +40,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cameras.append(ArloCam(hass, camera, config))
async_add_devices(cameras, True)
return True
class ArloCam(Camera):
@@ -49,14 +48,15 @@ class ArloCam(Camera):
def __init__(self, hass, camera, device_info):
"""Initialize an Arlo camera."""
super().__init__()
self._camera = camera
self._base_stn = hass.data['arlo'].base_stations[0]
self._name = self._camera.name
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
def camera_image(self):
"""Return a still image reponse from the camera."""
"""Return a still image response from the camera."""
return self._camera.last_image
@asyncio.coroutine
@@ -90,3 +90,27 @@ class ArloCam(Camera):
def brand(self):
"""Camera brand."""
return DEFAULT_BRAND
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status
def set_base_station_mode(self, mode):
"""Set the mode in the base station."""
self._base_stn.mode = mode
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
self.set_base_station_mode(ARLO_MODE_ARMED)
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)

View File

@@ -7,15 +7,16 @@ https://home-assistant.io/components/camera.axis/
import logging
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['axis']
DOMAIN = 'axis'
DEPENDENCIES = [DOMAIN]
def _get_image_url(host, mode):
@@ -27,12 +28,29 @@ def _get_image_url(host, mode):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis camera."""
device_info = {
CONF_NAME: discovery_info['name'],
CONF_USERNAME: discovery_info['username'],
CONF_PASSWORD: discovery_info['password'],
CONF_MJPEG_URL: _get_image_url(discovery_info['host'], 'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info['host'], 'single'),
config = {
CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME],
CONF_PASSWORD: discovery_info[CONF_PASSWORD],
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST], 'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST],
'single'),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
}
add_devices([MjpegCamera(hass, device_info)])
add_devices([AxisCamera(hass, config)])
class AxisCamera(MjpegCamera):
"""AxisCamera class."""
def __init__(self, hass, config):
"""Initialize Axis Communications camera component."""
super().__init__(hass, config)
async_dispatcher_connect(hass,
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
self._new_ip)
def _new_ip(self, host):
"""Set new IP for video stream."""
self._mjpeg_url = _get_image_url(host, 'mjpeg')
self._still_image_url = _get_image_url(host, 'mjpeg')

View File

@@ -5,25 +5,29 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import os
import logging
import homeassistant.util.dt as dt_util
from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo camera platform."""
add_devices([
DemoCamera('Demo camera')
DemoCamera(hass, config, 'Demo camera')
])
class DemoCamera(Camera):
"""The representation of a Demo camera."""
def __init__(self, name):
def __init__(self, hass, config, name):
"""Initialize demo camera component."""
super().__init__()
self._parent = hass
self._name = name
self._motion_status = False
def camera_image(self):
"""Return a faked still image response."""
@@ -38,3 +42,21 @@ class DemoCamera(Camera):
def name(self):
"""Return the name of this camera."""
return self._name
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False

View File

@@ -17,13 +17,15 @@ from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.components.camera import (
PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
_LOGGER = logging.getLogger(__name__)
CONF_CONTENT_TYPE = 'content_type'
CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change'
CONF_STILL_IMAGE_URL = 'still_image_url'
@@ -37,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string,
})
@@ -59,6 +62,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
self.content_type = device_info[CONF_CONTENT_TYPE]
username = device_info.get(CONF_USERNAME)
password = device_info.get(CONF_PASSWORD)

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.local_file/
"""
import logging
import mimetypes
import os
import voluptuous as vol
@@ -46,6 +47,10 @@ class LocalFile(Camera):
self._name = name
self._file_path = file_path
# Set content type of local file
content, _ = mimetypes.guess_type(file_path)
if content is not None:
self.content_type = content
def camera_image(self):
"""Return image response."""

View File

@@ -0,0 +1,102 @@
"""
Support for ONVIF Cameras with FFmpeg as decoder.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.onvif/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
DATA_FFMPEG)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['onvif-py3==0.1.3',
'suds-py3==1.3.3.0',
'http://github.com/tgaugry/suds-passworddigest-py3'
'/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip'
'#suds-passworddigest-py3==0.1.2a']
DEPENDENCIES = ['ffmpeg']
DEFAULT_NAME = 'ONVIF Camera'
DEFAULT_PORT = 5000
DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a ONVIF camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)):
return
async_add_devices([ONVIFCamera(hass, config)])
class ONVIFCamera(Camera):
"""An implementation of an ONVIF camera."""
def __init__(self, hass, config):
"""Initialize a ONVIF camera."""
from onvif import ONVIFService
super().__init__()
self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = '-q:v 2'
media = ONVIFService(
'http://{}:{}/onvif/device_service'.format(
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)
)
self._input = media.GetStreamUri().Uri
_LOGGER.debug("ONVIF Camera Using the following URL for %s: %s",
self._name, self._input)
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(
self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments)
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary,
loop=self.hass.loop)
yield from stream.open_camera(
self._input, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@property
def name(self):
"""Return the name of this camera."""
return self._name

View File

@@ -0,0 +1,17 @@
# Describes the format for available camera services
enable_motion_detection:
description: Enable the motion detection in a camera
fields:
entity_id:
description: Name(s) of entities to enable motion detection
example: 'camera.living_room_camera'
disable_motion_detection:
description: Disable the motion detection in a camera
fields:
entity_id:
description: Name(s) of entities to disable motion detection
example: 'camera.living_room_camera'

View File

@@ -24,22 +24,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not os.access(directory_path, os.R_OK):
_LOGGER.error("file path %s is not readable", directory_path)
return False
hub.update_smartcam()
hub.update_overview()
smartcams = []
smartcams.extend([
VerisureSmartcam(hass, value.deviceLabel, directory_path)
for value in hub.smartcam_status.values()])
VerisureSmartcam(hass, device_label, directory_path)
for device_label in hub.get(
"$.customerImageCameras[*].deviceLabel")])
add_devices(smartcams)
class VerisureSmartcam(Camera):
"""Representation of a Verisure camera."""
def __init__(self, hass, device_id, directory_path):
def __init__(self, hass, device_label, directory_path):
"""Initialize Verisure File Camera component."""
super().__init__()
self._device_id = device_id
self._device_label = device_label
self._directory_path = directory_path
self._image = None
self._image_id = None
@@ -58,28 +59,27 @@ class VerisureSmartcam(Camera):
def check_imagelist(self):
"""Check the contents of the image list."""
hub.update_smartcam_imagelist()
if (self._device_id not in hub.smartcam_dict or
not hub.smartcam_dict[self._device_id]):
hub.update_smartcam_imageseries()
image_ids = hub.get_image_info(
"$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId",
self._device_label)
if not image_ids:
return
images = hub.smartcam_dict[self._device_id]
new_image_id = images[0]
_LOGGER.debug("self._device_id=%s, self._images=%s, "
"self._new_image_id=%s", self._device_id,
images, new_image_id)
new_image_id = image_ids[0]
if (new_image_id == '-1' or
self._image_id == new_image_id):
_LOGGER.debug("The image is the same, or loading image_id")
return
_LOGGER.debug("Download new image %s", new_image_id)
hub.my_pages.smartcam.download_image(
self._device_id, new_image_id, self._directory_path)
new_image_path = os.path.join(
self._directory_path, '{}{}'.format(new_image_id, '.jpg'))
hub.session.download_image(
self._device_label, new_image_id, new_image_path)
_LOGGER.debug("Old image_id=%s", self._image_id)
self.delete_image(self)
self._image_id = new_image_id
self._image = os.path.join(
self._directory_path, '{}{}'.format(self._image_id, '.jpg'))
self._image = new_image_path
def delete_image(self, event):
"""Delete an old image."""
@@ -95,4 +95,6 @@ class VerisureSmartcam(Camera):
@property
def name(self):
"""Return the name of this camera."""
return hub.smartcam_status[self._device_id].location
return hub.get_first(
"$.customerImageCameras[?(@.deviceLabel=='%s')].area",
self._device_label)

View File

@@ -693,8 +693,14 @@ class ClimateDevice(Entity):
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if temp is None or not isinstance(temp, Number):
if temp is None:
return temp
# if the temperature is not a number this can cause issues
# with polymer components, so bail early there.
if not isinstance(temp, Number):
raise TypeError("Temperature is not a number: %s" % temp)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(
temp, self.temperature_unit, self.unit_of_measurement)

View File

@@ -0,0 +1,148 @@
"""
Platform for Flexit AC units with CI66 Modbus adapter.
Example configuration:
climate:
- platform: flexit
name: Main AC
slave: 21
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.flexit/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_SLAVE, TEMP_CELSIUS,
ATTR_TEMPERATURE, DEVICE_DEFAULT_NAME)
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
import homeassistant.components.modbus as modbus
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyflexit==0.3']
DEPENDENCIES = ['modbus']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Flexit Platform."""
modbus_slave = config.get(CONF_SLAVE, None)
name = config.get(CONF_NAME, None)
add_devices([Flexit(modbus_slave, name)], True)
class Flexit(ClimateDevice):
"""Representation of a Flexit AC unit."""
def __init__(self, modbus_slave, name):
"""Initialize the unit."""
from pyflexit import pyflexit
self._name = name
self._slave = modbus_slave
self._target_temperature = None
self._current_temperature = None
self._current_fan_mode = None
self._current_operation = None
self._fan_list = ['Off', 'Low', 'Medium', 'High']
self._current_operation = None
self._filter_hours = None
self._filter_alarm = None
self._heat_recovery = None
self._heater_enabled = False
self._heating = None
self._cooling = None
self._alarm = False
self.unit = pyflexit.pyflexit(modbus.HUB, modbus_slave)
def update(self):
"""Update unit attributes."""
if not self.unit.update():
_LOGGER.warning("Modbus read failed")
self._target_temperature = self.unit.get_target_temp
self._current_temperature = self.unit.get_temp
self._current_fan_mode =\
self._fan_list[self.unit.get_fan_speed]
self._filter_hours = self.unit.get_filter_hours
# Mechanical heat recovery, 0-100%
self._heat_recovery = self.unit.get_heat_recovery
# Heater active 0-100%
self._heating = self.unit.get_heating
# Cooling active 0-100%
self._cooling = self.unit.get_cooling
# Filter alarm 0/1
self._filter_alarm = self.unit.get_filter_alarm
# Heater enabled or not. Does not mean it's necessarily heating
self._heater_enabled = self.unit.get_heater_enabled
# Current operation mode
self._current_operation = self.unit.get_operation
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {
'filter_hours': self._filter_hours,
'filter_alarm': self._filter_alarm,
'heat_recovery': self._heat_recovery,
'heating': self._heating,
'heater_enabled': self._heater_enabled,
'cooling': self._cooling
}
@property
def should_poll(self):
"""Return the polling state."""
return True
@property
def name(self):
"""Return the name of the climate device."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
"""Return the list of available fan modes."""
return self._fan_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
self.unit.set_temp(self._target_temperature)
def set_fan_mode(self, fan):
"""Set new fan mode."""
self.unit.set_fan_speed(fan)

View File

@@ -67,7 +67,12 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self._values.get(self.gateway.const.SetReq.V_TEMP)
value = self._values.get(self.gateway.const.SetReq.V_TEMP)
if value is not None:
value = float(value)
return value
@property
def target_temperature(self):
@@ -79,21 +84,21 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
if temp is None:
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return temp
return float(temp)
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_COOL)
return float(self._values.get(set_req.V_HVAC_SETPOINT_COOL))
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return float(self._values.get(set_req.V_HVAC_SETPOINT_HEAT))
@property
def current_operation(self):

View File

@@ -15,7 +15,7 @@ from homeassistant.components.climate import (
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.2']
REQUIREMENTS = ['radiotherm==1.3']
_LOGGER = logging.getLogger(__name__)
@@ -84,6 +84,7 @@ class RadioThermostat(ClimateDevice):
self._name = None
self._fmode = None
self._tmode = None
self._tstate = None
self._hold_temp = hold_temp
self._away = False
self._away_temps = away_temps
@@ -140,6 +141,7 @@ class RadioThermostat(ClimateDevice):
self._name = self.device.name['raw']
self._fmode = self.device.fmode['human']
self._tmode = self.device.tmode['human']
self._tstate = self.device.tstate['human']
if self._tmode == 'Cool':
self._target_temperature = self.device.t_cool['raw']
@@ -147,6 +149,12 @@ class RadioThermostat(ClimateDevice):
elif self._tmode == 'Heat':
self._target_temperature = self.device.t_heat['raw']
self._current_operation = STATE_HEAT
elif self._tmode == 'Auto':
if self._tstate == 'Cool':
self._target_temperature = self.device.t_cool['raw']
elif self._tstate == 'Heat':
self._target_temperature = self.device.t_heat['raw']
self._current_operation = STATE_AUTO
else:
self._current_operation = STATE_IDLE
@@ -159,6 +167,12 @@ class RadioThermostat(ClimateDevice):
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_HEAT:
self.device.t_heat = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_AUTO:
if self._tstate == 'Cool':
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._tstate == 'Heat':
self.device.t_heat = round(temperature * 2.0) / 2.0
if self._hold_temp or self._away:
self.device.hold = 1
else:

View File

@@ -16,6 +16,7 @@ from homeassistant.const import (
ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.temperature import convert as convert_temperature
@@ -52,9 +53,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)):
if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]:
devices.append(SensiboClimate(client, dev))
except aiohttp.client_exceptions.ClientConnectorError:
except (aiohttp.client_exceptions.ClientConnectorError,
asyncio.TimeoutError):
_LOGGER.exception('Failed to connct to Sensibo servers.')
return False
raise PlatformNotReady
if devices:
async_add_devices(devices)

View File

@@ -24,6 +24,17 @@ CONST_OVERLAY_MANUAL = 'MANUAL'
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = 'TIMER'
CONST_MODE_FAN_HIGH = 'HIGH'
CONST_MODE_FAN_MIDDLE = 'MIDDLE'
CONST_MODE_FAN_LOW = 'LOW'
FAN_MODES_LIST = {
CONST_MODE_FAN_HIGH: 'High',
CONST_MODE_FAN_MIDDLE: 'Middle',
CONST_MODE_FAN_LOW: 'Low',
CONST_MODE_OFF: 'Off',
}
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: 'Manual',
CONST_OVERLAY_TIMER: 'Timer',
@@ -60,9 +71,15 @@ def create_climate_device(tado, hass, zone, name, zone_id):
capabilities = tado.get_capabilities(zone_id)
unit = TEMP_CELSIUS
min_temp = float(capabilities['temperatures']['celsius']['min'])
max_temp = float(capabilities['temperatures']['celsius']['max'])
ac_mode = capabilities['type'] != 'HEATING'
ac_mode = capabilities['type'] == 'AIR_CONDITIONING'
if ac_mode:
temperatures = capabilities['HEAT']['temperatures']
else:
temperatures = capabilities['temperatures']
min_temp = float(temperatures['celsius']['min'])
max_temp = float(temperatures['celsius']['max'])
data_id = 'zone {} {}'.format(name, zone_id)
device = TadoClimate(tado,
@@ -107,7 +124,9 @@ class TadoClimate(ClimateDevice):
self._max_temp = max_temp
self._target_temp = None
self._tolerance = tolerance
self._cooling = False
self._current_fan = CONST_MODE_OFF
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
@@ -129,13 +148,32 @@ class TadoClimate(ClimateDevice):
@property
def current_operation(self):
"""Return current readable operation mode."""
return OPERATION_LIST.get(self._current_operation)
if self._cooling:
return "Cooling"
else:
return OPERATION_LIST.get(self._current_operation)
@property
def operation_list(self):
"""Return the list of available operation modes (readable)."""
return list(OPERATION_LIST.values())
@property
def current_fan_mode(self):
"""Return the fan setting."""
if self.ac_mode:
return FAN_MODES_LIST.get(self._current_fan)
else:
return None
@property
def fan_list(self):
"""List of available fan modes."""
if self.ac_mode:
return list(FAN_MODES_LIST.values())
else:
return None
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
@@ -205,27 +243,27 @@ class TadoClimate(ClimateDevice):
if 'sensorDataPoints' in data:
sensor_data = data['sensorDataPoints']
temperature = float(
sensor_data['insideTemperature']['celsius'])
humidity = float(
sensor_data['humidity']['percentage'])
setting = 0
unit = TEMP_CELSIUS
if 'insideTemperature' in sensor_data:
temperature = float(
sensor_data['insideTemperature']['celsius'])
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
if 'humidity' in sensor_data:
humidity = float(
sensor_data['humidity']['percentage'])
self._cur_humidity = humidity
# temperature setting will not exist when device is off
if 'temperature' in data['setting'] and \
data['setting']['temperature'] is not None:
setting = float(
data['setting']['temperature']['celsius'])
unit = TEMP_CELSIUS
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
self._target_temp = self.hass.config.units.temperature(
setting, unit)
self._cur_humidity = humidity
self._target_temp = self.hass.config.units.temperature(
setting, unit)
if 'tadoMode' in data:
mode = data['tadoMode']
@@ -235,29 +273,39 @@ class TadoClimate(ClimateDevice):
power = data['setting']['power']
if power == 'OFF':
self._current_operation = CONST_MODE_OFF
self._current_fan = CONST_MODE_OFF
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._device_is_active = False
else:
self._device_is_active = True
if 'overlay' in data and data['overlay'] is not None:
overlay = True
termination = data['overlay']['termination']['type']
else:
if self._device_is_active:
overlay = False
termination = ""
overlay_data = None
termination = self._current_operation
cooling = False
fan_speed = CONST_MODE_OFF
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the 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"
if overlay and self._device_is_active:
# There is an overlay the device is on
self._overlay_mode = termination
self._current_operation = termination
else:
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._cooling = cooling
self._current_fan = fan_speed
def _control_heating(self):
"""Send new target temperature to mytado."""

View File

@@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([WinkAC(climate, hass, temp_unit)])
# pylint: disable=abstract-method,too-many-public-methods, too-many-branches
# pylint: disable=abstract-method
class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat."""

View File

@@ -0,0 +1,134 @@
"""
Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/comfoconnect/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import (discovery)
from homeassistant.helpers.dispatcher import (dispatcher_send)
REQUIREMENTS = ['pycomfoconnect==0.3']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'comfoconnect'
SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = 'comfoconnect_update_received'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature'
ATTR_OUTSIDE_HUMIDITY = 'outside_humidity'
ATTR_AIR_FLOW_SUPPLY = 'air_flow_supply'
ATTR_AIR_FLOW_EXHAUST = 'air_flow_exhaust'
CONF_USER_AGENT = 'user_agent'
DEFAULT_NAME = 'ComfoAirQ'
DEFAULT_PIN = 0
DEFAULT_TOKEN = '00000000000000000000000000000001'
DEFAULT_USER_AGENT = 'Home Assistant'
DEVICE = None
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN):
vol.Length(min=32, max=32, msg='invalid token'),
vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the ComfoConnect bridge."""
from pycomfoconnect import (Bridge)
conf = config[DOMAIN]
host = conf.get(CONF_HOST)
name = conf.get(CONF_NAME)
token = conf.get(CONF_TOKEN)
user_agent = conf.get(CONF_USER_AGENT)
pin = conf.get(CONF_PIN)
# Run discovery on the configured ip
bridges = Bridge.discover(host)
if not bridges:
_LOGGER.error("Could not connect to ComfoConnect bridge on %s", host)
return False
bridge = bridges[0]
_LOGGER.info("Bridge found: %s (%s)", bridge.uuid.hex(), bridge.host)
# Setup ComfoConnect Bridge
ccb = ComfoConnectBridge(hass, bridge, name, token, user_agent, pin)
hass.data[DOMAIN] = ccb
# Start connection with bridge
ccb.connect()
# Schedule disconnect on shutdown
def _shutdown(_event):
ccb.disconnect()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
# Load platforms
discovery.load_platform(hass, 'fan', DOMAIN, {}, config)
return True
class ComfoConnectBridge(object):
"""Representation of a ComfoConnect bridge."""
def __init__(self, hass, bridge, name, token, friendly_name, pin):
"""Initialize the ComfoConnect bridge."""
from pycomfoconnect import (ComfoConnect)
self.data = {}
self.name = name
self.hass = hass
self.comfoconnect = ComfoConnect(
bridge=bridge, local_uuid=bytes.fromhex(token),
local_devicename=friendly_name, pin=pin)
self.comfoconnect.callback_sensor = self.sensor_callback
def connect(self):
"""Connect with the bridge."""
_LOGGER.debug("Connecting with bridge")
self.comfoconnect.connect(True)
def disconnect(self):
"""Disconnect from the bridge."""
_LOGGER.debug("Disconnecting from bridge")
self.comfoconnect.disconnect()
def sensor_callback(self, var, value):
"""Callback function for sensor updates."""
_LOGGER.debug("Got value from bridge: %d = %d", var, value)
from pycomfoconnect import (
SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR)
if var in [SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR]:
self.data[var] = value / 10
else:
self.data[var] = value
# Notify listeners that we have received an update
dispatcher_send(self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, var)
def subscribe_sensor(self, sensor_id):
"""Subscribe for the specified sensor."""
self.comfoconnect.register_sensor(sensor_id)

View File

@@ -14,11 +14,13 @@ from homeassistant import core
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
REQUIREMENTS = ['fuzzywuzzy==0.15.0']
ATTR_TEXT = 'text'
ATTR_SENTENCE = 'sentence'
DOMAIN = 'conversation'
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
@@ -29,9 +31,12 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
cv.string: vol.Schema({
vol.Required(ATTR_SENTENCE): cv.string,
vol.Required('action'): cv.SCRIPT_SCHEMA,
})
})}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@@ -40,9 +45,30 @@ def setup(hass, config):
from fuzzywuzzy import process as fuzzyExtract
logger = logging.getLogger(__name__)
config = config.get(DOMAIN, {})
choices = {attrs[ATTR_SENTENCE]: script.Script(
hass,
attrs['action'],
name)
for name, attrs in config.items()}
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)

View File

@@ -27,6 +27,7 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'cover'
DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=15)
GROUP_NAME_ALL_COVERS = 'all covers'
@@ -39,6 +40,8 @@ DEVICE_CLASSES = [
'garage', # Garage door control
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
SUPPORT_OPEN = 1
SUPPORT_CLOSE = 2
SUPPORT_SET_POSITION = 4

View File

@@ -1,5 +1,5 @@
"""
The homematic cover platform.
The HomeMatic cover platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.homematic/
@@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HMCover(HMDevice, CoverDevice):
"""Representation a Homematic Cover."""
"""Representation a HomeMatic Cover."""
@property
def current_cover_position(self):
@@ -70,7 +70,6 @@ class HMCover(HMDevice, CoverDevice):
self._hmdevice.stop(self._channel)
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
# Add state to data dict
"""Generate a data dictoinary (self._data) from metadata."""
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})

View File

@@ -0,0 +1,185 @@
"""
Support for KNX covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.knx/
"""
import logging
import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, ATTR_POSITION, DEVICE_CLASSES_SCHEMA,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, SUPPORT_STOP,
SUPPORT_SET_TILT_POSITION
)
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice)
from homeassistant.const import (CONF_NAME, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_GETPOSITION_ADDRESS = 'getposition_address'
CONF_SETPOSITION_ADDRESS = 'setposition_address'
CONF_GETANGLE_ADDRESS = 'getangle_address'
CONF_SETANGLE_ADDRESS = 'setangle_address'
CONF_STOP = 'stop_address'
CONF_UPDOWN = 'updown_address'
CONF_INVERT_POSITION = 'invert_position'
CONF_INVERT_ANGLE = 'invert_angle'
DEFAULT_NAME = 'KNX Cover'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_UPDOWN): cv.string,
vol.Required(CONF_STOP): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_GETPOSITION_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SETPOSITION_ADDRESS): cv.string,
vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
vol.Inclusive(CONF_GETANGLE_ADDRESS, 'angle'): cv.string,
vol.Inclusive(CONF_SETANGLE_ADDRESS, 'angle'): cv.string,
vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Create and add an entity based on the configuration."""
add_devices([KNXCover(hass, KNXConfig(config))])
class KNXCover(KNXMultiAddressDevice, CoverDevice):
"""Representation of a KNX cover. e.g. a rollershutter."""
def __init__(self, hass, config):
"""Initialize the cover."""
KNXMultiAddressDevice.__init__(
self, hass, config,
['updown', 'stop'], # required
optional=['setposition', 'getposition',
'getangle', 'setangle']
)
self._device_class = config.config.get(CONF_DEVICE_CLASS)
self._invert_position = config.config.get(CONF_INVERT_POSITION)
self._invert_angle = config.config.get(CONF_INVERT_ANGLE)
self._hass = hass
self._current_pos = None
self._target_pos = None
self._current_tilt = None
self._target_tilt = None
self._supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \
SUPPORT_SET_POSITION | SUPPORT_STOP
# Tilt is only supported, if there is a angle get and set address
if CONF_SETANGLE_ADDRESS in config.config:
_LOGGER.debug("%s: Tilt supported at addresses %s, %s",
self.name, config.config.get(CONF_SETANGLE_ADDRESS),
config.config.get(CONF_GETANGLE_ADDRESS))
self._supported_features = self._supported_features | \
SUPPORT_SET_TILT_POSITION
@property
def should_poll(self):
"""Polling is needed for the KNX cover."""
return True
@property
def supported_features(self):
"""Flag supported features."""
return self._supported_features
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._current_pos
@property
def target_position(self):
"""Return the position we are trying to reach: 0 - 100."""
return self._target_pos
@property
def current_cover_tilt_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._current_tilt
@property
def target_tilt(self):
"""Return the tilt angle (in %) we are trying to reach: 0 - 100."""
return self._target_tilt
def set_cover_position(self, **kwargs):
"""Set new target position."""
position = kwargs.get(ATTR_POSITION)
if position is None:
return
if self._invert_position:
position = 100-position
self._target_pos = position
self.set_percentage('setposition', position)
_LOGGER.debug("%s: Set target position to %d", self.name, position)
def update(self):
"""Update device state."""
super().update()
value = self.get_percentage('getposition')
if value is not None:
self._current_pos = value
if self._invert_position:
self._current_pos = 100-value
_LOGGER.debug("%s: position = %d", self.name, value)
if self._supported_features & SUPPORT_SET_TILT_POSITION:
value = self.get_percentage('getangle')
if value is not None:
self._current_tilt = value
if self._invert_angle:
self._current_tilt = 100-value
_LOGGER.debug("%s: tilt = %d", self.name, value)
def open_cover(self, **kwargs):
"""Open the cover."""
_LOGGER.debug("%s: open: updown = 0", self.name)
self.set_int_value('updown', 0)
def close_cover(self, **kwargs):
"""Close the cover."""
_LOGGER.debug("%s: open: updown = 1", self.name)
self.set_int_value('updown', 1)
def stop_cover(self, **kwargs):
"""Stop the cover movement."""
_LOGGER.debug("%s: stop: stop = 1", self.name)
self.set_int_value('stop', 1)
def set_cover_tilt_position(self, tilt_position, **kwargs):
"""Move the cover til to a specific position."""
if self._invert_angle:
tilt_position = 100-tilt_position
self._target_tilt = round(tilt_position, -1)
self.set_percentage('setangle', tilt_position)
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class

View File

@@ -14,7 +14,9 @@ import homeassistant.components.mqtt as mqtt
from homeassistant.components.cover import (
CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT,
SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION)
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION,
ATTR_POSITION)
from homeassistant.exceptions import TemplateError
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
@@ -29,6 +31,8 @@ DEPENDENCIES = ['mqtt']
CONF_TILT_COMMAND_TOPIC = 'tilt_command_topic'
CONF_TILT_STATUS_TOPIC = 'tilt_status_topic'
CONF_POSITION_TOPIC = 'set_position_topic'
CONF_SET_POSITION_TEMPLATE = 'set_position_template'
CONF_PAYLOAD_OPEN = 'payload_open'
CONF_PAYLOAD_CLOSE = 'payload_close'
@@ -55,10 +59,17 @@ DEFAULT_TILT_MAX = 100
DEFAULT_TILT_OPTIMISTIC = False
DEFAULT_TILT_INVERT_STATE = False
OPEN_CLOSE_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP)
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COMMAND_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_POSITION_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_SET_POSITION_TEMPLATE, default=None): cv.template,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
@@ -87,6 +98,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
set_position_template = config.get(CONF_SET_POSITION_TEMPLATE)
if set_position_template is not None:
set_position_template.hass = hass
async_add_devices([MqttCover(
config.get(CONF_NAME),
@@ -109,6 +123,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_TILT_MAX),
config.get(CONF_TILT_STATE_OPTIMISTIC),
config.get(CONF_TILT_INVERT_STATE),
config.get(CONF_POSITION_TOPIC),
set_position_template,
)])
@@ -120,7 +136,7 @@ class MqttCover(CoverDevice):
payload_open, payload_close, payload_stop,
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert):
tilt_invert, position_topic, set_position_template):
"""Initialize the cover."""
self._position = None
self._state = None
@@ -145,6 +161,8 @@ class MqttCover(CoverDevice):
self._tilt_max = tilt_max
self._tilt_optimistic = tilt_optimistic
self._tilt_invert = tilt_invert
self._position_topic = position_topic
self._set_position_template = set_position_template
@asyncio.coroutine
def async_added_to_hass(self):
@@ -233,9 +251,11 @@ class MqttCover(CoverDevice):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
supported_features = 0
if self._command_topic is not None:
supported_features = OPEN_CLOSE_FEATURES
if self.current_cover_position is not None:
if self._position_topic is not None:
supported_features |= SUPPORT_SET_POSITION
if self._tilt_command_topic is not None:
@@ -315,6 +335,22 @@ class MqttCover(CoverDevice):
mqtt.async_publish(self.hass, self._tilt_command_topic,
level, self._qos, self._retain)
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
if self._set_position_template is not None:
try:
position = self._set_position_template.async_render(
**kwargs)
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
mqtt.async_publish(self.hass, self._position_topic,
position, self._qos, self._retain)
def find_percentage_in_range(self, position):
"""Find the 0-100% value within the specified range."""
# the range of motion as defined by the min max values

View File

@@ -14,9 +14,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
REQUIREMENTS = [
'https://github.com/arraylabs/pymyq/archive/v0.0.8.zip'
'#pymyq==0.0.8']
REQUIREMENTS = ['pymyq==0.0.8']
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,354 @@
"""
Support for covers which integrate with other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.template/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.cover import (
ENTITY_ID_FORMAT, CoverDevice, PLATFORM_SCHEMA,
SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT,
SUPPORT_SET_TILT_POSITION, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP,
SUPPORT_SET_POSITION, ATTR_POSITION, ATTR_TILT_POSITION)
from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
EVENT_HOMEASSISTANT_START, MATCH_ALL,
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
STATE_OPEN, STATE_CLOSED)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
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
from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_OPEN, STATE_CLOSED, 'true', 'false']
CONF_COVERS = 'covers'
CONF_POSITION_TEMPLATE = 'position_template'
CONF_TILT_TEMPLATE = 'tilt_template'
OPEN_ACTION = 'open_cover'
CLOSE_ACTION = 'close_cover'
STOP_ACTION = 'stop_cover'
POSITION_ACTION = 'set_cover_position'
TILT_ACTION = 'set_cover_tilt_position'
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
COVER_SCHEMA = vol.Schema({
vol.Required(OPEN_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CLOSE_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(STOP_ACTION): cv.SCRIPT_SCHEMA,
vol.Exclusive(CONF_POSITION_TEMPLATE,
CONF_VALUE_OR_POSITION_TEMPLATE): cv.template,
vol.Exclusive(CONF_VALUE_TEMPLATE,
CONF_VALUE_OR_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
vol.Optional(CONF_ENTITY_ID): cv.entity_ids
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Template cover."""
covers = []
for device, device_config in config[CONF_COVERS].items():
friendly_name = device_config.get(CONF_FRIENDLY_NAME, device)
state_template = device_config.get(CONF_VALUE_TEMPLATE)
position_template = device_config.get(CONF_POSITION_TEMPLATE)
tilt_template = device_config.get(CONF_TILT_TEMPLATE)
icon_template = device_config.get(CONF_ICON_TEMPLATE)
open_action = device_config[OPEN_ACTION]
close_action = device_config[CLOSE_ACTION]
stop_action = device_config[STOP_ACTION]
position_action = device_config.get(POSITION_ACTION)
tilt_action = device_config.get(TILT_ACTION)
if position_template is None and state_template is None:
_LOGGER.error('Must specify either %s' or '%s',
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
continue
template_entity_ids = set()
if state_template is not None:
temp_ids = state_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if position_template is not None:
temp_ids = position_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if tilt_template is not None:
temp_ids = tilt_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if icon_template is not None:
temp_ids = icon_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if not template_entity_ids:
template_entity_ids = MATCH_ALL
entity_ids = device_config.get(CONF_ENTITY_ID, template_entity_ids)
covers.append(
CoverTemplate(
hass,
device, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids
)
)
if not covers:
_LOGGER.error("No covers added")
return False
async_add_devices(covers, True)
return True
class CoverTemplate(CoverDevice):
"""Representation of a Template cover."""
def __init__(self, hass, device_id, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids):
"""Initialize the Template cover."""
self.hass = hass
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass)
self._name = friendly_name
self._template = state_template
self._position_template = position_template
self._tilt_template = tilt_template
self._icon_template = icon_template
self._open_script = Script(hass, open_action)
self._close_script = Script(hass, close_action)
self._stop_script = Script(hass, stop_action)
self._position_script = None
if position_action is not None:
self._position_script = Script(hass, position_action)
self._tilt_script = None
if tilt_action is not None:
self._tilt_script = Script(hass, tilt_action)
self._icon = None
self._position = None
self._tilt_value = None
self._entities = entity_ids
if self._template is not None:
self._template.hass = self.hass
if self._position_template is not None:
self._position_template.hass = self.hass
if self._tilt_template is not None:
self._tilt_template.hass = self.hass
if self._icon_template is not None:
self._icon_template.hass = self.hass
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._position = 100 if state.state == STATE_OPEN else 0
@callback
def template_cover_state_listener(entity, old_state, new_state):
"""Handle target device state changes."""
self.hass.async_add_job(self.async_update_ha_state(True))
@callback
def template_cover_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_cover_state_listener)
self.hass.async_add_job(self.async_update_ha_state(True))
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_cover_startup)
@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._position == 0
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._position
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._tilt_value
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return self._icon
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
if self.current_cover_position is not None:
supported_features |= SUPPORT_SET_POSITION
if self.current_cover_tilt_position is not None:
supported_features |= TILT_FEATURES
return supported_features
@property
def should_poll(self):
"""Return the polling state."""
return False
@asyncio.coroutine
def async_open_cover(self, **kwargs):
"""Move the cover up."""
self.hass.async_add_job(self._open_script.async_run())
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down."""
self.hass.async_add_job(self._close_script.async_run())
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
self.hass.async_add_job(self._stop_script.async_run())
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Set cover position."""
if ATTR_POSITION not in kwargs:
return
self._position = kwargs[ATTR_POSITION]
self.hass.async_add_job(self._position_script.async_run(
{"position": self._position}))
@asyncio.coroutine
def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
self._tilt_value = 100
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed."""
self._tilt_value = 0
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION not in kwargs:
return
self._tilt_value = kwargs[ATTR_TILT_POSITION]
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
if self._template is not None:
try:
state = self._template.async_render().lower()
if state in _VALID_STATES:
if state in ('true', STATE_OPEN):
self._position = 100
else:
self._position = 0
else:
_LOGGER.error(
'Received invalid cover is_on state: %s. Expected: %s',
state, ', '.join(_VALID_STATES))
self._position = None
except TemplateError as ex:
_LOGGER.error(ex)
self._position = None
if self._position_template is not None:
try:
state = float(self._position_template.async_render())
if state < 0 or state > 100:
self._position = None
_LOGGER.error("Cover position value must be"
" between 0 and 100."
" Value was: %.2f", state)
else:
self._position = state
except TemplateError as ex:
_LOGGER.error(ex)
self._position = None
except ValueError as ex:
_LOGGER.error(ex)
self._position = None
if self._tilt_template is not None:
try:
state = float(self._tilt_template.async_render())
if state < 0 or state > 100:
self._tilt_value = None
_LOGGER.error("Tilt value must be between 0 and 100."
" Value was: %.2f", state)
else:
self._tilt_value = state
except TemplateError as ex:
_LOGGER.error(ex)
self._tilt_value = None
except ValueError as ex:
_LOGGER.error(ex)
self._tilt_value = None
if self._icon_template is not None:
try:
self._icon = self._icon_template.async_render()
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning('Could not render icon template %s,'
' the state is unknown.', self._name)
return
self._icon = super().icon
_LOGGER.error('Could not render icon template %s: %s',
self._name, ex)

View File

@@ -210,6 +210,7 @@ def async_setup(hass, config):
description=("Press the button on the bridge to register Philips "
"Hue with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
fields=[{'id': 'username', 'name': 'Username'}],
submit_caption="I have pressed the button"
)
configurator_ids.append(request_id)

View File

@@ -27,6 +27,7 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.util.async import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
@@ -41,7 +42,7 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'device_tracker'
DEPENDENCIES = ['zone']
DEPENDENCIES = ['zone', 'group']
GROUP_NAME_ALL_DEVICES = 'all devices'
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
@@ -122,15 +123,10 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the device tracker."""
yaml_path = hass.config.path(YAML_DEVICES)
try:
conf = config.get(DOMAIN, [])
except vol.Invalid as ex:
async_log_exception(ex, DOMAIN, config, hass)
return False
else:
conf = conf[0] if conf else {}
consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
conf = config.get(DOMAIN, [])
conf = conf[0] if conf else {}
consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
devices = yield from async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(hass, consider_home, track_new, devices)
@@ -180,7 +176,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
if setup_tasks:
yield from asyncio.wait(setup_tasks, loop=hass.loop)
yield from tracker.async_setup_group()
tracker.async_setup_group()
@callback
def async_device_tracker_discovered(service, info):
@@ -233,7 +229,7 @@ class DeviceTracker(object):
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
self.consider_home = consider_home
self.track_new = track_new
self.group = None # type: group.Group
self.group = None
self._is_updating = asyncio.Lock(loop=hass.loop)
for dev in devices:
@@ -246,18 +242,21 @@ class DeviceTracker(object):
def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None,
battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
source_type: str=SOURCE_TYPE_GPS, picture: str=None,
icon: str=None):
"""Notify the device tracker that you see a device."""
self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps,
gps_accuracy, battery, attributes, source_type)
gps_accuracy, battery, attributes, source_type,
picture, icon)
)
@asyncio.coroutine
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None,
gps_accuracy=None, battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
source_type: str=SOURCE_TYPE_GPS, picture: str=None,
icon: str=None):
"""Notify the device tracker that you see a device.
This method is a coroutine.
@@ -285,7 +284,8 @@ class DeviceTracker(object):
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
device = Device(
self.hass, self.consider_home, self.track_new,
dev_id, mac, (host_name or dev_id).replace('_', ' '))
dev_id, mac, (host_name or dev_id).replace('_', ' '),
picture=picture, icon=icon)
self.devices[dev_id] = device
if mac is not None:
self.mac_to_dev[mac] = device
@@ -303,9 +303,10 @@ class DeviceTracker(object):
})
# During init, we ignore the group
if self.group is not None:
yield from self.group.async_update_tracked_entity_ids(
list(self.group.tracking) + [device.entity_id])
if self.group and self.track_new:
self.group.async_set_group(
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id])
# lookup mac vendor string to be stored in config
yield from device.set_vendor_for_mac()
@@ -327,16 +328,19 @@ class DeviceTracker(object):
update_config, self.hass.config.path(YAML_DEVICES),
dev_id, device)
@asyncio.coroutine
@callback
def async_setup_group(self):
"""Initialize group for all tracked devices.
This method is a coroutine.
This method must be run in the event loop.
"""
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = yield from group.Group.async_create_group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
entity_ids = [dev.entity_id for dev in self.devices.values()
if dev.track]
self.group = get_component('group')
self.group.async_set_group(
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
name=GROUP_NAME_ALL_DEVICES, entity_ids=entity_ids)
@callback
def async_update_stale(self, now: dt_util.dt.datetime):

View File

@@ -0,0 +1,110 @@
"""Support for Linksys Smart Wifi routers."""
import logging
import threading
from datetime import timedelta
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
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_TIMEOUT = 10
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
})
def get_scanner(hass, config):
"""Validate the configuration and return a Linksys AP scanner."""
try:
return LinksysSmartWifiDeviceScanner(config[DOMAIN])
except ConnectionError:
return None
class LinksysSmartWifiDeviceScanner(DeviceScanner):
"""This class queries a Linksys Access Point."""
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
response = self._make_request()
if not response.status_code == 200:
raise ConnectionError("Cannot connect to Linksys Access Point")
def scan_devices(self):
"""Scan for new devices and return a list with device IDs (MACs)."""
self._update_info()
return self.last_results.keys()
def get_device_name(self, mac):
"""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")
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 = device["friendlyName"]
properties = device["properties"]
for prop in properties:
if prop["name"] == "userDeviceName":
name = prop["value"]
_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
data = [{
"request": {
"sinceRevision": 0
},
"action": "http://linksys.com/jnap/devicelist/GetDevices"
}]
headers = {"X-JNAP-Action": "http://linksys.com/jnap/core/Transaction"}
return requests.post('http://{}/JNAP/'.format(self.host),
timeout=DEFAULT_TIMEOUT,
headers=headers,
json=data)

View File

@@ -158,6 +158,11 @@ class MikrotikScanner(DeviceScanner):
for device in devices
}
else:
self.last_results = mac_names
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

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.0']
REQUIREMENTS = ['libnacl==1.5.1']
_LOGGER = logging.getLogger(__name__)
@@ -116,7 +116,6 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
"key for topic %s", topic)
return None
# pylint: disable=too-many-return-statements
def validate_payload(topic, payload, data_type):
"""Validate the OwnTracks payload."""
try:

View File

@@ -57,7 +57,7 @@ class Host(object):
def update(self, see):
"""Update device state by sending one or more ping messages."""
failed = 0
while failed < self._count: # check more times if host in unreachable
while failed < self._count: # check more times if host is unreachable
if self.ping():
see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
return True

View File

@@ -19,7 +19,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.7']
REQUIREMENTS = ['pysnmp==4.3.8']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'

32
homeassistant/components/device_tracker/ubus.py Executable file → Normal file
View File

@@ -18,6 +18,7 @@ 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)
@@ -38,6 +39,23 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
def _refresh_on_acccess_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again."""
def decorator(self, *args, **kwargs):
"""Wrapper function to refresh session_id on PermissionError."""
try:
return func(self, *args, **kwargs)
except PermissionError:
_LOGGER.warning("Invalid session detected." +
" Tryign to refresh session_id and re-run the rpc")
self.session_id = _get_session_id(self.url, self.username,
self.password)
return func(self, *args, **kwargs)
return decorator
class UbusDeviceScanner(DeviceScanner):
"""
This class queries a wireless router running OpenWrt firmware.
@@ -48,14 +66,16 @@ class UbusDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.username = config[CONF_USERNAME]
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)
self.session_id = _get_session_id(self.url, username, password)
self.session_id = _get_session_id(self.url, self.username,
self.password)
self.hostapd = []
self.leasefile = None
self.mac2name = None
@@ -66,6 +86,7 @@ class UbusDeviceScanner(DeviceScanner):
self._update_info()
return self.last_results
@_refresh_on_acccess_denied
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
@@ -95,6 +116,7 @@ class UbusDeviceScanner(DeviceScanner):
return self.mac2name.get(device.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.
@@ -142,6 +164,12 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
if res.status_code == 200:
response = res.json()
if 'error' in response:
if 'message' in response['error'] and \
response['error']['message'] == "Access denied":
raise PermissionError(response['error']['message'])
else:
raise HomeAssistantError(response['error']['message'])
if rpcmethod == "call":
try:

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.unifi/
"""
import logging
import urllib
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@@ -15,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL
REQUIREMENTS = ['pyunifi==2.12']
REQUIREMENTS = ['pyunifi==2.13']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
@@ -40,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_scanner(hass, config):
"""Set up the Unifi device_tracker."""
from pyunifi.controller import Controller
from pyunifi.controller import Controller, APIError
host = config[DOMAIN].get(CONF_HOST)
username = config[DOMAIN].get(CONF_USERNAME)
@@ -53,7 +52,7 @@ def get_scanner(hass, config):
try:
ctrl = Controller(host, username, password, port, version='v4',
site_id=site_id, ssl_verify=verify_ssl)
except urllib.error.HTTPError as ex:
except APIError as ex:
_LOGGER.error("Failed to connect to Unifi: %s", ex)
persistent_notification.create(
hass, 'Failed to connect to Unifi. '
@@ -77,9 +76,10 @@ class UnifiScanner(DeviceScanner):
def _update(self):
"""Get the clients from the device."""
from pyunifi.controller import APIError
try:
clients = self._controller.get_clients()
except urllib.error.HTTPError as ex:
except APIError as ex:
_LOGGER.error("Failed to scan clients: %s", ex)
clients = []

View File

@@ -7,7 +7,10 @@ https://home-assistant.io/components/device_tracker.volvooncall/
import logging
from homeassistant.util import slugify
from homeassistant.components.volvooncall import DOMAIN
from homeassistant.helpers.dispatcher import (
dispatcher_connect, dispatcher_send)
from homeassistant.components.volvooncall import (
DATA_KEY, SIGNAL_VEHICLE_SEEN)
_LOGGER = logging.getLogger(__name__)
@@ -18,19 +21,19 @@ def setup_scanner(hass, config, see, discovery_info=None):
return
vin, _ = discovery_info
vehicle = hass.data[DOMAIN].vehicles[vin]
host_name = vehicle.registration_number
dev_id = 'volvo_' + slugify(host_name)
vehicle = hass.data[DATA_KEY].vehicles[vin]
def see_vehicle(vehicle):
"""Handle the reporting of the vehicle position."""
host_name = vehicle.registration_number
dev_id = 'volvo_{}'.format(slugify(host_name))
see(dev_id=dev_id,
host_name=host_name,
gps=(vehicle.position['latitude'],
vehicle.position['longitude']))
vehicle.position['longitude']),
icon='mdi:car')
hass.data[DOMAIN].entities[vin].append(see_vehicle)
see_vehicle(vehicle)
dispatcher_connect(hass, SIGNAL_VEHICLE_SEEN, see_vehicle)
dispatcher_send(hass, SIGNAL_VEHICLE_SEEN, vehicle)
return True

View File

@@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.11']
REQUIREMENTS = ['python-digitalocean==1.12']
_LOGGER = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ ATTR_VCPUS = 'vcpus'
CONF_DROPLETS = 'droplets'
DIGITAL_OCEAN = None
DATA_DIGITAL_OCEAN = 'data_do'
DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor']
DOMAIN = 'digital_ocean'
@@ -47,13 +47,14 @@ def setup(hass, config):
conf = config[DOMAIN]
access_token = conf.get(CONF_ACCESS_TOKEN)
global DIGITAL_OCEAN
DIGITAL_OCEAN = DigitalOcean(access_token)
digital = DigitalOcean(access_token)
if not DIGITAL_OCEAN.manager.get_account():
if not digital.manager.get_account():
_LOGGER.error("No Digital Ocean account found for the given API Token")
return False
hass.data[DATA_DIGITAL_OCEAN] = digital
return True

View File

@@ -55,6 +55,7 @@ SERVICE_HANDLERS = {
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'harmony': ('remote', 'harmony'),
'bose_soundtouch': ('media_player', 'soundtouch'),
}

View File

@@ -0,0 +1,98 @@
"""Parent component for Dyson Pure Cool Link devices."""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
CONF_DEVICES
REQUIREMENTS = ['libpurecoollink==0.1.5']
_LOGGER = logging.getLogger(__name__)
CONF_LANGUAGE = "language"
CONF_RETRY = "retry"
DEFAULT_TIMEOUT = 5
DEFAULT_RETRY = 10
DOMAIN = "dyson"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_LANGUAGE): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int,
vol.Optional(CONF_DEVICES, default=[]):
vol.All(cv.ensure_list, [dict]),
})
}, extra=vol.ALLOW_EXTRA)
DYSON_DEVICES = "dyson_devices"
def setup(hass, config):
"""Set up the Dyson parent component."""
_LOGGER.info("Creating new Dyson component")
if DYSON_DEVICES not in hass.data:
hass.data[DYSON_DEVICES] = []
from libpurecoollink.dyson import DysonAccount
dyson_account = DysonAccount(config[DOMAIN].get(CONF_USERNAME),
config[DOMAIN].get(CONF_PASSWORD),
config[DOMAIN].get(CONF_LANGUAGE))
logged = dyson_account.login()
timeout = config[DOMAIN].get(CONF_TIMEOUT)
retry = config[DOMAIN].get(CONF_RETRY)
if not logged:
_LOGGER.error("Not connected to Dyson account. Unable to add devices")
return False
_LOGGER.info("Connected to Dyson account")
dyson_devices = dyson_account.devices()
if CONF_DEVICES in config[DOMAIN] and config[DOMAIN].get(CONF_DEVICES):
configured_devices = config[DOMAIN].get(CONF_DEVICES)
for device in configured_devices:
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)
else:
_LOGGER.warning(
"Unable to find device %s in Dyson account",
device["device_id"])
else:
# Not yet reliable
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)
if connected:
_LOGGER.info("Connected to device %s", device)
hass.data[DYSON_DEVICES].append(device)
else:
_LOGGER.warning("Unable to connect to device %s", device)
# Start fan/sensors components
if hass.data[DYSON_DEVICES]:
_LOGGER.debug("Starting sensor/fan components")
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
discovery.load_platform(hass, "fan", DOMAIN, {}, config)
return True

View File

@@ -24,7 +24,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
REQUIREMENTS = ['pyeight==0.0.6']
REQUIREMENTS = ['pyeight==0.0.7']
_LOGGER = logging.getLogger(__name__)

View File

@@ -25,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'fan'
DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
GROUP_NAME_ALL_FANS = 'all fans'
@@ -73,7 +73,7 @@ FAN_TURN_ON_SCHEMA = vol.Schema({
}) # type: dict
FAN_TURN_OFF_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
}) # type: dict
FAN_OSCILLATE_SCHEMA = vol.Schema({
@@ -139,9 +139,7 @@ def turn_on(hass, entity_id: str=None, speed: str=None) -> None:
def turn_off(hass, entity_id: str=None) -> None:
"""Turn all or specified fan off."""
data = {
ATTR_ENTITY_ID: entity_id,
}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
@@ -218,8 +216,7 @@ def async_setup(hass, config: dict):
if not fan.should_poll:
continue
update_coro = hass.async_add_job(
fan.async_update_ha_state(True))
update_coro = hass.async_add_job(fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:

View File

@@ -0,0 +1,118 @@
"""
Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.comfoconnect/
"""
import logging
from homeassistant.components.comfoconnect import (
DOMAIN, ComfoConnectBridge, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED)
from homeassistant.components.fan import (
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.dispatcher import (dispatcher_connect)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['comfoconnect']
SPEED_MAPPING = {
0: SPEED_OFF,
1: SPEED_LOW,
2: SPEED_MEDIUM,
3: SPEED_HIGH
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the ComfoConnect fan platform."""
ccb = hass.data[DOMAIN]
add_devices([ComfoConnectFan(hass, name=ccb.name, ccb=ccb)], True)
return
class ComfoConnectFan(FanEntity):
"""Representation of the ComfoConnect fan platform."""
def __init__(self, hass, name, ccb: ComfoConnectBridge):
"""Initialize the ComfoConnect fan."""
from pycomfoconnect import SENSOR_FAN_SPEED_MODE
self._ccb = ccb
self._name = name
# Ask the bridge to keep us updated
self._ccb.comfoconnect.register_sensor(SENSOR_FAN_SPEED_MODE)
def _handle_update(var):
if var == SENSOR_FAN_SPEED_MODE:
_LOGGER.debug("Dispatcher update for %s", var)
self.schedule_update_ha_state()
# Register for dispatcher updates
dispatcher_connect(
hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update)
@property
def name(self):
"""Return the name of the fan."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend."""
return 'mdi:air-conditioner'
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_SET_SPEED
@property
def speed(self):
"""Return the current fan mode."""
from pycomfoconnect import (SENSOR_FAN_SPEED_MODE)
try:
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
return SPEED_MAPPING[speed]
except KeyError:
return STATE_UNKNOWN
@property
def speed_list(self):
"""List of available fan modes."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
if speed is None:
speed = SPEED_LOW
self.set_speed(speed)
def turn_off(self) -> None:
"""Turn off the fan (to away)."""
self.set_speed(SPEED_OFF)
def set_speed(self, mode):
"""Set fan speed."""
_LOGGER.debug('Changing fan mode to %s.', mode)
from pycomfoconnect import (
CMD_FAN_MODE_AWAY, CMD_FAN_MODE_LOW, CMD_FAN_MODE_MEDIUM,
CMD_FAN_MODE_HIGH)
if mode == SPEED_OFF:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_AWAY)
elif mode == SPEED_LOW:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_LOW)
elif mode == SPEED_MEDIUM:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_MEDIUM)
elif mode == SPEED_HIGH:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_HIGH)
# Update current mode
self.schedule_update_ha_state()

View File

@@ -9,31 +9,36 @@ from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_OSCILLATE, SUPPORT_DIRECTION)
from homeassistant.const import STATE_OFF
FAN_NAME = 'Living Room Fan'
FAN_ENTITY_ID = 'fan.living_room_fan'
DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
LIMITED_SUPPORT = SUPPORT_SET_SPEED
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up the demo fan platform."""
add_devices_callback([
DemoFan(hass, FAN_NAME, STATE_OFF),
DemoFan(hass, "Living Room Fan", FULL_SUPPORT),
DemoFan(hass, "Ceiling Fan", LIMITED_SUPPORT),
])
class DemoFan(FanEntity):
"""A demonstration fan component."""
def __init__(self, hass, name: str, initial_state: str) -> None:
def __init__(self, hass, name: str, supported_features: int) -> None:
"""Initialize the entity."""
self.hass = hass
self._speed = initial_state
self.oscillating = False
self.direction = "forward"
self._supported_features = supported_features
self._speed = STATE_OFF
self.oscillating = None
self.direction = None
self._name = name
if supported_features & SUPPORT_OSCILLATE:
self.oscillating = False
if supported_features & SUPPORT_DIRECTION:
self.direction = "forward"
@property
def name(self) -> str:
"""Get entity name."""
@@ -88,4 +93,4 @@ class DemoFan(FanEntity):
@property
def supported_features(self) -> int:
"""Flag supported features."""
return DEMO_SUPPORT
return self._supported_features

View File

@@ -0,0 +1,218 @@
"""Support for Dyson Pure Cool link fan."""
import logging
import asyncio
from os import path
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (FanEntity, SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
DOMAIN)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.dyson import DYSON_DEVICES
from homeassistant.config import load_yaml_config_file
DEPENDENCIES = ['dyson']
_LOGGER = logging.getLogger(__name__)
DYSON_FAN_DEVICES = "dyson_fan_devices"
SERVICE_SET_NIGHT_MODE = 'dyson_set_night_mode'
DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({
vol.Required('entity_id'): cv.entity_id,
vol.Required('night_mode'): cv.boolean
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Dyson fan components."""
_LOGGER.info("Creating new Dyson fans")
if DYSON_FAN_DEVICES not in hass.data:
hass.data[DYSON_FAN_DEVICES] = []
# Get Dyson Devices from parent component
for device in hass.data[DYSON_DEVICES]:
dyson_entity = DysonPureCoolLinkDevice(hass, device)
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
add_devices(hass.data[DYSON_FAN_DEVICES])
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
def service_handle(service):
"""Handle dyson services."""
entity_id = service.data.get('entity_id')
night_mode = service.data.get('night_mode')
fan_device = next([fan for fan in hass.data[DYSON_FAN_DEVICES] if
fan.entity_id == entity_id].__iter__(), None)
if fan_device is None:
_LOGGER.warning("Unable to find Dyson fan device %s",
str(entity_id))
return
if service.service == SERVICE_SET_NIGHT_MODE:
fan_device.night_mode(night_mode)
# Register dyson service(s)
hass.services.register(DOMAIN, SERVICE_SET_NIGHT_MODE,
service_handle,
descriptions.get(SERVICE_SET_NIGHT_MODE),
schema=DYSON_SET_NIGHT_MODE_SCHEMA)
class DysonPureCoolLinkDevice(FanEntity):
"""Representation of a Dyson fan."""
def __init__(self, hass, device):
"""Initialize the fan."""
_LOGGER.info("Creating device %s", device.name)
self.hass = hass
self._device = device
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Called when new messages received from the fan."""
_LOGGER.debug(
"Message received for fan device %s : %s", self.name, message)
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 fan."""
return self._device.name
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan. Never called ??."""
_LOGGER.debug("Set fan speed to: " + speed)
from libpurecoollink.const import FanSpeed, FanMode
if speed == FanSpeed.FAN_SPEED_AUTO.value:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
self._device.set_configuration(fan_mode=FanMode.FAN,
fan_speed=fan_speed)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
from libpurecoollink.const import FanSpeed, FanMode
if speed:
if speed == FanSpeed.FAN_SPEED_AUTO.value:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
self._device.set_configuration(fan_mode=FanMode.FAN,
fan_speed=fan_speed)
else:
# Speed not set, just turn on
self._device.set_configuration(fan_mode=FanMode.FAN)
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn off the fan."""
_LOGGER.debug("Turn off fan %s", self.name)
from libpurecoollink.const import FanMode
self._device.set_configuration(fan_mode=FanMode.OFF)
def oscillate(self: ToggleEntity, oscillating: bool) -> None:
"""Turn on/off oscillating."""
_LOGGER.debug("Turn oscillation %s for device %s", oscillating,
self.name)
from libpurecoollink.const import Oscillation
if oscillating:
self._device.set_configuration(
oscillation=Oscillation.OSCILLATION_ON)
else:
self._device.set_configuration(
oscillation=Oscillation.OSCILLATION_OFF)
@property
def oscillating(self):
"""Return the oscillation state."""
return self._device.state and self._device.state.oscillation == "ON"
@property
def is_on(self):
"""Return true if the entity is on."""
if self._device.state:
return self._device.state.fan_state == "FAN"
return False
@property
def speed(self) -> str:
"""Return the current speed."""
if self._device.state:
from libpurecoollink.const import FanSpeed
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
return self._device.state.speed
else:
return int(self._device.state.speed)
return None
@property
def current_direction(self):
"""Return direction of the fan [forward, reverse]."""
return None
@property
def is_night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
def night_mode(self: ToggleEntity, night_mode: bool) -> None:
"""Turn fan in night mode."""
_LOGGER.debug("Set %s night mode %s", self.name, night_mode)
from libpurecoollink.const import NightMode
if night_mode:
self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_ON)
else:
self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_OFF)
@property
def is_auto_mode(self):
"""Return auto mode."""
return self._device.state.fan_mode == "AUTO"
def auto_mode(self: ToggleEntity, auto_mode: bool) -> None:
"""Turn fan in auto mode."""
_LOGGER.debug("Set %s auto mode %s", self.name, auto_mode)
from libpurecoollink.const import FanMode
if auto_mode:
self._device.set_configuration(fan_mode=FanMode.AUTO)
else:
self._device.set_configuration(fan_mode=FanMode.FAN)
@property
def speed_list(self: ToggleEntity) -> list:
"""Get the list of available speeds."""
from libpurecoollink.const import FanSpeed
supported_speeds = [FanSpeed.FAN_SPEED_AUTO.value,
int(FanSpeed.FAN_SPEED_1.value),
int(FanSpeed.FAN_SPEED_2.value),
int(FanSpeed.FAN_SPEED_3.value),
int(FanSpeed.FAN_SPEED_4.value),
int(FanSpeed.FAN_SPEED_5.value),
int(FanSpeed.FAN_SPEED_6.value),
int(FanSpeed.FAN_SPEED_7.value),
int(FanSpeed.FAN_SPEED_8.value),
int(FanSpeed.FAN_SPEED_9.value),
int(FanSpeed.FAN_SPEED_10.value)]
return supported_speeds
@property
def supported_features(self: ToggleEntity) -> int:
"""Flag supported features."""
return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED

View File

@@ -0,0 +1,195 @@
"""
Support for Insteon fans via local hub control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan.insteon_local/
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.fan import (
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['insteon_local']
DOMAIN = 'fan'
INSTEON_LOCAL_FANS_CONF = 'insteon_local_fans.conf'
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
SUPPORT_INSTEON_LOCAL = SUPPORT_SET_SPEED
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Insteon local fan platform."""
insteonhub = hass.data['insteon_local']
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if len(conf_fans):
for device_id in conf_fans:
setup_fan(device_id, conf_fans[device_id], insteonhub, hass,
add_devices)
else:
linked = insteonhub.get_linked()
for device_id in linked:
if (linked[device_id]['cat_type'] == 'dimmer' and
linked[device_id]['sku'] == '2475F' and
device_id not in conf_fans):
request_configuration(device_id,
insteonhub,
linked[device_id]['model_name'] + ' ' +
linked[device_id]['sku'],
hass, add_devices)
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[device_id], 'Failed to register, please try again.')
return
def insteon_fan_config_callback(data):
"""The actions to do when our configuration callback is called."""
setup_fan(device_id, data.get('name'), insteonhub, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
insteon_fan_config_callback,
description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
submit_caption='Confirm',
fields=[{'id': 'name', 'name': 'Name', 'type': ''}]
)
def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the fan."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator.request_done(request_id)
_LOGGER.info("Device configuration done!")
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if device_id not in conf_fans:
conf_fans[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_FANS_CONF),
conf_fans):
_LOGGER.error("Failed to save configuration file")
device = insteonhub.fan(device_id)
add_devices_callback([InsteonLocalFanDevice(device, name)])
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config))
except IOError as error:
_LOGGER.error('Saving config file failed: %s', error)
return False
return True
else:
# We're reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except IOError as error:
_LOGGER.error("Reading configuration file failed: %s", error)
# This won't work yet
return False
else:
return {}
class InsteonLocalFanDevice(FanEntity):
"""An abstract Class for an Insteon node."""
def __init__(self, node, name):
"""Initialize the device."""
self.node = node
self.node.deviceName = name
self._speed = SPEED_OFF
@property
def name(self):
"""Return the the name of the node."""
return self.node.deviceName
@property
def unique_id(self):
"""Return the ID of this Insteon node."""
return 'insteon_local_{}_fan'.format(self.node.device_id)
@property
def speed(self) -> str:
"""Return the current speed."""
return self._speed
@property
def speed_list(self: ToggleEntity) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Update state of the fan."""
resp = self.node.status()
if 'cmd2' in resp:
if resp['cmd2'] == '00':
self._speed = SPEED_OFF
elif resp['cmd2'] == '55':
self._speed = SPEED_LOW
elif resp['cmd2'] == 'AA':
self._speed = SPEED_MEDIUM
elif resp['cmd2'] == 'FF':
self._speed = SPEED_HIGH
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_INSTEON_LOCAL
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn device on."""
if speed is None:
if ATTR_SPEED in kwargs:
speed = kwargs[ATTR_SPEED]
else:
speed = SPEED_MEDIUM
self.set_speed(speed)
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn device off."""
self.node.off()
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
if self.node.on(speed):
self._speed = speed

View File

@@ -58,7 +58,18 @@ set_direction:
fields:
entity_id:
description: Name(s) of the entities to toggle
exampl: 'fan.living_room'
example: 'fan.living_room'
direction:
description: The direction to rotate
example: 'left'
dyson_set_night_mode:
description: Set the fan in night mode
fields:
entity_id:
description: Name(s) of the entities to enable/disable night mode
example: 'fan.living_room'
night_mode:
description: Night mode status
example: true

View File

@@ -36,7 +36,7 @@ SPEED_TO_VALUE = {
def get_device(values, **kwargs):
"""Create zwave entity device."""
"""Create Z-Wave entity device."""
return ZwaveFan(values)

View File

@@ -3,21 +3,21 @@
FINGERPRINTS = {
"compatibility.js": "8e4c44b5f4288cc48ec1ba94a9bec812",
"core.js": "d4a7cb8c80c62b536764e0e81385f6aa",
"frontend.html": "ed18c05632c071eb4f7b012382d0f810",
"mdi.html": "f407a5a57addbe93817ee1b244d33fbe",
"frontend.html": "f170a7221615ca2839cb8fd51a82f50a",
"mdi.html": "c92bd28c434865d6cabb34cd3c0a3e4c",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-automation.html": "21cba0a4fee9d2b45dda47f7a1dd82d8",
"panels/ha-panel-config.html": "59d9eb28758b497a4d9b2428f978b9b1",
"panels/ha-panel-dev-event.html": "2db9c218065ef0f61d8d08db8093cad2",
"panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d",
"panels/ha-panel-dev-service.html": "415552027cb083badeff5f16080410ed",
"panels/ha-panel-dev-state.html": "d70314913b8923d750932367b1099750",
"panels/ha-panel-dev-template.html": "567fbf86735e1b891e40c2f4060fec9b",
"panels/ha-panel-automation.html": "4f98839bb082885657bbcd0ac04fc680",
"panels/ha-panel-config.html": "76853de505d173e82249bf605eb73505",
"panels/ha-panel-dev-event.html": "4886c821235492b1b92739b580d21c61",
"panels/ha-panel-dev-info.html": "24e888ec7a8acd0c395b34396e9001bc",
"panels/ha-panel-dev-service.html": "92c6be30b1af95791d5a6429df505852",
"panels/ha-panel-dev-state.html": "8f1a27c04db6329d31cfcc7d0d6a0869",
"panels/ha-panel-dev-template.html": "d33a55b937b50cdfe8b6fae81f70a139",
"panels/ha-panel-hassio.html": "9474ba65077371622f21ed9a30cf5229",
"panels/ha-panel-history.html": "89062c48c76206cad1cec14ddbb1cbb1",
"panels/ha-panel-history.html": "35177e2046c9a4191c8f51f8160255ce",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "6dd6a16f52117318b202e60f98400163",
"panels/ha-panel-map.html": "31c592c239636f91e07c7ac232a5ebc4",
"panels/ha-panel-zwave.html": "780a792213e98510b475f752c40ef0f9",
"panels/ha-panel-logbook.html": "7c45bd41c146ec38b9938b8a5188bb0d",
"panels/ha-panel-map.html": "0ba605729197c4724ecc7310b08f7050",
"panels/ha-panel-zwave.html": "2ea2223339d1d2faff478751c2927d11",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:16px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hass.config.core.version]]</p><p>Path to configuration.yaml: [[hass.config.core.config_dir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the Apache 2.0 license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},polymerVersion:{type:String,value:Polymer.version},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(e){e&&e.preventDefault(),this.errorLog="Loading error log…",this.hass.callApi("GET","error_log").then(function(e){this.errorLog=e||"No errors have been reported."}.bind(this))}})</script></body></html>
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:16px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header slot="header" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hass.config.core.version]]</p><p>Path to configuration.yaml: [[hass.config.core.config_dir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the Apache 2.0 license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},polymerVersion:{type:String,value:Polymer.version},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(e){e&&e.preventDefault(),this.errorLog="Loading error log…",this.hass.callApi("GET","error_log").then(function(e){this.errorLog=e||"No errors have been reported."}.bind(this))}})</script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,8 @@ from homeassistant import config as conf_util, core as ha
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED,
STATE_UNLOCKED, STATE_UNKNOWN, ATTR_ASSUMED_STATE, SERVICE_RELOAD)
STATE_UNLOCKED, STATE_OK, STATE_PROBLEM, STATE_UNKNOWN,
ATTR_ASSUMED_STATE, SERVICE_RELOAD)
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
@@ -30,13 +31,23 @@ CONF_ENTITIES = 'entities'
CONF_VIEW = 'view'
CONF_CONTROL = 'control'
ATTR_ADD_ENTITIES = 'add_entities'
ATTR_AUTO = 'auto'
ATTR_CONTROL = 'control'
ATTR_ENTITIES = 'entities'
ATTR_ICON = 'icon'
ATTR_NAME = 'name'
ATTR_OBJECT_ID = 'object_id'
ATTR_ORDER = 'order'
ATTR_VIEW = 'view'
ATTR_VISIBLE = 'visible'
ATTR_CONTROL = 'control'
SERVICE_SET_VISIBILITY = 'set_visibility'
SERVICE_SET = 'set'
SERVICE_REMOVE = 'remove'
CONTROL_TYPES = vol.In(['hidden', None])
SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_VISIBLE): cv.boolean
@@ -44,6 +55,21 @@ SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({
RELOAD_SERVICE_SCHEMA = vol.Schema({})
SET_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_OBJECT_ID): cv.slug,
vol.Optional(ATTR_NAME): cv.string,
vol.Optional(ATTR_VIEW): cv.boolean,
vol.Optional(ATTR_ICON): cv.string,
vol.Optional(ATTR_CONTROL): CONTROL_TYPES,
vol.Optional(ATTR_VISIBLE): cv.boolean,
vol.Exclusive(ATTR_ENTITIES, 'entities'): cv.entity_ids,
vol.Exclusive(ATTR_ADD_ENTITIES, 'entities'): cv.entity_ids,
})
REMOVE_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_OBJECT_ID): cv.slug,
})
_LOGGER = logging.getLogger(__name__)
@@ -60,7 +86,7 @@ GROUP_SCHEMA = vol.Schema({
CONF_VIEW: cv.boolean,
CONF_NAME: cv.string,
CONF_ICON: cv.icon,
CONF_CONTROL: cv.string,
CONF_CONTROL: CONTROL_TYPES,
})
CONFIG_SCHEMA = vol.Schema({
@@ -69,7 +95,8 @@ CONFIG_SCHEMA = vol.Schema({
# List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
(STATE_OPEN, STATE_CLOSED), (STATE_LOCKED, STATE_UNLOCKED)]
(STATE_OPEN, STATE_CLOSED), (STATE_LOCKED, STATE_UNLOCKED),
(STATE_PROBLEM, STATE_OK)]
def _get_group_on_off(state):
@@ -99,10 +126,10 @@ def reload(hass):
hass.add_job(async_reload, hass)
@asyncio.coroutine
@callback
def async_reload(hass):
"""Reload the automation from config."""
yield from hass.services.async_call(DOMAIN, SERVICE_RELOAD)
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RELOAD))
def set_visibility(hass, entity_id=None, visible=True):
@@ -111,6 +138,46 @@ def set_visibility(hass, entity_id=None, visible=True):
hass.services.call(DOMAIN, SERVICE_SET_VISIBILITY, data)
def set_group(hass, object_id, name=None, entity_ids=None, visible=None,
icon=None, view=None, control=None, add=None):
"""Create a new user group."""
hass.add_job(
async_set_group, hass, object_id, name, entity_ids, visible, icon,
view, control, add)
@callback
def async_set_group(hass, object_id, name=None, entity_ids=None, visible=None,
icon=None, view=None, control=None, add=None):
"""Create a new user group."""
data = {
key: value for key, value in [
(ATTR_OBJECT_ID, object_id),
(ATTR_NAME, name),
(ATTR_ENTITIES, entity_ids),
(ATTR_VISIBLE, visible),
(ATTR_ICON, icon),
(ATTR_VIEW, view),
(ATTR_CONTROL, control),
(ATTR_ADD_ENTITIES, add),
] if value is not None
}
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SET, data))
def remove(hass, name):
"""Remove a user group."""
hass.add_job(async_remove, hass, name)
@callback
def async_remove(hass, object_id):
"""Remove a user group."""
data = {ATTR_OBJECT_ID: object_id}
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data))
def expand_entity_ids(hass, entity_ids):
"""Return entity_ids with group entity ids replaced by their members.
@@ -170,6 +237,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def async_setup(hass, config):
"""Set up all groups found definded in the configuration."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
service_groups = {}
yield from _async_process_config(hass, config, component)
@@ -179,29 +247,116 @@ def async_setup(hass, config):
)
@asyncio.coroutine
def reload_service_handler(service_call):
def reload_service_handler(service):
"""Remove all groups and load new ones from config."""
conf = yield from component.async_prepare_reload()
if conf is None:
return
yield from _async_process_config(hass, conf, component)
hass.services.async_register(
DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA)
@asyncio.coroutine
def groups_service_handler(service):
"""Handle dynamic group service functions."""
object_id = service.data[ATTR_OBJECT_ID]
# new group
if service.service == SERVICE_SET and object_id not in service_groups:
entity_ids = service.data.get(ATTR_ENTITIES) or \
service.data.get(ATTR_ADD_ENTITIES) or None
extra_arg = {attr: service.data[attr] for attr in (
ATTR_VISIBLE, ATTR_ICON, ATTR_VIEW, ATTR_CONTROL
) if service.data.get(attr) is not None}
new_group = yield from Group.async_create_group(
hass, service.data.get(ATTR_NAME, object_id),
object_id=object_id,
entity_ids=entity_ids,
user_defined=False,
**extra_arg
)
service_groups[object_id] = new_group
return
# update group
if service.service == SERVICE_SET:
group = service_groups[object_id]
need_update = False
if ATTR_ADD_ENTITIES in service.data:
delta = service.data[ATTR_ADD_ENTITIES]
entity_ids = set(group.tracking) | set(delta)
yield from group.async_update_tracked_entity_ids(entity_ids)
if ATTR_ENTITIES in service.data:
entity_ids = service.data[ATTR_ENTITIES]
yield from group.async_update_tracked_entity_ids(entity_ids)
if ATTR_NAME in service.data:
group.name = service.data[ATTR_NAME]
need_update = True
if ATTR_VISIBLE in service.data:
group.visible = service.data[ATTR_VISIBLE]
need_update = True
if ATTR_ICON in service.data:
group.icon = service.data[ATTR_ICON]
need_update = True
if ATTR_CONTROL in service.data:
group.control = service.data[ATTR_CONTROL]
need_update = True
if ATTR_VIEW in service.data:
group.view = service.data[ATTR_VIEW]
need_update = True
if need_update:
yield from group.async_update_ha_state()
return
# remove group
if service.service == SERVICE_REMOVE:
if object_id not in service_groups:
_LOGGER.warning("Group '%s' not exists!", object_id)
return
del_group = service_groups.pop(object_id)
yield from del_group.async_stop()
hass.services.async_register(
DOMAIN, SERVICE_SET, groups_service_handler,
descriptions[DOMAIN][SERVICE_SET], schema=SET_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_REMOVE, groups_service_handler,
descriptions[DOMAIN][SERVICE_REMOVE], schema=REMOVE_SERVICE_SCHEMA)
@asyncio.coroutine
def visibility_service_handler(service):
"""Change visibility of a group."""
visible = service.data.get(ATTR_VISIBLE)
tasks = [group.async_set_visible(visible) for group
in component.async_extract_from_service(service,
expand_group=False)]
yield from asyncio.wait(tasks, loop=hass.loop)
tasks = []
for group in component.async_extract_from_service(service,
expand_group=False):
group.visible = visible
tasks.append(group.async_update_ha_state())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_VISIBILITY, visibility_service_handler,
descriptions[DOMAIN][SERVICE_SET_VISIBILITY],
schema=SET_VISIBILITY_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA)
return True
@@ -231,8 +386,8 @@ def _async_process_config(hass, config, component):
class Group(Entity):
"""Track a group of entity ids."""
def __init__(self, hass, name, order=None, user_defined=True, icon=None,
view=False, control=None):
def __init__(self, hass, name, order=None, visible=True, icon=None,
view=False, control=None, user_defined=True):
"""Initialize a group.
This Object has factory function for creation.
@@ -240,31 +395,33 @@ class Group(Entity):
self.hass = hass
self._name = name
self._state = STATE_UNKNOWN
self._user_defined = user_defined
self._order = order
self._icon = icon
self._view = view
self.view = view
self.tracking = []
self.group_on = None
self.group_off = None
self.visible = visible
self.control = control
self._user_defined = user_defined
self._order = order
self._assumed_state = False
self._async_unsub_state_changed = None
self._visible = True
self._control = control
@staticmethod
def create_group(hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, control=None, object_id=None):
visible=True, icon=None, view=False, control=None,
object_id=None):
"""Initialize a group."""
return run_coroutine_threadsafe(
Group.async_create_group(hass, name, entity_ids, user_defined,
icon, view, control, object_id),
Group.async_create_group(
hass, name, entity_ids, user_defined, visible, icon, view,
control, object_id),
hass.loop).result()
@staticmethod
@asyncio.coroutine
def async_create_group(hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, control=None,
visible=True, icon=None, view=False, control=None,
object_id=None):
"""Initialize a group.
@@ -273,8 +430,9 @@ class Group(Entity):
group = Group(
hass, name,
order=len(hass.states.async_entity_ids(DOMAIN)),
user_defined=user_defined, icon=icon, view=view,
control=control)
visible=visible, icon=icon, view=view, control=control,
user_defined=user_defined
)
group.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id or name, hass=hass)
@@ -297,6 +455,11 @@ class Group(Entity):
"""Return the name of the group."""
return self._name
@name.setter
def name(self, value):
"""Set Group name."""
self._name = value
@property
def state(self):
"""Return the state of the group."""
@@ -307,19 +470,16 @@ class Group(Entity):
"""Return the icon of the group."""
return self._icon
@asyncio.coroutine
def async_set_visible(self, visible):
"""Change visibility of the group."""
if self._visible != visible:
self._visible = visible
yield from self.async_update_ha_state()
@icon.setter
def icon(self, value):
"""Set Icon for group."""
self._icon = value
@property
def hidden(self):
"""If group should be hidden or not."""
# Visibility from set_visibility service overrides
if self._visible:
return not self._user_defined or self._view
if self.visible and not self.view:
return False
return True
@property
@@ -331,10 +491,10 @@ class Group(Entity):
}
if not self._user_defined:
data[ATTR_AUTO] = True
if self._view:
if self.view:
data[ATTR_VIEW] = True
if self._control:
data[ATTR_CONTROL] = self._control
if self.control:
data[ATTR_CONTROL] = self.control
return data
@property

View File

@@ -1,5 +1,5 @@
"""
Support for Homematic devices.
Support for HomeMatic devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
@@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['pyhomematic==0.1.27']
REQUIREMENTS = ['pyhomematic==0.1.28']
DOMAIN = 'homematic'
@@ -228,7 +228,7 @@ def set_var_value(hass, entity_id, value):
def set_dev_value(hass, address, channel, param, value, proxy=None):
"""Send virtual keypress to the Homematic controlller."""
"""Call setValue XML-RPC method of supplied proxy."""
data = {
ATTR_ADDRESS: address,
ATTR_CHANNEL: channel,
@@ -245,16 +245,15 @@ def reconnect(hass):
hass.services.call(DOMAIN, SERVICE_RECONNECT, {})
# pylint: disable=unused-argument
def setup(hass, config):
"""Set up the Homematic component."""
from pyhomematic import HMConnection
hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY)
hass.data[DATA_DEVINIT] = {}
hass.data[DATA_STORE] = []
hass.data[DATA_STORE] = set()
# Create hosts list for pyhomematic
# Create hosts-dictionary for pyhomematic
remotes = {}
hosts = {}
for rname, rconfig in config[DOMAIN][CONF_HOSTS].items():
@@ -286,10 +285,10 @@ def setup(hass, config):
interface_id='homeassistant'
)
# Start server thread, connect to peer, initialize to receive events
# Start server thread, connect to hosts, initialize to receive events
hass.data[DATA_HOMEMATIC].start()
# Stops server when Homeassistant is shutting down
# Stops server when HASS is shutting down
hass.bus.listen_once(
EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop)
@@ -299,12 +298,12 @@ def setup(hass, config):
entity_hubs.append(HMHub(
hass, hub_data[CONF_NAME], hub_data[CONF_VARIABLES]))
# Register Homematic services
# Register HomeMatic services
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def _hm_service_virtualkey(service):
"""Service handle virtualkey services."""
"""Service to handle virtualkey servicecalls."""
address = service.data.get(ATTR_ADDRESS)
channel = service.data.get(ATTR_CHANNEL)
param = service.data.get(ATTR_PARAM)
@@ -315,18 +314,18 @@ def setup(hass, config):
_LOGGER.error("%s not found for service virtualkey!", address)
return
# If param exists for this device
# Parameter doesn't exist for device
if param not in hmdevice.ACTIONNODE:
_LOGGER.error("%s not datapoint in hm device %s", param, address)
return
# Channel exists?
# Channel doesn't exist for device
if channel not in hmdevice.ACTIONNODE[param]:
_LOGGER.error("%i is not a channel in hm device %s",
channel, address)
return
# Call key
# Call parameter
hmdevice.actionNodeData(param, True, channel)
hass.services.register(
@@ -335,7 +334,7 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_VIRTUALKEY)
def _service_handle_value(service):
"""Set value on homematic variable."""
"""Service to call setValue method for HomeMatic system variable."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
name = service.data[ATTR_NAME]
value = service.data[ATTR_VALUE]
@@ -347,7 +346,7 @@ def setup(hass, config):
entities = entity_hubs
if not entities:
_LOGGER.error("Homematic controller not found!")
_LOGGER.error("No HomeMatic hubs available")
return
for hub in entities:
@@ -359,7 +358,7 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_SET_VAR_VALUE)
def _service_handle_reconnect(service):
"""Reconnect to all homematic hubs."""
"""Service to reconnect all HomeMatic hubs."""
hass.data[DATA_HOMEMATIC].reconnect()
hass.services.register(
@@ -368,7 +367,7 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_RECONNECT)
def _service_handle_device(service):
"""Service handle set_dev_value services."""
"""Service to call setValue method for HomeMatic devices."""
address = service.data.get(ATTR_ADDRESS)
channel = service.data.get(ATTR_CHANNEL)
param = service.data.get(ATTR_PARAM)
@@ -380,7 +379,6 @@ def setup(hass, config):
_LOGGER.error("%s not found!", address)
return
# Call key
hmdevice.setValue(param, value, channel)
hass.services.register(
@@ -392,10 +390,9 @@ def setup(hass, config):
def _system_callback_handler(hass, config, src, *args):
"""Handle the callback."""
"""System callback handler."""
# New devices available at hub
if src == 'newDevices':
_LOGGER.debug("newDevices with: %s", args)
# pylint: disable=unused-variable
(interface_id, dev_descriptions) = args
proxy = interface_id.split('-')[-1]
@@ -403,34 +400,25 @@ def _system_callback_handler(hass, config, src, *args):
if not hass.data[DATA_DEVINIT][proxy]:
return
# Get list of all keys of the devices (ignoring channels)
key_dict = {}
addresses = []
for dev in dev_descriptions:
key_dict[dev['ADDRESS'].split(':')[0]] = True
# Remove device they allready init by HA
tmp_devs = key_dict.copy()
for dev in tmp_devs:
if dev in hass.data[DATA_STORE]:
del key_dict[dev]
else:
hass.data[DATA_STORE].append(dev)
address = dev['ADDRESS'].split(':')[0]
if address not in hass.data[DATA_STORE]:
hass.data[DATA_STORE].add(address)
addresses.append(address)
# Register EVENTS
# Search all device with a EVENTNODE that include data
# Search all devices with an EVENTNODE that includes data
bound_event_callback = partial(_hm_event_handler, hass, proxy)
for dev in key_dict:
for dev in addresses:
hmdevice = hass.data[DATA_HOMEMATIC].devices[proxy].get(dev)
# Have events?
if hmdevice.EVENTNODE:
_LOGGER.debug("Register Events from %s", dev)
hmdevice.setEventCallback(
callback=bound_event_callback, bequeath=True)
# If configuration allows autodetection of devices,
# all devices not configured are added.
if key_dict:
# Create HASS entities
if addresses:
for component_name, discovery_type in (
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
@@ -440,18 +428,18 @@ def _system_callback_handler(hass, config, src, *args):
('climate', DISCOVER_CLIMATE)):
# Get all devices of a specific type
found_devices = _get_devices(
hass, discovery_type, key_dict, proxy)
hass, discovery_type, addresses, proxy)
# When devices of this type are found
# they are setup in HA and an event is fired
# they are setup in HASS and an discovery event is fired
if found_devices:
# Fire discovery event
discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
}, config)
# Homegear error message
elif src == 'error':
_LOGGER.debug("Error: %s", args)
_LOGGER.error("Error: %s", args)
(interface_id, errorcode, message) = args
hass.bus.fire(EVENT_ERROR, {
ATTR_ERRORCODE: errorcode,
@@ -460,7 +448,7 @@ def _system_callback_handler(hass, config, src, *args):
def _get_devices(hass, discovery_type, keys, proxy):
"""Get the Homematic devices for given discovery_type."""
"""Get the HomeMatic devices for given discovery_type."""
device_arr = []
for key in keys:
@@ -468,11 +456,11 @@ def _get_devices(hass, discovery_type, keys, proxy):
class_name = device.__class__.__name__
metadata = {}
# Class supported by discovery type
# Class not supported by discovery type
if class_name not in HM_DEVICE_TYPES[discovery_type]:
continue
# Load metadata if needed to generate a param list
# Load metadata needed to generate a parameter list
if discovery_type == DISCOVER_SENSORS:
metadata.update(device.SENSORNODE)
elif discovery_type == DISCOVER_BINARY_SENSORS:
@@ -480,45 +468,41 @@ def _get_devices(hass, discovery_type, keys, proxy):
else:
metadata.update({None: device.ELEMENT})
if metadata:
# Generate options for 1...n elements with 1...n params
for param, channels in metadata.items():
if param in HM_IGNORE_DISCOVERY_NODE:
continue
# Generate options for 1...n elements with 1...n parameters
for param, channels in metadata.items():
if param in HM_IGNORE_DISCOVERY_NODE:
continue
# Add devices
_LOGGER.debug("%s: Handling %s: %s: %s",
discovery_type, key, param, channels)
for channel in channels:
name = _create_ha_name(
name=device.NAME, channel=channel, param=param,
count=len(channels)
)
device_dict = {
CONF_PLATFORM: "homematic",
ATTR_ADDRESS: key,
ATTR_PROXY: proxy,
ATTR_NAME: name,
ATTR_CHANNEL: channel
}
if param is not None:
device_dict[ATTR_PARAM] = param
# Add devices
_LOGGER.debug("%s: Handling %s: %s: %s",
discovery_type, key, param, channels)
for channel in channels:
name = _create_ha_name(
name=device.NAME, channel=channel, param=param,
count=len(channels)
)
device_dict = {
CONF_PLATFORM: "homematic",
ATTR_ADDRESS: key,
ATTR_PROXY: proxy,
ATTR_NAME: name,
ATTR_CHANNEL: channel
}
if param is not None:
device_dict[ATTR_PARAM] = param
# Add new device
try:
DEVICE_SCHEMA(device_dict)
device_arr.append(device_dict)
except vol.MultipleInvalid as err:
_LOGGER.error("Invalid device config: %s",
str(err))
else:
_LOGGER.debug("Got no params for %s", key)
_LOGGER.debug("%s autodiscovery done: %s", discovery_type, str(device_arr))
# Add new device
try:
DEVICE_SCHEMA(device_dict)
device_arr.append(device_dict)
except vol.MultipleInvalid as err:
_LOGGER.error("Invalid device config: %s",
str(err))
return device_arr
def _create_ha_name(name, channel, param, count):
"""Generate a unique object name."""
"""Generate a unique entity id."""
# HMDevice is a simple device
if count == 1 and param is None:
return name
@@ -527,11 +511,11 @@ def _create_ha_name(name, channel, param, count):
if count > 1 and param is None:
return "{} {}".format(name, channel)
# With multiple param first elements
# With multiple parameters on first channel
if count == 1 and param is not None:
return "{} {}".format(name, param)
# Multiple param on object with multiple elements
# Multiple parameters with multiple channels
if count > 1 and param is not None:
return "{} {} {}".format(name, channel, param)
@@ -546,14 +530,14 @@ def _hm_event_handler(hass, proxy, device, caller, attribute, value):
_LOGGER.error("Event handling channel convert error!")
return
# is not a event?
# Return if not an event supported by device
if attribute not in hmdevice.EVENTNODE:
return
_LOGGER.debug("Event %s for %s channel %i", attribute,
hmdevice.NAME, channel)
# keypress event
# Keypress event
if attribute in HM_PRESS_EVENTS:
hass.bus.fire(EVENT_KEYPRESS, {
ATTR_NAME: hmdevice.NAME,
@@ -562,7 +546,7 @@ def _hm_event_handler(hass, proxy, device, caller, attribute, value):
})
return
# impulse event
# Impulse event
if attribute in HM_IMPULSE_EVENTS:
hass.bus.fire(EVENT_IMPULSE, {
ATTR_NAME: hmdevice.NAME,
@@ -574,7 +558,7 @@ def _hm_event_handler(hass, proxy, device, caller, attribute, value):
def _device_from_servicecall(hass, service):
"""Extract homematic device from service call."""
"""Extract HomeMatic device from service call."""
address = service.data.get(ATTR_ADDRESS)
proxy = service.data.get(ATTR_PROXY)
if address == 'BIDCOS-RF':
@@ -589,10 +573,10 @@ def _device_from_servicecall(hass, service):
class HMHub(Entity):
"""The Homematic hub. I.e. CCU2/HomeGear."""
"""The HomeMatic hub. (CCU2/HomeGear)."""
def __init__(self, hass, name, use_variables):
"""Initialize Homematic hub."""
"""Initialize HomeMatic hub."""
self.hass = hass
self.entity_id = "{}.{}".format(DOMAIN, name.lower())
self._homematic = hass.data[DATA_HOMEMATIC]
@@ -601,7 +585,7 @@ class HMHub(Entity):
self._state = STATE_UNKNOWN
self._use_variables = use_variables
# load data
# Load data
track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB)
self._update_hub(None)
@@ -617,7 +601,7 @@ class HMHub(Entity):
@property
def should_poll(self):
"""Return false. Homematic Hub object update variable."""
"""Return false. HomeMatic Hub object updates variables."""
return False
@property
@@ -660,7 +644,7 @@ class HMHub(Entity):
self.schedule_update_ha_state()
def hm_set_variable(self, name, value):
"""Set variable on homematic controller."""
"""Set variable value on CCU/Homegear."""
if name not in self._variables:
_LOGGER.error("Variable %s not found on %s", name, self.name)
return
@@ -676,10 +660,10 @@ class HMHub(Entity):
class HMDevice(Entity):
"""The Homematic device base object."""
"""The HomeMatic device base object."""
def __init__(self, hass, config):
"""Initialize a generic Homematic device."""
"""Initialize a generic HomeMatic device."""
self.hass = hass
self._homematic = hass.data[DATA_HOMEMATIC]
self._name = config.get(ATTR_NAME)
@@ -692,13 +676,13 @@ class HMDevice(Entity):
self._connected = False
self._available = False
# Set param to uppercase
# Set parameter to uppercase
if self._state:
self._state = self._state.upper()
@property
def should_poll(self):
"""Return false. Homematic states are pushed by the XML RPC Server."""
"""Return false. HomeMatic states are pushed by the XML-RPC Server."""
return False
@property
@@ -721,49 +705,44 @@ class HMDevice(Entity):
"""Return device specific state attributes."""
attr = {}
# no data available to create
# No data available
if not self.available:
return attr
# Generate an attributes list
# Generate a dictionary with attributes
for node, data in HM_ATTRIBUTE_SUPPORT.items():
# Is an attributes and exists for this object
# Is an attribute and exists for this object
if node in self._data:
value = data[1].get(self._data[node], self._data[node])
attr[data[0]] = value
# static attributes
# Static attributes
attr['id'] = self._hmdevice.ADDRESS
attr['proxy'] = self._proxy
return attr
def link_homematic(self):
"""Connect to Homematic."""
# Device is already linked
"""Connect to HomeMatic."""
if self._connected:
return True
# Init
# Initialize
self._hmdevice = self._homematic.devices[self._proxy][self._address]
self._connected = True
# Check if Homematic class is okay for HA class
_LOGGER.info("Start linking %s to %s", self._address, self._name)
try:
# Init datapoints of this object
# Initialize datapoints of this object
self._init_data()
if self.hass.data[DATA_DELAY]:
# We delay / pause loading of data to avoid overloading
# of CCU / Homegear when doing auto detection
# We optionally delay / pause loading of data to avoid
# overloading of CCU / Homegear
time.sleep(self.hass.data[DATA_DELAY])
self._load_data_from_hm()
_LOGGER.debug("%s datastruct: %s", self._name, str(self._data))
# Link events from pyhomatic
# Link events from pyhomematic
self._subscribe_homematic_events()
self._available = not self._hmdevice.UNREACH
_LOGGER.debug("%s linking done", self._name)
# pylint: disable=broad-except
except Exception as err:
self._connected = False
@@ -774,29 +753,28 @@ class HMDevice(Entity):
"""Handle all pyhomematic device events."""
_LOGGER.debug("%s received event '%s' value: %s", self._name,
attribute, value)
have_change = False
has_changed = False
# Is data needed for this instance?
if attribute in self._data:
# Did data change?
if self._data[attribute] != value:
self._data[attribute] = value
have_change = True
has_changed = True
# If available it has changed
# Availability has changed
if attribute == 'UNREACH':
self._available = bool(value)
have_change = True
has_changed = True
# If it has changed data point, update HA
if have_change:
_LOGGER.debug("%s update_ha_state after '%s'", self._name,
attribute)
# If it has changed data point, update HASS
if has_changed:
self.schedule_update_ha_state()
def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job."""
channels_to_sub = {0: True} # add channel 0 for UNREACH
channels_to_sub = set()
channels_to_sub.add(0) # Add channel 0 for UNREACH
# Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
@@ -814,8 +792,7 @@ class HMDevice(Entity):
# Prepare for subscription
try:
if int(channel) >= 0:
channels_to_sub.update({int(channel): True})
channels_to_sub.add(int(channel))
except (ValueError, TypeError):
_LOGGER.error("Invalid channel in metadata from %s",
self._name)
@@ -858,14 +835,14 @@ class HMDevice(Entity):
return None
def _init_data(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
# Add all attributes to data dict
"""Generate a data dict (self._data) from the HomeMatic metadata."""
# Add all attributes to data dictionary
for data_note in self._hmdevice.ATTRIBUTENODE:
self._data.update({data_note: STATE_UNKNOWN})
# init device specified data
# Initialize device specific data
self._init_data_struct()
def _init_data_struct(self):
"""Generate a data dict from the Homematic device metadata."""
"""Generate a data dictionary from the HomeMatic device metadata."""
raise NotImplementedError

View File

@@ -51,7 +51,7 @@ CONF_TRUSTED_NETWORKS = 'trusted_networks'
CONF_LOGIN_ATTEMPTS_THRESHOLD = 'login_attempts_threshold'
CONF_IP_BAN_ENABLED = 'ip_ban_enabled'
# TLS configuation follows the best-practice guidelines specified here:
# TLS configuration follows the best-practice guidelines specified here:
# https://wiki.mozilla.org/Security/Server_Side_TLS
# Intermediate guidelines are followed.
SSL_VERSION = ssl.PROTOCOL_SSLv23
@@ -339,7 +339,7 @@ class HomeAssistantWSGI(object):
@asyncio.coroutine
def stop(self):
"""Stop the wsgi server."""
"""Stop the WSGI server."""
if self.server:
self.server.close()
yield from self.server.wait_closed()

View File

@@ -19,6 +19,8 @@ from .const import (
KEY_FAILED_LOGIN_ATTEMPTS)
from .util import get_real_ip
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_ID_BAN = 'ip-ban'
NOTIFICATION_ID_LOGIN = 'http-login'
@@ -29,8 +31,6 @@ SCHEMA_IP_BAN_ENTRY = vol.Schema({
vol.Optional('banned_at'): vol.Any(None, cv.datetime)
})
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def ban_middleware(app, handler):

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