Compare commits

..

107 Commits

Author SHA1 Message Date
Paulus Schoutsen 50e5032f86 Merge pull request #9131 from home-assistant/release-0-52
0.52
2017-08-25 22:11:12 -07:00
Martin Hjelmare 1d615ea6c3 Refactor mysensors callback and add validation (#9069)
* Refactor mysensors callback and add validation

* Add mysensors entity class. The mysensors entity class inherits from
  a more general mysensors device class.
* Extract mysensors name function.
* Add setup_mysensors_platform for mysensors platforms.
* Add mysensors const schemas.
* Update mysensors callback and add child validation.
* Remove gateway wrapper class.
* Add better logging for mysensors callback.
* Add discover_persistent_devices function.
* Remove discovery in mysensors component setup.
* Clean up gateway storage in hass.data.
* Update all mysensors platforms.
  * Add repr for MySensorsNotificationDevice.
  * Fix bug in mysensors climate target temperatures.
  * Clean up platforms. Child validation simplifies assumptions in
    platforms.
  * Remove not needed try except statements. All messages are validated
    already in pymysensors.
* Clean up logging.
* Add timer debug logging if callback is slow.
* Upgrade pymysensors to 0.11.0.

* Make dispatch callback async

* Pass tuple device_args and optional add_devices

* Also return new_devices as list instead of dictionary.
2017-08-25 21:47:22 -07:00
Paulus Schoutsen 2e1b1635b1 Version bump to 0.52 2017-08-25 08:44:02 -07:00
Paulus Schoutsen fe7dca5144 Merge remote-tracking branch 'origin/master' into dev 2017-08-25 08:43:26 -07:00
Fabian Affolter fdeef2f707 Use const (#9127)
* Use const

* Align quotes
2017-08-25 13:30:00 +02:00
PhracturedBlue 2ec0d25a38 optimistic mode for template covers (w/o timed movement) (#8402)
* Emulate set_current_position in cover.template

* Add opportunistic mode

* Prevent another move when cover is already moving.  Add tests for opotunistic/timed-delay mode

* Remove timed-move capabilities

* Set init state to unknown

* cleanup template

* Update test_template.py
2017-08-25 12:33:53 +02:00
Daniel Høyer Iversen fb5019e73f refactor pushbullet (#9125)
* refactor push bullet
2017-08-25 11:25:06 +02:00
Daniel Høyer Iversen 1e276a7b07 Xiaomi (#9126)
* small fixes xiaomi
2017-08-25 10:22:32 +02:00
Daniel Høyer Iversen d72a181e30 Update flux_led.py (#9122) 2017-08-24 23:31:57 +02:00
lekobob 698d133455 Simplisafe unknown status fix (#9111)
* Simplisafe unknown status fix

Changed simplisafe-python requirement to 1.0.5 and changed state return
case statements to lower case

* Bump requirements_all.txt
2017-08-24 08:09:50 +02:00
Jeroen ter Heerdt 0dccef4063 pythonegardia package requirement to .18 (#9104)
* Bumping pythonegardia package requirement up to .18

* Updating requirements_all to reflect updated pythonegardia package .18
2017-08-23 17:11:13 +02:00
Daniel Høyer Iversen feb85b90b4 upgrade Xiaomi Gateway lib to 0.3 (#9101) 2017-08-23 12:37:17 +02:00
Robin 48909539be Fix issue 8894 with uk_transport component if no next_buses or next_trains (#9046)
* Fix bug if no next_buses or trains

Fixes https://github.com/home-assistant/home-assistant/issues/8894

* Requested fixes
2017-08-23 00:05:06 -07:00
Fabian Affolter 2355216f61 Catch exceptions (#9085)
* Catch exceptions

* Fix pylint disable

* Move check for emtpy target list
2017-08-22 22:21:09 -07:00
Teemu R 55a44b0a1c Yeelight fix updates on hsv mode (#9093)
* cast strings to integers for hsv_to_rgb conversion, fixes #6473

* remove type_checking, flake8 does not like that.

* use hsv_to_rgb to convert to correct rgb value
2017-08-22 22:19:33 -07:00
happyleavesaoc 27b0d648a6 bump fedex version (#9099) 2017-08-22 22:14:06 -07:00
happyleavesaoc 90724847a3 bump snapcast version (#9100) 2017-08-22 22:13:52 -07:00
Teemu R 90fb33f610 Support changing the bulb color for tplink smartbulbs, fixes #8766 (#8780)
* Support changing the bulb color for tplink smartbulbs, related to #8766

* existence of ATTR_RGB_COLOR in kwargs, not just its existence...

* return modified supported features

* rgb-hsv conversion utils from hass return bogus values (at least for this device), so doing conversions directly with colorsys

* add typing & documentation for color model conversions

* make linters happy

* cast hsv to integer before passing it to the backend library

* make sure the bulb is on before adjusting the other settings

* allow floats as inputs for conversions, return always integers

* use typing hint in the parameter list instead of at assignment

* do not assign local color state inside turn_on, but let update handle doing it

* use forward declaration for typing, fixes travis requirements build hopefully

* rename hsv and rgb

* remove type-checking check, forward declarations should work just fine without it

* disable (broken) pylint warnings, these can be removed after astroid is updated from 1.4.9 to 1.5
2017-08-22 22:11:44 -07:00
aetolus cb59b3fee1 Add worldtidesinfo sensor component (#8860)
* Style fixes for worldtidesinfo sensor component

* Fix D202 for worldtidesinfo sensor component

* Multiple fixes

* Multiple fixes

* Fixes

* more

* working with changes

* changes

* changes

* fix style errors

* fix style errors

* Complete rewrite

* worldtidesinfo

Fix D202 for worldtidesinfo sensor component

Multiple fixes

Multiple fixes

Fixes

more

working with changes

changes

changes

fix style errors

fix style errors

Complete rewrite

PR Changes

* Fix

* fix scan interval & lint
2017-08-23 00:40:16 +02:00
Michaël Arnauts 90689c38f7 Fix netdata system_load and add disk_free. (#9091) 2017-08-22 17:52:29 +02:00
Jan Losinski fd6fd765b2 Pilight switch: restore last state after restart (#8580)
* Pilight switch: restore last state after restart

This uses the restore_state helper to set the last known state to
pilight switches when the devices are initialized after a HA
restart.

Without this HA forget the state on every restart and needs to be told
the sttae by retoggling the switches. This can cause unwanted effects
as a switch toggling may emit an RF signal.

* Make hound happy

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>

* Remove entity_id generation as requested in review.

* Make hound happy again.

* fix comments

* fix lint
2017-08-22 16:40:14 +02:00
William Scanlon 06a20d0d15 Fix octoprint errors when printer is off/disconnected (#8988)
* Fix octoprint errors when printer is off/disconnected
2017-08-22 09:37:06 -04:00
Fabian Affolter 252aea37d2 Don't redefine consts (#9086) 2017-08-22 13:12:01 +02:00
Alex 5a3a43cd5b 9043 Fixed error while running dev docker (#9044) 2017-08-22 13:48:50 +03:00
Fabian Affolter dd0ca0adc4 Upgrade credstash to 1.13.3 (#9088) 2017-08-22 13:32:46 +03:00
Fabian Affolter c77d2ea341 Remove dash (#9089) 2017-08-22 13:31:53 +03:00
Fabian Affolter 4a3be6d514 Upgrade youtube_dl to 2017.8.18 (#9079) 2017-08-22 10:26:31 +03:00
Max da2cb8e97e Fix device attribute in fritz_callmonitor.py (fixes #9055) (#9081) 2017-08-22 10:24:36 +03:00
Fabian Affolter 42fcaf9a75 Upgrade discord.py to 0.16.10 (#9082) 2017-08-22 10:24:02 +03:00
Fabian Affolter af8aec001c Upgrade uber_rides to 0.5.1 (#9080) 2017-08-22 10:23:11 +03:00
Tom Harris f6c5e5ff00 Added insteonplm device_override multiple capabilities (#9078) 2017-08-22 10:22:37 +03:00
Erik Eriksson 398735c9be async_query returns False if connection to server failed, handle this properly (#9070) 2017-08-22 07:09:11 +02:00
Alok Saboo 8ceeee032c Bump abodepy to 0.7.1 (#9077)
* Version bump to 0.7.1

* Update abodepy version
2017-08-22 07:08:27 +02:00
Nolan Gilley 54f01f3f11 bump python-ecobee-api version to 0.0.8 (#9074) 2017-08-21 23:16:17 +02:00
Alok Saboo bc549e9525 Use builtin constants for Abode alarm_control_panel (#9059)
* Use builtin constants for alarm_control_panel

* Made it consistent with other alarm panels

* Replaced STATE_UNKNOWN with None
2017-08-21 22:20:38 +02:00
Erik Eriksson 4bb78097a7 eliqonline: channel id is an integer (#9072) 2017-08-21 21:57:12 +02:00
Ståle Semb Hauknes 7c380588a0 Workday sensor offset (#8824)
* Add support for offset for the workday sensor

* Update tests for workday sensor

* Changed from 'offset' to 'days_offset'

* Attributes bugfix (dictionary key variable repeated with different values)
2017-08-21 13:24:30 +02:00
Fabian Affolter f7daefd7a5 Upgrade onkyo-eiscp to 1.2.4 (fixes #8995) (#9068) 2017-08-21 11:25:34 +02:00
Martin Berg 97e6a69adb Add support for Prowl notifications. (#9028)
* Add support for Prowl notifications.

* Use HA session handler.

* Simplify http request logic.

* flake

* fix double fetch data

* Remove periods from log messages
2017-08-21 10:46:07 +02:00
Fabian Affolter fe7384a4ef Upgrade slacker to 0.9.60 (#9065)
* Upgrade slacker to 0.9.60

* Group imports
2017-08-21 10:23:29 +02:00
Fabian Affolter 3c9e09ce16 Upgrade sendgrid to 5.0.0 (#9062) 2017-08-21 07:15:37 +02:00
Matt Schmitt c3d548a0dd Update fitbit.py (#9064)
Minor format update, set ‘type’ attribute to lowercase (Fitbit returns
all uppercase currently)
2017-08-21 07:15:15 +02:00
Alok Saboo ebd64cded9 Bump dlib face_recognition to 0.2.2 (#9060) 2017-08-20 22:30:35 +02:00
Anders Melchiorsen fee89d8d16 LIFX: avoid rare NoneType errors (#9054)
* Get full multizone state during registration

We used to rely on the periodic update to get the state of each zone, only
establishing the number of zones during registration. This resulted in errors
if the current state was needed for a partial color change before the first
async_update happened.

Now we do a full update before adding the light. Thus async_update can no
longer assume device.color_zones to be defined and must instead use the
response message to decide the total number of zones.

* Insist on getting the initial state

If a response to the initial state query is lost we used to just carry on.
This resulted in type errors when we next tried to access the undefined state.

After this commit the light is not added before we have the full state.

This scenario mostly happens when something is misbehaving and the type errors
were actually useful in figuring out what happend. So an error message is
logged in their place.

* Remove lint
2017-08-20 20:29:54 +02:00
Alok Saboo b3d16e8f89 Add Abode home security component (#9030)
* Add Abode home security component

* Remove protected member

* Remove debug messages

* Remove unwanted debug messages

* Updated based on script/gen_requirements_all

* Commit to restart the build process

* Remove unwanted return

* Removed unused listener

* Address Pascal's comments

* Updated alarm control panel based on Pascal's comments

* Removed debug messages

* Removed unused hass object
2017-08-20 16:55:48 +02:00
Matt Schmitt c059dfdb67 Update Fitbit sensor (icons, formatting, client update) (#9031)
* Update fitbit.py

Add variable icon for battery status, clean up formatting for resource
names and values

* Update fitbit.py and requirements_all.txt

Fix PR comments and update client

* Update fitbit.py

Add dict map for battery levels and use icon util
2017-08-19 22:47:31 +02:00
boojew d153ee0b9f Add speeds to fan dropdown in ISY fan component (#9004)
* Add speeds to fan dropdown in ISY fan component

* Update isy994.py

* Update isy994.py

* Update isy994.py

* Update isy994.py

* Update isy994.py

* Update isy994.py

* Update isy994.py

* Update isy994.py
2017-08-19 21:17:47 +02:00
David 0f9ae8827c Upgrade python-pushover to 0.3 (#9045)
* Upgrade python-pushover

* Upgrade python-pushover
2017-08-19 17:00:07 +02:00
Steve 5d52993231 Support Windows in UPNP discovery (#8936)
* Support WIndows and Linix

* Correct indentation

* reduce line length

* Lint
2017-08-19 15:26:27 +02:00
Matt Schmitt 84025e46ff Update ios.py (#9041)
Use battery icon util for charging condition also
2017-08-19 15:24:13 +02:00
Sören Oldag bf66019c66 Configurable timeout for webostv. (#9042)
* Configurable timeout for webostv.

* Make PEP257 validation to pass
2017-08-19 15:14:02 +02:00
Sören Oldag a748b5ee5e Update pwmled to 1.2.1. (#9040) 2017-08-19 13:23:46 +02:00
Robin 98370560e1 Adds London_air component (#9020)
* Adds London_air component

* Fix lints

* Reduce fixture

* Fix config validate

* Fix naming

* fix tests
2017-08-19 11:05:16 +02:00
Matt Schmitt 597f53ae30 Update iOS sensor (battery icon fix and format updates) (#9032)
* Update ios.py

Clean up battery and charging icons (MDI was missing some versions),
fix minor bug when battery level = 95%

* Update ios.py

Migrated function to battery icon util
2017-08-19 10:59:54 +02:00
Steven Looman 7ac1e469b7 Set password after connecting. Fixes #8983 (#9039) 2017-08-19 10:58:42 +02:00
John Mihalic ecc249aa27 Refactor USPS into component with Sensors+Camera (#8679)
* Inital USPS Camera expansion

* Cleanup debugging, add camera change interval

* Change to local nomail image

* Explicitly pass in date

* Move camera date info to model property

* Fix copy typo

* Fix hound line-length

* Fix lint whitespace

* Fix requirements

* Bump myusps version, clarify interval, alter update scheme

* Add units

* Code cleanup, address comments

* Use built-in scan interval, remove nomail image

* Remove logging line
2017-08-18 23:47:36 +02:00
celeroll 6215e27de4 Fix Geizhals index issue when not 4 prices available (#9035)
* Out of index issue, when not 4 prices are available

* Removed the parenthesis, to fix the lint error.
2017-08-18 19:59:20 +02:00
Paulus Schoutsen b282167f26 Add state_with_unit property to state objects in templates (#9014)
* Wrap state objects in templates

* Fix tests

* Fix bugs

* Lint

* Remove invalid state warning
2017-08-17 23:19:35 -07:00
Pascal Vizeli c278209c7b Update ffmpeg to 1.7 to fix severals problems (#9029)
* Update ffmpeg to 1.7 to fix severals problems

* Update ffmpeg.py

* Update requirements_test_all.txt
2017-08-18 00:51:52 +02:00
Tom Matheussen 427d7ee1fc Check if album image(s) exist in spotify (#9024)
* Check if album image(s) exist in spotify

* Actually set the image to None

* Simplified using ternary operator
2017-08-17 22:39:20 +02:00
Dan 55234a7fa3 Update onkyo-eiscp to 1.2.3 (#9019) 2017-08-16 21:51:03 -07:00
BioSehnsucht 3765f882c7 Add HipChat notify service. (#8918)
* Add HipChat notify service.

* Change HipChat notify service to use python-simple-hipchat-v2.

* Change HipChat notify service to use hipnotify

* Change HipChat notify service to remove redundant validation
2017-08-16 19:26:30 -04:00
Michael Hertig b75ce4f1b2 Fix #9010 - Swiss Public Transportation shows departure time in the past (#9011) 2017-08-16 21:28:51 +02:00
Dan Cinnamon 95663f8126 Update to pyenvisalink 2.2, and remove range validation on zonedump i… (#8981)
* Update to pyenvisalink 2.2, and remove range validation on zonedump interval.

* Keep using default timer dump variable, only remove minimum check.

* Fix lint issue

* Indentation issue
2017-08-16 12:08:15 +02:00
karlkar f114263845 Pushbullet, fix multiple messages sent when url param is set (#9006) 2017-08-16 09:29:42 +02:00
mjj4791 e7ce110dc6 Buienradar newconditions (#8897)
* new monitored conditions and support for new weathercard

* new monitored conditions and support for new weathercard

* minor changes
2017-08-15 23:07:04 -07:00
timstanley1985 3342db33e4 MQTT Switch - Add configurable availability payload (#8934)
* Add configurable availabilty payload

* Fix

* Fix

* Lint fixes

* Fix tests

* Fix tests

* Move from const.py to mqtt switch

* New test

* Fix flake*
2017-08-15 23:04:57 -07:00
Paulus Schoutsen 0fb281c5b3 Update frontend 2017-08-15 22:34:46 -07:00
Paulus Schoutsen 2dab239021 Add scripts editor backend (#8993)
* Add scripts editor backend

* Fix docstrings
2017-08-15 22:09:10 -07:00
Adam Mills 95c57412ff Automatic device tracker remove password (#9002)
* Remove now disabled password auth from automatic

* Fallback to configurator more permissively

* Fix test for changes

* Bump lib
2017-08-15 21:04:44 -04:00
Aaron Bach eb42d59210 Adds port/SSL config options for RainMachine (#8986)
* Adding port/SSL config updates

* New requirements generated

* Made `port` and `ssl` parameters optional

* Add defaults for new parameters

* Re-adding guard clause

* pass > continue
2017-08-15 20:03:40 +02:00
Tim Lyakhovetskiy 6507cc1dc8 Fix #8960 - Decora Wi-Fi Switch unable to set brightness (#8989) 2017-08-15 16:12:16 +02:00
Daniel Høyer Iversen 1892eb654f Is_allowed_path raise for None path (#8953)
* is_allowed_path

* Fix #8948

* assert path is not None

* Update test_core.py

* Update test_core.py

* Update test_core.py
2017-08-15 15:41:37 +02:00
Jack 5309006494 Added continue-on-errors, added value template (#8971)
* Added continue-on-errors, added value template

* Refactored long lines

* Fixed whitespace issues
2017-08-14 16:31:06 +02:00
Philipp Schmitt e2920ce5e5 Nello.io lock support (#8957)
* Initial Nello.io lock support

* Log an error when unlocking failed

* Make the lock's state always locked
2017-08-14 10:02:37 +02:00
Adam Mills 19d1d748d4 Add support for Automatic OAuth2 authentication (#8962)
* Add support for Automatic OAuth2 authentication

* Fix async conversion of configurator

* Rename method for async

* Use hass.components to get configurator component

* Fix typo

* Move session data to hidden directory

* Make configurator callback optional
2017-08-13 22:37:50 -07:00
Paulus Schoutsen 8d661f8dea Merge pull request #8980 from home-assistant/release-0-51-2
0.51.2
2017-08-13 22:18:05 -07:00
Paulus Schoutsen 9363b189ba Sabnzbd: do not assume discovery info is a dict (#8951) 2017-08-13 21:54:21 -07:00
Eugenio Panadero 4da876e5c2 fix DeviceException handling when updating xiaomi vacuum (#8954)
* Fix DeviceException handling when updating entity

* add DeviceException error handling to generic request
2017-08-13 21:54:21 -07:00
Martin Hjelmare 335008ae5c Fix call to ha_send_commands (#8956)
* Name keyword arguments correctly according to dependency lib.
* Only pass keyword arguments that are not None.
2017-08-13 21:54:20 -07:00
Paulus Schoutsen a4da31b573 fix issue #8948 in pushbullet (#8965)
* fix issue #8948 in pushbullet

* pushbullet
2017-08-13 21:54:20 -07:00
Andrey cd795489ca Turn foscam verbose mode off (#8967) 2017-08-13 21:54:20 -07:00
Andrey a8a037db49 Fix zwave power_consumption attribute (#8968) 2017-08-13 21:54:19 -07:00
Paulus Schoutsen fc8e8e5d8c Update frontend 2017-08-13 21:53:26 -07:00
Paulus Schoutsen 56597d290c Version bump to 0.51.2 2017-08-13 21:53:22 -07:00
Paulus Schoutsen 8fcec03adf Update frontend 2017-08-13 21:52:36 -07:00
Andrey a0ddb24245 Turn foscam verbose mode off (#8967) 2017-08-13 18:16:38 -07:00
Andrey 23273d3e88 Fix zwave power_consumption attribute (#8968) 2017-08-13 18:15:59 -07:00
Paulus Schoutsen 74adebc2fd fix issue #8948 in pushbullet (#8965)
* fix issue #8948 in pushbullet

* pushbullet
2017-08-13 13:28:36 -07:00
Paulus Schoutsen 4b3a932d88 Sabnzbd: do not assume discovery info is a dict (#8951) 2017-08-13 11:29:48 -07:00
Martin Hjelmare cbe5225e04 Fix call to ha_send_commands (#8956)
* Name keyword arguments correctly according to dependency lib.
* Only pass keyword arguments that are not None.
2017-08-13 11:28:33 -07:00
Matt Schmitt 811fdc5533 Add service to alarm control panel for night mode arming (#8614)
* Update const.py

* Update __init__.py

* Update services.yaml

* Update totalconnect.py

* Update manual.py

Add night arm service for manual alarm control panel

* Update test_manual.py

Add tests for night mode arming

* Update manual.py

Fix docstring
2017-08-13 19:57:48 +02:00
Eugenio Panadero c92e5c147a fix DeviceException handling when updating xiaomi vacuum (#8954)
* Fix DeviceException handling when updating entity

* add DeviceException error handling to generic request
2017-08-13 15:02:48 +02:00
Sebastian Muszynski 73d6227021 Remove spaces from Xiami switch attributes (#8952)
* Attributes of the xiaomi zigbee plug changed.

* Reformat.
2017-08-13 09:54:43 +02:00
Alok Saboo 79f45b5176 Fixed cert_expiry sensor to delay firing on HA startup (#8920)
* Fixed cert_expiry sensor to delay firing on HA startup

* Addressed Travis complaints

* Added imports

* Fixed cert_expiry sensor to delay firing on HA startup

* Changed comment
2017-08-12 23:49:15 -07:00
Paulus Schoutsen b18679ec0b Merge pull request #8942 from home-assistant/release-0-51-1
0.51.1
2017-08-12 14:59:38 -07:00
Paulus Schoutsen 7d566c2c3d Version bump to 0.51.1 2017-08-12 14:56:34 -07:00
Paulus Schoutsen 46d9d77d03 Update frontend 2017-08-12 14:56:24 -07:00
Paulus Schoutsen 4a98b32a03 Update frontend 2017-08-12 14:54:50 -07:00
Martin Hjelmare fbb6782081 Fix SET_TEMPERATURE_SCHEMA in climate component (#8879)
* Require either temperature or high/low target temperatures.
* Add tests.
2017-08-12 09:39:05 -07:00
cribbstechnologies 369caeedbd fixing emulated hue issue and testing it (#8928)
* fixing emulated hue issue and testing it

* fixing hound issues

* I should probably stop using vim

* Check against dict directly instead of items.
2017-08-12 08:50:02 -07:00
groth-its 489a02b2c2 Fix hue lights for Philips and non-philips lights (#8905) 2017-08-12 08:38:12 -07:00
Fabian Affolter c4550d02c5 Add version sensor (#8912)
* Add version sensor

* Set version directly

* Rework tests and fix typo

* Remove additional blank line
2017-08-12 08:52:56 +02:00
Martin Hjelmare 49733b7fdf Remove not needed call to update (#8930)
* This will ensure no I/O in entity properties.
2017-08-11 19:55:57 -07:00
Philipp Schmitt 0999e2ddc4 Update roombapy to 1.3.1 to avoid installing all the mapping dependencies (#8925) 2017-08-11 11:22:22 +02:00
William Scanlon d427063acd Update python-wink version to fix Dome water valve bug. (#8923) 2017-08-11 08:35:45 +02:00
Fabian Affolter ff3a4637a4 Version bump to 0.52.0.dev0 2017-08-10 23:28:04 +02:00
133 changed files with 3538 additions and 1416 deletions
+10 -1
View File
@@ -8,6 +8,9 @@ omit =
homeassistant/helpers/signal.py
# omit pieces of code that rely on external devices being present
homeassistant/components/abode.py
homeassistant/components/*/abode.py
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
@@ -176,6 +179,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/usps.py
homeassistant/components/*/usps.py
homeassistant/components/velbus.py
homeassistant/components/*/velbus.py
@@ -326,6 +332,7 @@ omit =
homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nello.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
@@ -383,6 +390,7 @@ omit =
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/hipchat.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
@@ -391,6 +399,7 @@ omit =
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/prowl.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
homeassistant/components/notify/pushover.py
@@ -517,9 +526,9 @@ omit =
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
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/worldtidesinfo.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py
+3
View File
@@ -94,3 +94,6 @@ docs/build
# Windows Explorer
desktop.ini
/home-assistant.pyproj
/home-assistant.sln
/.vs/home-assistant/v14
+75
View File
@@ -0,0 +1,75 @@
"""
This component provides basic support for Abode Home Security system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/abode/
"""
import logging
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME
REQUIREMENTS = ['abodepy==0.7.1']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by goabode.com"
DOMAIN = 'abode'
DEFAULT_NAME = 'Abode'
DATA_ABODE = 'data_abode'
DEFAULT_ENTITY_NAMESPACE = 'abode'
NOTIFICATION_ID = 'abode_notification'
NOTIFICATION_TITLE = 'Abode Security Setup'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up Abode component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
try:
data = AbodeData(username, password)
hass.data[DATA_ABODE] = data
for component in ['binary_sensor', 'alarm_control_panel']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
return True
class AbodeData:
"""Shared Abode data."""
def __init__(self, username, password):
"""Initialize Abode oject."""
import abodepy
self.abode = abodepy.Abode(username, password)
self.devices = self.abode.get_devices()
_LOGGER.debug("Abode Security set up with %s devices",
len(self.devices))
@@ -13,7 +13,8 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT)
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@@ -31,6 +32,7 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
@@ -81,6 +83,18 @@ def alarm_arm_away(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
@bind_hass
def alarm_arm_night(hass, code=None, entity_id=None):
"""Send the alarm the command for arm night."""
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
@bind_hass
def alarm_trigger(hass, code=None, entity_id=None):
"""Send the alarm the command for trigger."""
@@ -187,6 +201,17 @@ class AlarmControlPanel(Entity):
"""
return self.hass.async_add_job(self.alarm_arm_away, code)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
raise NotImplementedError()
def async_alarm_arm_night(self, code=None):
"""Send arm night command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_night, code)
def alarm_trigger(self, code=None):
"""Send alarm trigger command."""
raise NotImplementedError()
@@ -0,0 +1,82 @@
"""
This component provides HA alarm_control_panel support for Abode System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.abode/
"""
import logging
from homeassistant.components.abode import (DATA_ABODE, DEFAULT_NAME)
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.components.alarm_control_panel as alarm
DEPENDENCIES = ['abode']
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:security'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device."""
data = hass.data.get(DATA_ABODE)
add_devices([AbodeAlarm(hass, data, data.abode.get_alarm())])
class AbodeAlarm(alarm.AlarmControlPanel):
"""An alarm_control_panel implementation for Abode."""
def __init__(self, hass, data, device):
"""Initialize the alarm control panel."""
super(AbodeAlarm, self).__init__()
self._device = device
self._name = "{0}".format(DEFAULT_NAME)
@property
def should_poll(self):
"""Return the polling state."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return icon."""
return ICON
@property
def state(self):
"""Return the state of the device."""
if self._device.mode == "standby":
state = STATE_ALARM_DISARMED
elif self._device.mode == "away":
state = STATE_ALARM_ARMED_AWAY
elif self._device.mode == "home":
state = STATE_ALARM_ARMED_HOME
else:
state = None
return state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._device.set_standby()
self.schedule_update_ha_state()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._device.set_home()
self.schedule_update_ha_state()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._device.set_away()
self.schedule_update_ha_state()
def update(self):
"""Update the device state."""
self._device.refresh()
@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
REQUIREMENTS = ['pythonegardia==1.0.17']
REQUIREMENTS = ['pythonegardia==1.0.18']
_LOGGER = logging.getLogger(__name__)
@@ -12,9 +12,10 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
@@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
def state(self):
"""Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_NIGHT) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
@@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
return
self._state = STATE_ALARM_ARMED_NIGHT
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
@@ -31,6 +31,17 @@ alarm_arm_away:
description: An optional code to arm away the alarm control panel with
example: 1234
alarm_arm_night:
description: Send the alarm the command for arm night
fields:
entity_id:
description: Name of alarm control panel to arm night
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm night the alarm control panel with
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger
@@ -16,7 +16,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['simplisafe-python==1.0.4']
REQUIREMENTS = ['simplisafe-python==1.0.5']
_LOGGER = logging.getLogger(__name__)
@@ -89,11 +89,11 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
def state(self):
"""Return the state of the device."""
status = self.simplisafe.state()
if status == 'Off':
if status == 'off':
state = STATE_ALARM_DISARMED
elif status == 'Home':
elif status == 'home':
state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
elif status == 'away':
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
@@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.11']
@@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel):
state = STATE_ALARM_ARMED_HOME
elif status == self._client.ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_STAY_NIGHT:
state = STATE_ALARM_ARMED_NIGHT
elif status == self._client.ARMING:
state = STATE_ALARM_ARMING
elif status == self._client.DISARMING:
state = STATE_ALARM_DISARMING
else:
state = STATE_UNKNOWN
@@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel):
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()
def alarm_arm_night(self, code=None):
"""Send arm night command."""
self._client.arm_stay_night()
+1 -1
View File
@@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials):
hass.async_add_job(configurator.request_done, instance)
instance = configurator.request_config(
hass, 'Apple TV Authentication', configuration_callback,
'Apple TV Authentication', configuration_callback,
description='Please enter PIN code shown on screen.',
submit_caption='Confirm',
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
+1 -1
View File
@@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber):
title = '{} ({})'.format(name, host)
request_id = configurator.request_config(
hass, title, configuration_callback,
title, configuration_callback,
description='Functionality: ' + str(AXIS_INCLUDE),
entity_picture="/static/images/logo_axis.png",
link_name='Axis platform documentation',
@@ -0,0 +1,81 @@
"""
This component provides HA binary_sensor support for Abode Security System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.abode/
"""
import logging
from homeassistant.components.abode import (CONF_ATTRIBUTION, DATA_ABODE)
from homeassistant.const import (ATTR_ATTRIBUTION)
from homeassistant.components.binary_sensor import (BinarySensorDevice)
DEPENDENCIES = ['abode']
_LOGGER = logging.getLogger(__name__)
# Sensor types: Name, device_class
SENSOR_TYPES = {
'Door Contact': 'opening',
'Motion Camera': 'motion',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device."""
data = hass.data.get(DATA_ABODE)
sensors = []
for sensor in data.devices:
_LOGGER.debug('Sensor type %s', sensor.type)
if sensor.type in ['Door Contact', 'Motion Camera']:
sensors.append(AbodeBinarySensor(hass, data, sensor))
_LOGGER.debug('Adding %d sensors', len(sensors))
add_devices(sensors)
class AbodeBinarySensor(BinarySensorDevice):
"""A binary sensor implementation for Abode device."""
def __init__(self, hass, data, device):
"""Initialize a sensor for Abode device."""
super(AbodeBinarySensor, self).__init__()
self._device = device
@property
def should_poll(self):
"""Return the polling state."""
return True
@property
def name(self):
"""Return the name of the sensor."""
return "{0} {1}".format(self._device.type, self._device.name)
@property
def is_on(self):
"""Return True if the binary sensor is on."""
if self._device.type == 'Door Contact':
return self._device.status != 'Closed'
elif self._device.type == 'Motion Camera':
return self._device.get_value('motion_event') == '1'
@property
def device_class(self):
"""Return the class of the binary sensor."""
return SENSOR_TYPES.get(self._device.type)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs['device_id'] = self._device.device_id
attrs['battery_low'] = self._device.battery_low
return attrs
def update(self):
"""Update the device state."""
self._device.refresh()
@@ -4,62 +4,27 @@ Support for MySensors binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (DEVICE_CLASSES,
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
BinarySensorDevice)
from homeassistant.const import STATE_ON
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DOOR: [set_req.V_TRIPPED],
pres.S_MOTION: [set_req.V_TRIPPED],
pres.S_SMOKE: [set_req.V_TRIPPED],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_SPRINKLER: [set_req.V_TRIPPED],
pres.S_WATER_LEAK: [set_req.V_TRIPPED],
pres.S_SOUND: [set_req.V_TRIPPED],
pres.S_VIBRATION: [set_req.V_TRIPPED],
pres.S_MOISTURE: [set_req.V_TRIPPED],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsBinarySensor, add_devices))
"""Setup the mysensors platform for binary sensors."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
add_devices=add_devices)
class MySensorsBinarySensor(
mysensors.MySensorsDeviceEntity, BinarySensorDevice):
mysensors.MySensorsEntity, BinarySensorDevice):
"""Represent the value of a MySensors Binary Sensor child node."""
@property
def is_on(self):
"""Return True if the binary sensor is on."""
if self.value_type in self._values:
return self._values[self.value_type] == STATE_ON
return False
return self._values.get(self.value_type) == STATE_ON
@property
def device_class(self):
@@ -6,13 +6,12 @@ https://home-assistant.io/components/binary_sensor.workday/
"""
import asyncio
import logging
import datetime
from datetime import datetime, timedelta
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, WEEKDAYS
import homeassistant.util.dt as dt_util
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.helpers.config_validation as cv
@@ -39,11 +38,14 @@ CONF_EXCLUDES = 'excludes'
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
DEFAULT_NAME = 'Workday Sensor'
ALLOWED_DAYS = WEEKDAYS + ['holiday']
CONF_OFFSET = 'days_offset'
DEFAULT_OFFSET = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES):
@@ -60,8 +62,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
province = config.get(CONF_PROVINCE)
workdays = config.get(CONF_WORKDAYS)
excludes = config.get(CONF_EXCLUDES)
days_offset = config.get(CONF_OFFSET)
year = datetime.datetime.now().year
year = (datetime.now() + timedelta(days=days_offset)).year
obj_holidays = getattr(holidays, country)(years=year)
if province:
@@ -85,7 +88,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.debug("%s %s", date, name)
add_devices([IsWorkdaySensor(
obj_holidays, workdays, excludes, sensor_name)], True)
obj_holidays, workdays, excludes, days_offset, sensor_name)], True)
def day_to_string(day):
@@ -99,12 +102,13 @@ def day_to_string(day):
class IsWorkdaySensor(BinarySensorDevice):
"""Implementation of a Workday sensor."""
def __init__(self, obj_holidays, workdays, excludes, name):
def __init__(self, obj_holidays, workdays, excludes, days_offset, name):
"""Initialize the Workday sensor."""
self._name = name
self._obj_holidays = obj_holidays
self._workdays = workdays
self._excludes = excludes
self._days_offset = days_offset
self._state = None
@property
@@ -135,6 +139,16 @@ class IsWorkdaySensor(BinarySensorDevice):
return False
@property
def state_attributes(self):
"""Return the attributes of the entity."""
# return self._attributes
return {
CONF_WORKDAYS: self._workdays,
CONF_EXCLUDES: self._excludes,
CONF_OFFSET: self._days_offset
}
@asyncio.coroutine
def async_update(self):
"""Get date and look whether it is a holiday."""
@@ -142,11 +156,12 @@ class IsWorkdaySensor(BinarySensorDevice):
self._state = False
# Get iso day of the week (1 = Monday, 7 = Sunday)
day = datetime.datetime.today().isoweekday() - 1
date = datetime.today() + timedelta(days=self._days_offset)
day = date.isoweekday() - 1
day_of_week = day_to_string(day)
if self.is_include(day_of_week, dt_util.now()):
if self.is_include(day_of_week, date):
self._state = True
if self.is_exclude(day_of_week, dt_util.now()):
if self.is_exclude(day_of_week, date):
self._state = False
+1 -1
View File
@@ -56,7 +56,7 @@ class FoscamCam(Camera):
from foscam import FoscamCamera
self._foscam_session = FoscamCamera(ip_address, port, self._username,
self._password)
self._password, verbose=False)
def camera_image(self):
"""Return a still image reponse from the camera."""
+94
View File
@@ -0,0 +1,94 @@
"""
Support for a camera made up of usps mail images.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera.usps/
"""
from datetime import timedelta
import logging
from homeassistant.components.camera import Camera
from homeassistant.components.usps import DATA_USPS
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['usps']
SCAN_INTERVAL = timedelta(seconds=10)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up USPS mail camera."""
if discovery_info is None:
return
usps = hass.data[DATA_USPS]
add_devices([USPSCamera(usps)])
class USPSCamera(Camera):
"""Representation of the images available from USPS."""
def __init__(self, usps):
"""Initialize the USPS camera images."""
super().__init__()
self._usps = usps
self._name = self._usps.name
self._session = self._usps.session
self._mail_img = []
self._last_mail = None
self._mail_index = 0
self._mail_count = 0
self._timer = None
def camera_image(self):
"""Update the camera's image if it has changed."""
self._usps.update()
try:
self._mail_count = len(self._usps.mail)
except TypeError:
# No mail
return None
if self._usps.mail != self._last_mail:
# Mail items must have changed
self._mail_img = []
if len(self._usps.mail) >= 1:
self._last_mail = self._usps.mail
for article in self._usps.mail:
_LOGGER.debug("Fetching article image: %s", article)
img = self._session.get(article['image']).content
self._mail_img.append(img)
try:
return self._mail_img[self._mail_index]
except IndexError:
return None
@property
def name(self):
"""Return the name of this camera."""
return '{} mail'.format(self._name)
@property
def model(self):
"""Return date of mail as model."""
try:
return 'Date: {}'.format(self._usps.mail[0]['date'])
except IndexError:
return None
@property
def should_poll(self):
"""Update the mail image index periodically."""
return True
def update(self):
"""Update mail image index."""
if self._mail_index < (self._mail_count - 1):
self._mail_index += 1
else:
self._mail_index = 0
+22 -72
View File
@@ -4,15 +4,11 @@ MySensors platform that offers a Climate (MySensors-HVAC) component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_OFF, STATE_AUTO, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
DICT_HA_TO_MYS = {
STATE_AUTO: 'AutoChangeOver',
@@ -29,28 +25,12 @@ DICT_MYS_TO_HA = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the mysensors climate."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
if float(gateway.protocol_version) < 1.5:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_HVAC: [set_req.V_HVAC_FLOW_STATE],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsHVAC, add_devices))
"""Setup the mysensors climate."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices)
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
"""Representation of a MySensors HVAC."""
@property
@@ -84,26 +64,28 @@ 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 float(temp)
return float(temp) if temp is not None else None
@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 float(self._values.get(set_req.V_HVAC_SETPOINT_COOL))
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
return float(temp) if temp is not None else None
@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 float(self._values.get(set_req.V_HVAC_SETPOINT_HEAT))
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return float(temp) if temp is not None else None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_FLOW_STATE)
return self._values.get(self.value_type)
@property
def operation_list(self):
@@ -128,7 +110,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
heat = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
cool = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
updates = ()
updates = []
if temp is not None:
if heat is not None:
# Set HEAT Target temperature
@@ -146,7 +128,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
# optimistically assume that device has changed state
self._values[value_type] = value
self.schedule_update_ha_state()
@@ -156,54 +138,22 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
# optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_FLOW_STATE,
self.node_id, self.child_id, self.value_type,
DICT_HA_TO_MYS[operation_mode])
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
# optimistically assume that device has changed state
self._values[self.value_type] = operation_mode
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
set_req = self.gateway.const.SetReq
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == set_req.V_HVAC_FLOW_STATE:
self._values[value_type] = DICT_MYS_TO_HA[value]
else:
self._values[value_type] = value
def set_humidity(self, humidity):
"""Set new target humidity."""
_LOGGER.error("Service Not Implemented yet")
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_on(self):
"""Turn away mode on."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_off(self):
"""Turn away mode off."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
_LOGGER.error("Service Not Implemented yet")
super().update()
self._values[self.value_type] = DICT_MYS_TO_HA[
self._values[self.value_type]]
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'group', 'hassbian', 'automation')
SECTIONS = ('core', 'group', 'hassbian', 'automation', 'script')
ON_DEMAND = ('zwave')
+19
View File
@@ -0,0 +1,19 @@
"""Provide configuration end points for scripts."""
import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.script import SCRIPT_ENTRY_SCHEMA, async_reload
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'scripts.yaml'
@asyncio.coroutine
def async_setup(hass):
"""Set up the script config API."""
hass.http.register_view(EditKeyBasedConfigView(
'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA,
post_write_hook=async_reload
))
return True
+70 -37
View File
@@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
import asyncio
import functools as ft
import logging
from homeassistant.core import callback as async_callback
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util.async import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
_KEY_INSTANCE = 'configurator'
DATA_REQUESTS = 'configurator_requests'
ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description'
ATTR_DESCRIPTION_IMAGE = 'description_image'
@@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured'
@bind_hass
def request_config(
hass, name, callback, description=None, description_image=None,
@async_callback
def async_request_config(
hass, name, callback=None, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None,
entity_picture=None):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
instance = run_callback_threadsafe(hass.loop,
_async_get_instance,
hass).result()
instance = hass.data.get(_KEY_INSTANCE)
request_id = instance.request_config(
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
request_id = instance.async_request_config(
name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture)
_REQUESTS[request_id] = instance
if DATA_REQUESTS not in hass.data:
hass.data[DATA_REQUESTS] = {}
hass.data[DATA_REQUESTS][request_id] = instance
return request_id
def notify_errors(request_id, error):
@bind_hass
def request_config(hass, *args, **kwargs):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
return run_callback_threadsafe(
hass.loop, ft.partial(async_request_config, hass, *args, **kwargs)
).result()
@bind_hass
@async_callback
def async_notify_errors(hass, request_id, error):
"""Add errors to a config request."""
try:
_REQUESTS[request_id].notify_errors(request_id, error)
hass.data[DATA_REQUESTS][request_id].async_notify_errors(
request_id, error)
except KeyError:
# If request_id does not exist
pass
def request_done(request_id):
@bind_hass
def notify_errors(hass, request_id, error):
"""Add errors to a config request."""
return run_callback_threadsafe(
hass.loop, async_notify_errors, hass, request_id, error
).result()
@bind_hass
@async_callback
def async_request_done(hass, request_id):
"""Mark a configuration request as done."""
try:
_REQUESTS.pop(request_id).request_done(request_id)
hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id)
except KeyError:
# If request_id does not exist
pass
@bind_hass
def request_done(hass, request_id):
"""Mark a configuration request as done."""
return run_callback_threadsafe(
hass.loop, async_request_done, hass, request_id
).result()
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the configurator component."""
return True
@async_callback
def _async_get_instance(hass):
"""Get an instance per hass object."""
instance = hass.data.get(_KEY_INSTANCE)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
return instance
class Configurator(object):
"""The class to keep track of current configuration requests."""
@@ -105,14 +133,16 @@ class Configurator(object):
self._cur_id = 0
self._requests = {}
hass.services.async_register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call)
def request_config(
@async_callback
def async_request_config(
self, name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture):
"""Set up a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None:
fields = []
@@ -138,11 +168,12 @@ class Configurator(object):
] if value is not None
})
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
self.hass.states.async_set(entity_id, STATE_CONFIGURE, data)
return request_id
def notify_errors(self, request_id, error):
@async_callback
def async_notify_errors(self, request_id, error):
"""Update the state with errors."""
if not self._validate_request_id(request_id):
return
@@ -154,9 +185,10 @@ class Configurator(object):
new_data = dict(state.attributes)
new_data[ATTR_ERRORS] = error
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
self.hass.states.async_set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id):
@async_callback
def async_request_done(self, request_id):
"""Remove the configuration request."""
if not self._validate_request_id(request_id):
return
@@ -167,15 +199,16 @@ class Configurator(object):
# the result fo the service call (current design limitation).
# Instead, we will set it to configured to give as feedback but delete
# it shortly after so that it is deleted when the client updates.
self.hass.states.set(entity_id, STATE_CONFIGURED)
self.hass.states.async_set(entity_id, STATE_CONFIGURED)
def deferred_remove(event):
"""Remove the request state."""
self.hass.states.remove(entity_id)
self.hass.states.async_remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call):
@async_callback
def async_handle_service_call(self, call):
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
@@ -186,8 +219,8 @@ class Configurator(object):
entity_id, fields, callback = self._requests[request_id]
# field validation goes here?
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
if callback:
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
"""Generate a unique configurator ID."""
+5 -29
View File
@@ -4,42 +4,18 @@ Support for MySensors covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for covers."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsCover, add_devices))
"""Setup the mysensors platform for covers."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices)
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
"""Representation of the value of a MySensors Cover child node."""
@property
+38 -22
View File
@@ -19,7 +19,7 @@ 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)
CONF_OPTIMISTIC, 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
@@ -39,6 +39,8 @@ CLOSE_ACTION = 'close_cover'
STOP_ACTION = 'stop_cover'
POSITION_ACTION = 'set_cover_position'
TILT_ACTION = 'set_cover_tilt_position'
CONF_TILT_OPTIMISTIC = 'tilt_optimistic'
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
CONF_OPEN_OR_CLOSE = 'open_or_close'
@@ -56,6 +58,8 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
@@ -83,11 +87,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
stop_action = device_config.get(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
optimistic = device_config.get(CONF_OPTIMISTIC)
tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC)
if position_action is None and open_action is None:
_LOGGER.error('Must specify at least one of %s' or '%s',
@@ -125,7 +126,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
device, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids
position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids
)
)
if not covers:
@@ -142,7 +144,8 @@ class CoverTemplate(CoverDevice):
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):
position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids):
"""Initialize the Template cover."""
self.hass = hass
self.entity_id = async_generate_entity_id(
@@ -167,6 +170,9 @@ class CoverTemplate(CoverDevice):
self._tilt_script = None
if tilt_action is not None:
self._tilt_script = Script(hass, tilt_action)
self._optimistic = (optimistic or
(not state_template and not position_template))
self._tilt_optimistic = tilt_optimistic or not tilt_template
self._icon = None
self._position = None
self._tilt_value = None
@@ -260,19 +266,23 @@ class CoverTemplate(CoverDevice):
def async_open_cover(self, **kwargs):
"""Move the cover up."""
if self._open_script:
self.hass.async_add_job(self._open_script.async_run())
yield from self._open_script.async_run()
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 100}))
yield from self._position_script.async_run({"position": 100})
if self._optimistic:
self._position = 100
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down."""
if self._close_script:
self.hass.async_add_job(self._close_script.async_run())
yield from self._close_script.async_run()
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 0}))
yield from self._position_script.async_run({"position": 0})
if self._optimistic:
self._position = 0
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
@@ -284,29 +294,35 @@ class CoverTemplate(CoverDevice):
def async_set_cover_position(self, **kwargs):
"""Set cover position."""
self._position = kwargs[ATTR_POSITION]
self.hass.async_add_job(self._position_script.async_run(
{"position": self._position}))
yield from self._position_script.async_run(
{"position": self._position})
if self._optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@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}))
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@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}))
yield from self._tilt_script.async_run(
{"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
self._tilt_value = kwargs[ATTR_TILT_POSITION]
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
if self._tilt_optimistic:
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_update(self):
@@ -6,28 +6,32 @@ https://home-assistant.io/components/device_tracker.automatic/
"""
import asyncio
from datetime import timedelta
import json
import logging
import os
from aiohttp import web
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
ATTR_GPS, ATTR_GPS_ACCURACY)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.4.0']
REQUIREMENTS = ['aioautomatic==0.6.0']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
CONF_SECRET = 'secret'
CONF_DEVICES = 'devices'
CONF_CURRENT_LOCATION = 'current_location'
DEFAULT_TIMEOUT = 5
@@ -38,38 +42,74 @@ ATTR_FUEL_LEVEL = 'fuel_level'
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json'
DATA_CONFIGURING = 'automatic_configurator_clients'
DATA_REFRESH_TOKEN = 'refresh_token'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None): vol.All(
cv.ensure_list, [cv.string])
})
def _get_refresh_token_from_file(hass, filename):
"""Attempt to load session data from file."""
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
try:
with open(path) as data_file:
data = json.load(data_file)
if data is None:
return None
return data.get(DATA_REFRESH_TOKEN)
except ValueError:
return None
def _write_refresh_token_to_file(hass, filename, refresh_token):
"""Attempt to store session data to file."""
path = hass.config.path(filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w+') as data_file:
json.dump({
DATA_REFRESH_TOKEN: refresh_token
}, data_file)
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Validate the configuration and return an Automatic scanner."""
import aioautomatic
hass.http.register_view(AutomaticAuthCallbackView())
scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE
client = aioautomatic.Client(
client_id=config[CONF_CLIENT_ID],
client_secret=config[CONF_SECRET],
client_session=async_get_clientsession(hass),
request_kwargs={'timeout': DEFAULT_TIMEOUT})
try:
try:
session = yield from client.create_session_from_password(
FULL_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
except aioautomatic.exceptions.ForbiddenError as exc:
if not str(exc).startswith("invalid_scope"):
raise exc
_LOGGER.info("Client not authorized for current_location scope. "
"location:updated events will not be received.")
session = yield from client.create_session_from_password(
DEFAULT_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID])
refresh_token = yield from hass.async_add_job(
_get_refresh_token_from_file, hass, filename)
@asyncio.coroutine
def initialize_data(session):
"""Initialize the AutomaticData object from the created session."""
hass.async_add_job(
_write_refresh_token_to_file, hass, filename,
session.refresh_token)
data = AutomaticData(
hass, client, session, config[CONF_DEVICES], async_see)
@@ -77,26 +117,86 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
vehicles = yield from session.get_vehicles()
for vehicle in vehicles:
hass.async_add_job(data.load_vehicle(vehicle))
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
return False
@callback
def ws_connect(event):
"""Open the websocket connection."""
hass.async_add_job(data.ws_connect())
# Create a task instead of adding a tracking job, since this task will
# run until the websocket connection is closed.
hass.loop.create_task(data.ws_connect())
@callback
def ws_close(event):
"""Close the websocket connection."""
hass.async_add_job(data.ws_close())
if refresh_token is not None:
try:
session = yield from client.create_session_from_refresh_token(
refresh_token)
yield from initialize_data(session)
return True
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close)
configurator = hass.components.configurator
request_id = configurator.async_request_config(
"Automatic", description=(
"Authorization required for Automatic device tracker."),
link_name="Click here to authorize Home Assistant.",
link_url=client.generate_oauth_url(scope),
entity_picture="/static/images/logo_automatic.png",
)
@asyncio.coroutine
def initialize_callback(code, state):
"""Callback after OAuth2 response is returned."""
try:
session = yield from client.create_session_from_oauth_code(
code, state)
yield from initialize_data(session)
configurator.async_request_done(request_id)
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
configurator.async_notify_errors(request_id, str(err))
return False
if DATA_CONFIGURING not in hass.data:
hass.data[DATA_CONFIGURING] = {}
hass.data[DATA_CONFIGURING][client.state] = initialize_callback
return True
class AutomaticAuthCallbackView(HomeAssistantView):
"""Handle OAuth finish callback requests."""
requires_auth = False
url = '/api/automatic/callback'
name = 'api:automatic:callback'
@callback
def get(self, request): # pylint: disable=no-self-use
"""Finish OAuth callback request."""
hass = request.app['hass']
params = request.query
response = web.HTTPFound('/states')
if 'state' not in params or 'code' not in params:
if 'error' in params:
_LOGGER.error(
"Error authorizing Automatic: %s", params['error'])
return response
else:
_LOGGER.error(
"Error authorizing Automatic. Invalid response returned.")
return response
if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]:
_LOGGER.error("Automatic configuration request not found.")
return response
code = params['code']
state = params['state']
initialize_callback = hass.data[DATA_CONFIGURING][state]
hass.async_add_job(initialize_callback(code, state))
return response
class AutomaticData(object):
"""A class representing an Automatic cloud service connection."""
@@ -115,6 +215,8 @@ class AutomaticData(object):
lambda name, event: self.hass.async_add_job(
self.handle_event(name, event)))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
@asyncio.coroutine
def handle_event(self, name, event):
"""Coroutine to update state for a realtime event."""
@@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify
import homeassistant.util.dt as dt_util
from homeassistant.util.location import distance
from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__)
@@ -209,7 +208,7 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator = self.hass.components.configurator
configurator.request_done(request_id)
# Trigger the next step immediately
@@ -217,7 +216,7 @@ class Icloud(DeviceScanner):
def icloud_need_trusted_device(self):
"""We need a trusted device."""
configurator = get_component('configurator')
configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING:
return
@@ -229,7 +228,7 @@ class Icloud(DeviceScanner):
devicesstring += "{}: {};".format(i, devicename)
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
'iCloud {}'.format(self.accountname),
self.icloud_trusted_device_callback,
description=(
'Please choose your trusted device by entering'
@@ -259,17 +258,17 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator = self.hass.components.configurator
configurator.request_done(request_id)
def icloud_need_verification_code(self):
"""Return the verification code."""
configurator = get_component('configurator')
configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING:
return
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
'iCloud {}'.format(self.accountname),
self.icloud_verification_callback,
description=('Please enter the validation code:'),
entity_picture="/static/images/config_icloud.png",
@@ -4,61 +4,51 @@ Support for tracking MySensors devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.helpers.dispatcher import dispatcher_connect
from homeassistant.util import slugify
DEPENDENCIES = ['mysensors']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the MySensors tracker."""
def mysensors_callback(gateway, msg):
"""Set up callback for mysensors platform."""
node = gateway.sensors[msg.node_id]
if node.sketch_name is None:
_LOGGER.debug("No sketch_name: node %s", msg.node_id)
return
"""Set up the MySensors device scanner."""
new_devices = mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsDeviceScanner,
device_args=(see, ))
if not new_devices:
return False
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
child = node.children.get(msg.child_id)
if child is None:
return
position = child.values.get(set_req.V_POSITION)
if child.type != pres.S_GPS or position is None:
return
try:
latitude, longitude, _ = position.split(',')
except ValueError:
_LOGGER.error("Payload for V_POSITION %s is not of format "
"latitude, longitude, altitude", position)
return
name = '{} {} {}'.format(
node.sketch_name, msg.node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: msg.node_id,
}
see(
dev_id=slugify(name),
host_name=name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=attr
)
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
for gateway in gateways:
if float(gateway.protocol_version) < 2.0:
continue
gateway.platform_callbacks.append(mysensors_callback)
for device in new_devices:
dev_id = (
id(device.gateway), device.node_id, device.child_id,
device.value_type)
dispatcher_connect(
hass, mysensors.SIGNAL_CALLBACK.format(*dev_id),
device.update_callback)
return True
class MySensorsDeviceScanner(mysensors.MySensorsDevice):
"""Represent a MySensors scanner."""
def __init__(self, see, *args):
"""Set up instance."""
super().__init__(*args)
self.see = see
def update_callback(self):
"""Update the device."""
self.update()
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
position = child.values[self.value_type]
latitude, longitude, _ = position.split(',')
self.see(
dev_id=slugify(self.name),
host_name=self.name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=self.device_state_attributes
)
+4 -5
View File
@@ -13,10 +13,9 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle
REQUIREMENTS = ['python-ecobee-api==0.0.7']
REQUIREMENTS = ['python-ecobee-api==0.0.8']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(network, hass, config):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'ecobee' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['ecobee'], "Failed to register, please try again.")
@@ -56,7 +55,7 @@ def request_configuration(network, hass, config):
setup_ecobee(hass, network, config)
_CONFIGURING['ecobee'] = configurator.request_config(
hass, "Ecobee", ecobee_configuration_callback,
"Ecobee", ecobee_configuration_callback,
description=(
'Please authorize this app at https://www.ecobee.com/consumer'
'portal/index.html with pin code: ' + network.pin),
@@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config):
return
if 'ecobee' in _CONFIGURING:
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('ecobee'))
hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)
+8 -22
View File
@@ -2,7 +2,6 @@
import threading
import socket
import logging
import os
import select
from aiohttp import web
@@ -86,18 +85,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
advertise_ip, advertise_port).replace("\n", "\r\n") \
.encode('utf-8')
# Set up a pipe for signaling to the receiver that it's time to
# shutdown. Essentially, we place the SSDP socket into nonblocking
# mode and use select() to wait for data to arrive on either the SSDP
# socket or the pipe. If data arrives on either one, select() returns
# and tells us which filenos have data ready to read.
#
# When we want to stop the responder, we write data to the pipe, which
# causes the select() to return and indicate that said pipe has data
# ready to be read, which indicates to us that the responder needs to
# be shutdown.
self._interrupted_read_pipe, self._interrupted_write_pipe = os.pipe()
def run(self):
"""Run the server."""
# Listen for UDP port 1900 packets sent to SSDP multicast address
@@ -119,7 +106,7 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
socket.inet_aton(self.host_ip_addr))
if self.upnp_bind_multicast:
ssdp_socket.bind(("239.255.255.250", 1900))
ssdp_socket.bind(("", 1900))
else:
ssdp_socket.bind((self.host_ip_addr, 1900))
@@ -130,16 +117,13 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
try:
read, _, _ = select.select(
[self._interrupted_read_pipe, ssdp_socket], [],
[ssdp_socket])
[ssdp_socket], [],
[ssdp_socket], 2)
if self._interrupted_read_pipe in read:
# Implies self._interrupted is True
clean_socket_close(ssdp_socket)
return
elif ssdp_socket in read:
if ssdp_socket in read:
data, addr = ssdp_socket.recvfrom(1024)
else:
# most likely the timeout, so check for interupt
continue
except socket.error as ex:
if self._interrupted:
@@ -148,6 +132,9 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
_LOGGER.error("UPNP Responder socket exception occured: %s",
ex.__str__)
# without the following continue, a second exception occurs
# because the data object has not been initialized
continue
if "M-SEARCH" in data.decode('utf-8'):
# SSDP M-SEARCH method received, respond to it with our info
@@ -161,7 +148,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
"""Stop the server."""
# Request for server
self._interrupted = True
os.write(self._interrupted_write_pipe, bytes([0]))
self.join()
+4 -4
View File
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['pyenvisalink==2.1']
REQUIREMENTS = ['pyenvisalink==2.2']
_LOGGER = logging.getLogger(__name__)
@@ -74,9 +74,9 @@ CONFIG_SCHEMA = vol.Schema({
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(
CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int),
}),
}, extra=vol.ALLOW_EXTRA)
@@ -13,7 +13,6 @@ 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 = {}
@@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
'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',
@@ -85,7 +84,7 @@ 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 = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Device configuration done!")
+11 -1
View File
@@ -16,6 +16,11 @@ from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
# Define term used for medium speed. This must be set as the fan component uses
# 'medium' which the ISY does not understand
ISY_SPEED_MEDIUM = 'med'
VALUE_TO_STATE = {
0: SPEED_OFF,
63: SPEED_LOW,
@@ -29,7 +34,7 @@ STATE_TO_VALUE = {}
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH]
STATES = [SPEED_OFF, SPEED_LOW, ISY_SPEED_MEDIUM, SPEED_HIGH]
# pylint: disable=unused-argument
@@ -93,6 +98,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
else:
self.speed = self.state
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
class ISYFanProgram(ISYFanDevice):
"""Representation of an ISY994 fan program."""
+1 -1
View File
@@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['ha-ffmpeg==1.5']
REQUIREMENTS = ['ha-ffmpeg==1.7']
DOMAIN = 'ffmpeg'
+2 -2
View File
@@ -3,10 +3,10 @@
FINGERPRINTS = {
"compatibility.js": "1686167ff210e001f063f5c606b2e74b",
"core.js": "2a7d01e45187c7d4635da05065b5e54e",
"frontend.html": "fb225cfababf965f8e19a8eb5c5a2a7e",
"frontend.html": "6c8192a4393c9e83516dc8177b75c23d",
"mdi.html": "e91f61a039ed0a9936e7ee5360da3870",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "878fd176dad70fe5cb8fc3c4ca72145c",
"panels/ha-panel-config.html": "bd20a3b11b46522e3c705a0b6a72b9dc",
"panels/ha-panel-dev-event.html": "d409e7ab537d9fe629126d122345279c",
"panels/ha-panel-dev-info.html": "b0e55eb657fd75f21aba2426ac0cedc0",
"panels/ha-panel-dev-mqtt.html": "94b222b013a98583842de3e72d5888c6",
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long
@@ -37,7 +37,7 @@
/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren, quotes, comma-spacing */
'use strict';
var precacheConfig = [["/","ac95485aa7eafc661d515253bbba1b6d"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-fb225cfababf965f8e19a8eb5c5a2a7e.html","429b61684b027ffe2a50de3d04dd3db6"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var precacheConfig = [["/","535d629ec4d3936dba0ca4ca84dabeb2"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-6c8192a4393c9e83516dc8177b75c23d.html","56d5bfe9e11a8b81a686f20aeae3c359"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var cacheName = 'sw-precache-v3--' + (self.registration ? self.registration.scope : '');
+6 -7
View File
@@ -45,13 +45,12 @@ def setup(hass, config):
try:
sock.connect((host, port))
sock.shutdown(2)
_LOGGER.debug('Connection to Graphite possible')
_LOGGER.debug("Connection to Graphite possible")
except socket.error:
_LOGGER.error('Not able to connect to Graphite')
_LOGGER.error("Not able to connect to Graphite")
return False
GraphiteFeeder(hass, host, port, prefix)
return True
@@ -143,15 +142,15 @@ class GraphiteFeeder(threading.Thread):
_LOGGER.debug("Processing STATE_CHANGED event for %s",
event.data['entity_id'])
try:
self._report_attributes(event.data['entity_id'],
event.data['new_state'])
self._report_attributes(
event.data['entity_id'], event.data['new_state'])
# pylint: disable=broad-except
except Exception:
# Catch this so we can avoid the thread dying and
# make it visible.
_LOGGER.exception("Failed to process STATE_CHANGED event")
else:
_LOGGER.warning("Processing unexpected event type %s",
event.event_type)
_LOGGER.warning(
"Processing unexpected event type %s", event.event_type)
self._queue.task_done()
@@ -15,7 +15,7 @@ from homeassistant.components.image_processing import (
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity)
REQUIREMENTS = ['face_recognition==0.2.0']
REQUIREMENTS = ['face_recognition==0.2.2']
_LOGGER = logging.getLogger(__name__)
@@ -16,7 +16,7 @@ from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['face_recognition==0.2.0']
REQUIREMENTS = ['face_recognition==0.2.2']
_LOGGER = logging.getLogger(__name__)
+6 -2
View File
@@ -79,8 +79,12 @@ def async_setup(hass, config):
#
# Override the device default capabilities for a specific address
#
plm.protocol.devices.add_override(
device['address'], 'capabilities', [device['platform']])
if isinstance(device['platform'], list):
plm.protocol.devices.add_override(
device['address'], 'capabilities', device['platform'])
else:
plm.protocol.devices.add_override(
device['address'], 'capabilities', [device['platform']])
hass.data['insteon_plm'] = plm
@@ -116,8 +116,8 @@ class DecoraWifiLight(Light):
attribs = {'power': 'ON'}
if ATTR_BRIGHTNESS in kwargs:
min_level = self._switch.get('minLevel', 0)
max_level = self._switch.get('maxLevel', 100)
min_level = self._switch.data.get('minLevel', 0)
max_level = self._switch.data.get('maxLevel', 100)
brightness = int(kwargs[ATTR_BRIGHTNESS] * max_level / 255)
brightness = max(brightness, min_level)
attribs['brightness'] = brightness
+3 -3
View File
@@ -122,7 +122,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if ipaddr in light_ips:
continue
device['name'] = '{} {}'.format(device['id'], ipaddr)
device[ATTR_MODE] = 'rgbw'
device[ATTR_MODE] = MODE_RGBW
device[CONF_PROTOCOL] = None
light = FluxLight(device)
lights.append(light)
@@ -216,9 +216,9 @@ class FluxLight(Light):
elif rgb is not None:
self._bulb.setRgb(*tuple(rgb))
elif brightness is not None:
if self._mode == 'rgbw':
if self._mode == MODE_RGBW:
self._bulb.setWarmWhite255(brightness)
elif self._mode == 'rgb':
elif self._mode == MODE_RGB:
(red, green, blue) = self._bulb.getRgb()
self._bulb.setRgb(red, green, blue, brightness=brightness)
elif effect == EFFECT_RANDOM:
+9 -14
View File
@@ -23,7 +23,6 @@ from homeassistant.components.light import (
SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME)
from homeassistant.loader import get_component
from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE
import homeassistant.helpers.config_validation as cv
@@ -164,9 +163,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
lights = {}
@@ -268,7 +265,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_unreachable, allow_in_emulated_hue,
allow_hue_groups):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@@ -284,7 +281,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_in_emulated_hue, allow_hue_groups)
_CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
"Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."),
entity_picture="/static/images/logo_philips_hue.png",
@@ -384,7 +381,6 @@ class HueLight(Light):
hue, sat = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
command['hue'] = hue
command['sat'] = sat
command['bri'] = self.info['bri']
else:
command['xy'] = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
@@ -399,14 +395,13 @@ class HueLight(Light):
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['xy'] = xyb[0], xyb[1]
command['bri'] = xyb[2]
elif ATTR_COLOR_TEMP in kwargs:
temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
if ATTR_BRIGHTNESS in kwargs:
command['bri'] = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs:
temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
flash = kwargs.get(ATTR_FLASH)
if flash == FLASH_LONG:
@@ -425,9 +420,9 @@ class HueLight(Light):
elif effect == EFFECT_RANDOM:
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.bridge_type == 'hue':
if self.info.get('manufacturername') != "OSRAM":
command['effect'] = 'none'
elif (self.bridge_type == 'hue' and
self.info.get('manufacturername') == 'Philips'):
command['effect'] = 'none'
self._command_func(self.light_id, command)
@@ -11,7 +11,6 @@ from datetime import timedelta
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
@@ -54,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@@ -69,7 +68,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
'Insteon ' + model + ' addr: ' + device_id,
insteon_light_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
@@ -82,7 +81,7 @@ def setup_light(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the light."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.debug("Device configuration done")
+31 -22
View File
@@ -325,29 +325,33 @@ class LIFXManager(object):
entity = self.entities[device.mac_addr]
entity.registered = True
_LOGGER.debug("%s register AGAIN", entity.who)
yield from entity.async_update()
yield from entity.async_update_ha_state()
yield from entity.update_hass()
else:
_LOGGER.debug("%s register NEW", device.ip_addr)
device.timeout = MESSAGE_TIMEOUT
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
# Read initial state
ack = AwaitAioLIFX().wait
yield from ack(device.get_version)
yield from ack(device.get_color)
version_resp = yield from ack(device.get_version)
if version_resp:
color_resp = yield from ack(device.get_color)
if lifxwhite(device):
entity = LIFXWhite(device, self.effects_conductor)
elif lifxmultizone(device):
yield from ack(partial(device.get_color_zones, start_index=0))
entity = LIFXStrip(device, self.effects_conductor)
if version_resp is None or color_resp is None:
_LOGGER.error("Failed to initialize %s", device.ip_addr)
else:
entity = LIFXColor(device, self.effects_conductor)
device.timeout = MESSAGE_TIMEOUT
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
self.async_add_devices([entity])
if lifxwhite(device):
entity = LIFXWhite(device, self.effects_conductor)
elif lifxmultizone(device):
entity = LIFXStrip(device, self.effects_conductor)
else:
entity = LIFXColor(device, self.effects_conductor)
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
self.async_add_devices([entity], True)
@callback
def unregister(self, device):
@@ -674,9 +678,14 @@ class LIFXStrip(LIFXColor):
@asyncio.coroutine
def update_color_zones(self):
"""Get updated color information for each zone."""
ack = AwaitAioLIFX().wait
bulb = self.device
# Each get_color_zones returns the next 8 zones
for zone in range(0, len(bulb.color_zones), 8):
yield from ack(partial(bulb.get_color_zones, start_index=zone))
zone = 0
top = 1
while self.available and zone < top:
# Each get_color_zones can update 8 zones at once
resp = yield from AwaitAioLIFX().wait(partial(
self.device.get_color_zones,
start_index=zone,
end_index=zone+7))
if resp:
zone += 8
top = resp.count
+31 -141
View File
@@ -4,64 +4,35 @@ Support for MySensors lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_WHITE_VALUE,
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_WHITE_VALUE, DOMAIN,
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_WHITE_VALUE, Light)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import rgb_hex_to_rgb_list
_LOGGER = logging.getLogger(__name__)
ATTR_VALUE = 'value'
ATTR_VALUE_TYPE = 'value_type'
SUPPORT_MYSENSORS = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR |
SUPPORT_WHITE_VALUE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for lights."""
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DIMMER: [set_req.V_DIMMER],
}
device_class_map = {
pres.S_DIMMER: MySensorsLightDimmer,
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_RGB_LIGHT: [set_req.V_RGB],
pres.S_RGBW_LIGHT: [set_req.V_RGBW],
})
map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE)
device_class_map.update({
pres.S_RGB_LIGHT: MySensorsLightRGB,
pres.S_RGBW_LIGHT: MySensorsLightRGBW,
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, device_class_map, add_devices))
"""Setup the mysensors platform for lights."""
device_class_map = {
'S_DIMMER': MySensorsLightDimmer,
'S_RGB_LIGHT': MySensorsLightRGB,
'S_RGBW_LIGHT': MySensorsLightRGBW,
}
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, device_class_map,
add_devices=add_devices)
class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
class MySensorsLight(mysensors.MySensorsEntity, Light):
"""Representation of a MySensors Light child node."""
def __init__(self, *args):
"""Initialize a MySensors Light."""
mysensors.MySensorsDeviceEntity.__init__(self, *args)
super().__init__(*args)
self._state = None
self._brightness = None
self._rgb = None
@@ -101,7 +72,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
"""Turn on light child device."""
set_req = self.gateway.const.SetReq
if self._state or set_req.V_LIGHT not in self._values:
if self._state:
return
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_LIGHT, 1)
@@ -110,7 +81,6 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._state = True
self._values[set_req.V_LIGHT] = STATE_ON
self.schedule_update_ha_state()
def _turn_on_dimmer(self, **kwargs):
"""Turn on dimmer child device."""
@@ -130,7 +100,6 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._brightness = brightness
self._values[set_req.V_DIMMER] = percent
self.schedule_update_ha_state()
def _turn_on_rgb_and_w(self, hex_template, **kwargs):
"""Turn on RGB or RGBW child device."""
@@ -144,16 +113,11 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
return
if new_rgb is not None:
rgb = list(new_rgb)
if rgb is None:
return
if hex_template == '%02x%02x%02x%02x':
if new_white is not None:
rgb.append(new_white)
elif white is not None:
rgb.append(white)
else:
_LOGGER.error("White value is not updated for RGBW light")
return
rgb.append(white)
hex_color = hex_template % tuple(rgb)
if len(rgb) > 3:
white = rgb.pop()
@@ -164,104 +128,40 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):
# optimistically assume that light has changed state
self._rgb = rgb
self._white = white
if hex_color:
self._values[self.value_type] = hex_color
self.schedule_update_ha_state()
self._values[self.value_type] = hex_color
def _turn_off_light(self, value_type=None, value=None):
"""Turn off light child device."""
set_req = self.gateway.const.SetReq
value_type = (
set_req.V_LIGHT
if set_req.V_LIGHT in self._values else value_type)
value = 0 if set_req.V_LIGHT in self._values else value
return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
def _turn_off_dimmer(self, value_type=None, value=None):
"""Turn off dimmer child device."""
set_req = self.gateway.const.SetReq
value_type = (
set_req.V_DIMMER
if set_req.V_DIMMER in self._values else value_type)
value = 0 if set_req.V_DIMMER in self._values else value
return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
def _turn_off_rgb_or_w(self, value_type=None, value=None):
"""Turn off RGB or RGBW child device."""
if float(self.gateway.protocol_version) >= 1.5:
set_req = self.gateway.const.SetReq
if self.value_type == set_req.V_RGB:
value = '000000'
elif self.value_type == set_req.V_RGBW:
value = '00000000'
return {ATTR_VALUE_TYPE: self.value_type, ATTR_VALUE: value}
def _turn_off_main(self, value_type=None, value=None):
def turn_off(self):
"""Turn the device off."""
set_req = self.gateway.const.SetReq
if value_type is None or value is None:
_LOGGER.warning(
"%s: value_type %s, value = %s, None is not valid argument "
"when setting child value", self._name, value_type, value)
return
value_type = self.gateway.const.SetReq.V_LIGHT
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
self.node_id, self.child_id, value_type, 0)
if self.gateway.optimistic:
# optimistically assume that light has changed state
self._state = False
self._values[value_type] = (
STATE_OFF if set_req.V_LIGHT in self._values else value)
self._values[value_type] = STATE_OFF
self.schedule_update_ha_state()
def _update_light(self):
"""Update the controller with values from light child."""
value_type = self.gateway.const.SetReq.V_LIGHT
if value_type in self._values:
self._values[value_type] = (
STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF)
self._state = self._values[value_type] == STATE_ON
self._state = self._values[value_type] == STATE_ON
def _update_dimmer(self):
"""Update the controller with values from dimmer child."""
set_req = self.gateway.const.SetReq
value_type = set_req.V_DIMMER
value_type = self.gateway.const.SetReq.V_DIMMER
if value_type in self._values:
self._brightness = round(255 * int(self._values[value_type]) / 100)
if self._brightness == 0:
self._state = False
if set_req.V_LIGHT not in self._values:
self._state = self._brightness > 0
def _update_rgb_or_w(self):
"""Update the controller with values from RGB or RGBW child."""
set_req = self.gateway.const.SetReq
value = self._values[self.value_type]
if len(value) != 6 and len(value) != 8:
_LOGGER.error(
"Wrong value %s for %s", value, set_req(self.value_type).name)
return
color_list = rgb_hex_to_rgb_list(value)
if set_req.V_LIGHT not in self._values and \
set_req.V_DIMMER not in self._values:
self._state = max(color_list) > 0
if len(color_list) > 3:
if set_req.V_RGBW != self.value_type:
_LOGGER.error(
"Wrong value %s for %s",
value, set_req(self.value_type).name)
return
self._white = color_list.pop()
self._rgb = color_list
def _update_main(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
self._values[value_type] = value
class MySensorsLightDimmer(MySensorsLight):
"""Dimmer child class to MySensorsLight."""
@@ -270,18 +170,12 @@ class MySensorsLightDimmer(MySensorsLight):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_dimmer()
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
if self.gateway.optimistic:
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
super().update()
self._update_light()
self._update_dimmer()
@@ -294,20 +188,12 @@ class MySensorsLightRGB(MySensorsLight):
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x', **kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_rgb_or_w()
ret = self._turn_off_dimmer(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
if self.gateway.optimistic:
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
super().update()
self._update_light()
self._update_dimmer()
self._update_rgb_or_w()
@@ -316,8 +202,12 @@ class MySensorsLightRGB(MySensorsLight):
class MySensorsLightRGBW(MySensorsLightRGB):
"""RGBW child class to MySensorsLightRGB."""
# pylint: disable=too-many-ancestors
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs)
if self.gateway.optimistic:
self.schedule_update_ha_state()
@@ -14,7 +14,7 @@ from homeassistant.components.light import (
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pwmled==1.1.1']
REQUIREMENTS = ['pwmled==1.2.1']
_LOGGER = logging.getLogger(__name__)
+40 -7
View File
@@ -5,14 +5,17 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.tplink/
"""
import logging
import colorsys
from homeassistant.const import (CONF_HOST, CONF_NAME)
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP)
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_RGB_COLOR,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR)
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
from homeassistant.util.color import \
color_temperature_kelvin_to_mired as kelvin_to_mired
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired)
from typing import Tuple
REQUIREMENTS = ['pyHS100==0.2.4.2']
@@ -39,10 +42,26 @@ def brightness_from_percentage(percent):
return (percent*255.0)/100.0
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def rgb_to_hsv(rgb: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert RGB tuple (values 0-255) to HSV (degrees, %, %)."""
hue, sat, value = colorsys.rgb_to_hsv(rgb[0]/255, rgb[1]/255, rgb[2]/255)
return int(hue * 360), int(sat * 100), int(value * 100)
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def hsv_to_rgb(hsv: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert HSV tuple (degrees, %, %) to RGB (values 0-255)."""
red, green, blue = colorsys.hsv_to_rgb(hsv[0]/360, hsv[1]/100, hsv[2]/100)
return int(red * 255), int(green * 255), int(blue * 255)
class TPLinkSmartBulb(Light):
"""Representation of a TPLink Smart Bulb."""
def __init__(self, smartbulb, name):
def __init__(self, smartbulb: 'SmartBulb', name):
"""Initialize the bulb."""
self.smartbulb = smartbulb
@@ -55,6 +74,7 @@ class TPLinkSmartBulb(Light):
self._state = None
self._color_temp = None
self._brightness = None
self._rgb = None
_LOGGER.debug("Setting up TP-Link Smart Bulb")
@property
@@ -64,6 +84,8 @@ class TPLinkSmartBulb(Light):
def turn_on(self, **kwargs):
"""Turn the light on."""
self.smartbulb.state = self.smartbulb.BULB_STATE_ON
if ATTR_COLOR_TEMP in kwargs:
self.smartbulb.color_temp = \
mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])
@@ -72,7 +94,9 @@ class TPLinkSmartBulb(Light):
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
self.smartbulb.brightness = brightness_to_percentage(brightness)
self.smartbulb.state = self.smartbulb.BULB_STATE_ON
if ATTR_RGB_COLOR in kwargs:
rgb = kwargs.get(ATTR_RGB_COLOR)
self.smartbulb.hsv = rgb_to_hsv(rgb)
def turn_off(self):
"""Turn the light off."""
@@ -88,6 +112,11 @@ class TPLinkSmartBulb(Light):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def rgb_color(self):
"""Return the color in RGB."""
return self._rgb
@property
def is_on(self):
"""True if device is on."""
@@ -106,10 +135,14 @@ class TPLinkSmartBulb(Light):
self.smartbulb.color_temp != 0):
self._color_temp = kelvin_to_mired(
self.smartbulb.color_temp)
self._rgb = hsv_to_rgb(self.smartbulb.hsv)
except (SmartPlugException, OSError) as ex:
_LOGGER.warning('Could not read state for %s: %s', self.name, ex)
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_TPLINK
supported_features = SUPPORT_TPLINK
if self.smartbulb.is_color:
supported_features += SUPPORT_RGB_COLOR
return supported_features
+14 -5
View File
@@ -6,6 +6,7 @@ https://home-assistant.io/components/light.yeelight/
"""
import logging
import colorsys
from typing import Tuple
import voluptuous as vol
@@ -89,6 +90,14 @@ YEELIGHT_EFFECT_LIST = [
EFFECT_STOP]
# Travis-CI runs too old astroid https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def hsv_to_rgb(hsv: Tuple[float, float, float]) -> Tuple[int, int, int]:
"""Convert HSV tuple (degrees, %, %) to RGB (values 0-255)."""
red, green, blue = colorsys.hsv_to_rgb(hsv[0]/360, hsv[1]/100, hsv[2]/100)
return int(red * 255), int(green * 255), int(blue * 255)
def _cmd(func):
"""Define a wrapper to catch exceptions from the bulb."""
def _wrap(self, *args, **kwargs):
@@ -192,10 +201,10 @@ class YeelightLight(Light):
if color_mode == 2: # color temperature
return color_temperature_to_rgb(self.color_temp)
if color_mode == 3: # hsv
hue = self._properties.get('hue')
sat = self._properties.get('sat')
val = self._properties.get('bright')
return colorsys.hsv_to_rgb(hue, sat, val)
hue = int(self._properties.get('hue'))
sat = int(self._properties.get('sat'))
val = int(self._properties.get('bright'))
return hsv_to_rgb((hue, sat, val))
rgb = int(rgb)
blue = rgb & 0xff
@@ -214,7 +223,7 @@ class YeelightLight(Light):
return self._bulb.last_properties
@property
def _bulb(self) -> object:
def _bulb(self) -> 'yeelight.Bulb':
import yeelight
if self._bulb_device is None:
try:
+99
View File
@@ -0,0 +1,99 @@
"""
Nello.io lock platform.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/lock.nello/
"""
from itertools import filterfalse
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.lock import (LockDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME)
REQUIREMENTS = ['pynello==1.5']
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
ATTR_LOCATION_ID = 'location_id'
EVENT_DOOR_BELL = 'nello_bell_ring'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nello lock platform."""
from pynello import Nello
nello = Nello(config.get(CONF_USERNAME), config.get(CONF_PASSWORD))
add_devices([NelloLock(lock) for lock in nello.locations], True)
class NelloLock(LockDevice):
"""Representation of a Nello lock."""
def __init__(self, nello_lock):
"""Initialize the lock."""
self._nello_lock = nello_lock
self._device_attrs = None
self._activity = None
self._name = None
@property
def name(self):
"""Return the name of the lock."""
return self._name
@property
def is_locked(self):
"""Return true if lock is locked."""
return True
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return self._device_attrs
def update(self):
"""Update the nello lock properties."""
self._nello_lock.update()
# Location identifiers
location_id = self._nello_lock.location_id
short_id = self._nello_lock.short_id
address = self._nello_lock.address
self._name = 'Nello {}'.format(short_id)
self._device_attrs = {
ATTR_ADDRESS: address,
ATTR_LOCATION_ID: location_id
}
# Process recent activity
activity = self._nello_lock.activity
if self._activity:
# Filter out old events
new_activity = list(
filterfalse(lambda x: x in self._activity, activity))
if new_activity:
for act in new_activity:
activity_type = act.get('type')
if activity_type == 'bell.ring.denied':
event_data = {
'address': address,
'date': act.get('date'),
'description': act.get('description'),
'location_id': location_id,
'short_id': short_id
}
self.hass.bus.fire(EVENT_DOOR_BELL, event_data)
# Save the activity history so that we don't trigger an event twice
self._activity = activity
def unlock(self, **kwargs):
"""Unlock the device."""
if not self._nello_lock.open_door():
_LOGGER.error("Failed to unlock")
+14 -15
View File
@@ -15,7 +15,7 @@ from homeassistant.components.media_player import (
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['youtube_dl==2017.8.6']
REQUIREMENTS = ['youtube_dl==2017.8.18']
_LOGGER = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ def setup(hass, config):
'media_player', 'services.yaml'))
def play_media(call):
"""Get stream URL and send it to the media_player.play_media."""
"""Get stream URL and send it to the play_media service."""
MediaExtractor(hass, config[DOMAIN], call.data).extract_and_send()
hass.services.register(DOMAIN,
@@ -66,7 +66,7 @@ class MEQueryException(Exception):
pass
class MediaExtractor:
class MediaExtractor(object):
"""Class which encapsulates all extraction logic."""
def __init__(self, hass, component_config, call_data):
@@ -107,15 +107,14 @@ class MediaExtractor:
ydl = YoutubeDL({'quiet': True, 'logger': _LOGGER})
try:
all_media = ydl.extract_info(self.get_media_url(),
process=False)
all_media = ydl.extract_info(self.get_media_url(), process=False)
except DownloadError:
# This exception will be logged by youtube-dl itself
raise MEDownloadException()
if 'entries' in all_media:
_LOGGER.warning("Playlists are not supported, "
"looking for the first video")
_LOGGER.warning(
"Playlists are not supported, looking for the first video")
entries = list(all_media['entries'])
if len(entries) > 0:
selected_media = entries[0]
@@ -126,14 +125,14 @@ class MediaExtractor:
selected_media = all_media
def stream_selector(query):
"""Find stream url that matches query."""
"""Find stream URL that matches query."""
try:
ydl.params['format'] = query
requested_stream = ydl.process_ie_result(selected_media,
download=False)
requested_stream = ydl.process_ie_result(
selected_media, download=False)
except (ExtractorError, DownloadError):
_LOGGER.error("Could not extract stream for the query: %s",
query)
_LOGGER.error(
"Could not extract stream for the query: %s", query)
raise MEQueryException()
return requested_stream['url']
@@ -141,7 +140,7 @@ class MediaExtractor:
return stream_selector
def call_media_player_service(self, stream_selector, entity_id):
"""Call media_player.play_media service."""
"""Call Media player play_media service."""
stream_query = self.get_stream_query_for_entity(entity_id)
try:
@@ -164,8 +163,8 @@ class MediaExtractor:
def get_stream_query_for_entity(self, entity_id):
"""Get stream format query for entity."""
default_stream_query = self.config.get(CONF_DEFAULT_STREAM_QUERY,
DEFAULT_STREAM_QUERY)
default_stream_query = self.config.get(
CONF_DEFAULT_STREAM_QUERY, DEFAULT_STREAM_QUERY)
if entity_id:
media_content_type = self.call_data.get(ATTR_MEDIA_CONTENT_TYPE)
@@ -11,7 +11,6 @@ import re
import voluptuous as vol
from homeassistant.loader import get_component
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
@@ -132,7 +131,7 @@ def setup_bravia(config, pin, hass, add_devices):
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done")
@@ -150,7 +149,7 @@ def request_configuration(config, hass, add_devices):
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@@ -171,7 +170,7 @@ def request_configuration(config, hass, add_devices):
request_configuration(config, hass, add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, name, bravia_configuration_callback,
name, bravia_configuration_callback,
description='Enter the Pin shown on your Sony Bravia TV.' +
'If no Pin is shown, enter 0000 to let TV show you a Pin.',
description_image="/static/images/smart-tv.png",
@@ -18,7 +18,6 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['websocket-client==0.37.0']
@@ -48,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def request_configuration(hass, config, url, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'gpmdp' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['gpmdp'], "Failed to register, please try again.")
@@ -96,7 +95,7 @@ def request_configuration(hass, config, url, add_devices_callback):
break
_CONFIGURING['gpmdp'] = configurator.request_config(
hass, DEFAULT_NAME, gpmdp_configuration_callback,
DEFAULT_NAME, gpmdp_configuration_callback,
description=(
'Enter the pin that is displayed in the '
'Google Play Music Desktop Player.'),
@@ -117,7 +116,7 @@ def setup_gpmdp(hass, config, code, add_devices):
return
if 'gpmdp' in _CONFIGURING:
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('gpmdp'))
add_devices([GPMDP(name, url, code)], True)
+3 -2
View File
@@ -79,14 +79,15 @@ class MpdDevice(MediaPlayerDevice):
self._client = mpd.MPDClient()
self._client.timeout = 5
self._client.idletimeout = None
if password is not None:
self._client.password(password)
def _connect(self):
"""Connect to MPD."""
import mpd
try:
self._client.connect(self.server, self.port)
if self.password is not None:
self._client.password(self.password)
except mpd.ConnectionError:
return
@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
from homeassistant.const import (STATE_OFF, STATE_ON, CONF_HOST, CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['onkyo-eiscp==1.1']
REQUIREMENTS = ['onkyo-eiscp==1.2.4']
_LOGGER = logging.getLogger(__name__)
@@ -23,7 +23,6 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.loader import get_component
REQUIREMENTS = ['plexapi==2.0.2']
@@ -143,7 +142,7 @@ def setup_plexserver(
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done")
@@ -236,7 +235,7 @@ def setup_plexserver(
def request_configuration(host, hass, config, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host],
@@ -254,7 +253,6 @@ def request_configuration(host, hass, config, add_devices_callback):
)
_CONFIGURING[host] = configurator.request_config(
hass,
'Plex Media Server',
plex_configuration_callback,
description=('Enter the X-Plex-Token'),
@@ -20,7 +20,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['snapcast==2.0.6']
REQUIREMENTS = ['snapcast==2.0.7']
_LOGGER = logging.getLogger(__name__)
@@ -10,7 +10,6 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.loader import get_component
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
@@ -62,9 +61,9 @@ SCAN_INTERVAL = timedelta(seconds=30)
def request_configuration(hass, config, add_devices, oauth):
"""Request Spotify authorization."""
configurator = get_component('configurator')
configurator = hass.components.configurator
hass.data[DOMAIN] = configurator.request_config(
hass, DEFAULT_NAME, lambda _: None,
DEFAULT_NAME, lambda _: None,
link_name=CONFIGURATOR_LINK_NAME,
link_url=oauth.get_authorize_url(),
description=CONFIGURATOR_DESCRIPTION,
@@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
request_configuration(hass, config, add_devices, oauth)
return
if hass.data.get(DOMAIN):
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(hass.data.get(DOMAIN))
del hass.data[DOMAIN]
player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME),
@@ -186,7 +185,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice):
self._artist = ', '.join([artist.get('name')
for artist in item.get('artists')])
self._uri = current.get('uri')
self._image_url = item.get('album').get('images')[0].get('url')
images = item.get('album').get('images')
self._image_url = images[0].get('url') if images else None
# Playing state
self._state = STATE_PAUSED
if current.get('is_playing'):
@@ -95,7 +95,8 @@ class LogitechMediaServer(object):
"""Create a list of devices connected to LMS."""
result = []
data = yield from self.async_query('players', 'status')
if data is False:
return result
for players in data.get('players_loop', []):
player = SqueezeBoxDevice(
self, players['playerid'], players['name'])
@@ -19,10 +19,9 @@ from homeassistant.components.media_player import (
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF,
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, CONF_TIMEOUT, STATE_OFF,
STATE_PLAYING, STATE_PAUSED,
STATE_UNKNOWN, CONF_NAME, CONF_FILENAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pylgtv==0.1.7',
@@ -56,7 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_MAC): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA,
vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string
vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string,
vol.Optional(CONF_TIMEOUT, default=10): cv.positive_int,
})
@@ -79,17 +79,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
mac = config.get(CONF_MAC)
name = config.get(CONF_NAME)
customize = config.get(CONF_CUSTOMIZE)
timeout = config.get(CONF_TIMEOUT)
config = hass.config.path(config.get(CONF_FILENAME))
setup_tv(host, mac, name, customize, config, hass, add_devices)
setup_tv(host, mac, name, customize, config, timeout, hass, add_devices)
def setup_tv(host, mac, name, customize, config, hass, add_devices):
def setup_tv(host, mac, name, customize, config, timeout, hass, add_devices):
"""Set up a LG WebOS TV based on host parameter."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
from websockets.exceptions import ConnectionClosed
client = WebOsClient(host, config)
client = WebOsClient(host, config, timeout)
if not client.is_registered():
if host in _CONFIGURING:
@@ -100,30 +101,30 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices):
_LOGGER.warning(
"Connected to LG webOS TV %s but not paired", host)
return
except (OSError, ConnectionClosed, TypeError,
asyncio.TimeoutError):
except (OSError, ConnectionClosed, asyncio.TimeoutError):
_LOGGER.error("Unable to connect to host %s", host)
return
else:
# Not registered, request configuration.
_LOGGER.warning("LG webOS TV %s needs to be paired", host)
request_configuration(
host, mac, name, customize, config, hass, add_devices)
host, mac, name, customize, config, timeout, hass, add_devices)
return
# If we came here and configuring this host, mark as done.
if client.is_registered() and host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
add_devices([LgWebOSDevice(host, mac, name, customize, config)], True)
add_devices([LgWebOSDevice(host, mac, name, customize, config, timeout)],
True)
def request_configuration(
host, mac, name, customize, config, hass, add_devices):
host, mac, name, customize, config, timeout, hass, add_devices):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
@@ -133,11 +134,12 @@ def request_configuration(
# pylint: disable=unused-argument
def lgtv_configuration_callback(data):
"""Handle configuration changes."""
setup_tv(host, mac, name, customize, config, hass, add_devices)
"""The actions to do when our configuration callback is called."""
setup_tv(host, mac, name, customize, config, timeout, hass,
add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, name, lgtv_configuration_callback,
name, lgtv_configuration_callback,
description='Click start and accept the pairing request on your TV.',
description_image='/static/images/config_webos.png',
submit_caption='Start pairing request'
@@ -147,11 +149,11 @@ def request_configuration(
class LgWebOSDevice(MediaPlayerDevice):
"""Representation of a LG WebOS TV."""
def __init__(self, host, mac, name, customize, config):
def __init__(self, host, mac, name, customize, config, timeout):
"""Initialize the webos device."""
from pylgtv import WebOsClient
from wakeonlan import wol
self._client = WebOsClient(host, config)
self._client = WebOsClient(host, config, timeout)
self._wol = wol
self._mac = mac
self._customize = customize
+340 -140
View File
@@ -4,30 +4,37 @@ Connect to a MySensors gateway via pymysensors API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import asyncio
from collections import defaultdict
import logging
import os
import socket
import sys
from timeit import default_timer as timer
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.setup import setup_component
from homeassistant.components.mqtt import (
valid_publish_topic, valid_subscribe_topic)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, CONF_NAME, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
from homeassistant.setup import setup_component
REQUIREMENTS = ['pymysensors==0.10.0']
REQUIREMENTS = ['pymysensors==0.11.0']
_LOGGER = logging.getLogger(__name__)
ATTR_CHILD_ID = 'child_id'
ATTR_DESCRIPTION = 'description'
ATTR_DEVICE = 'device'
ATTR_DEVICES = 'devices'
ATTR_NODE_ID = 'node_id'
CONF_BAUD_RATE = 'baud_rate'
@@ -44,11 +51,16 @@ CONF_VERSION = 'version'
DEFAULT_BAUD_RATE = 115200
DEFAULT_TCP_PORT = 5003
DEFAULT_VERSION = 1.4
DEFAULT_VERSION = '1.4'
DOMAIN = 'mysensors'
MQTT_COMPONENT = 'mqtt'
MYSENSORS_GATEWAYS = 'mysensors_gateways'
MYSENSORS_PLATFORM_DEVICES = 'mysensors_devices_{}'
PLATFORM = 'platform'
SCHEMA = 'schema'
SIGNAL_CALLBACK = 'mysensors_callback_{}_{}_{}_{}'
TYPE = 'type'
def is_socket_address(value):
@@ -144,11 +156,127 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean,
vol.Optional(CONF_RETAIN, default=True): cv.boolean,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.Coerce(float),
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
}))
}, extra=vol.ALLOW_EXTRA)
# mysensors const schemas
BINARY_SENSOR_SCHEMA = {PLATFORM: 'binary_sensor', TYPE: 'V_TRIPPED'}
CLIMATE_SCHEMA = {PLATFORM: 'climate', TYPE: 'V_HVAC_FLOW_STATE'}
LIGHT_DIMMER_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_DIMMER',
SCHEMA: {'V_DIMMER': cv.string, 'V_LIGHT': cv.string}}
LIGHT_PERCENTAGE_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_PERCENTAGE',
SCHEMA: {'V_PERCENTAGE': cv.string, 'V_STATUS': cv.string}}
LIGHT_RGB_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_RGB', SCHEMA: {
'V_RGB': cv.string, 'V_STATUS': cv.string}}
LIGHT_RGBW_SCHEMA = {
PLATFORM: 'light', TYPE: 'V_RGBW', SCHEMA: {
'V_RGBW': cv.string, 'V_STATUS': cv.string}}
NOTIFY_SCHEMA = {PLATFORM: 'notify', TYPE: 'V_TEXT'}
DEVICE_TRACKER_SCHEMA = {PLATFORM: 'device_tracker', TYPE: 'V_POSITION'}
DUST_SCHEMA = [
{PLATFORM: 'sensor', TYPE: 'V_DUST_LEVEL'},
{PLATFORM: 'sensor', TYPE: 'V_LEVEL'}]
SWITCH_LIGHT_SCHEMA = {PLATFORM: 'switch', TYPE: 'V_LIGHT'}
SWITCH_STATUS_SCHEMA = {PLATFORM: 'switch', TYPE: 'V_STATUS'}
MYSENSORS_CONST_SCHEMA = {
'S_DOOR': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_MOTION': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SMOKE': [BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SPRINKLER': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_STATUS'}],
'S_WATER_LEAK': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_SOUND': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_VIBRATION': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_MOISTURE': [
BINARY_SENSOR_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_LEVEL'},
{PLATFORM: 'switch', TYPE: 'V_ARMED'}],
'S_HVAC': [CLIMATE_SCHEMA],
'S_COVER': [
{PLATFORM: 'cover', TYPE: 'V_DIMMER'},
{PLATFORM: 'cover', TYPE: 'V_PERCENTAGE'},
{PLATFORM: 'cover', TYPE: 'V_LIGHT'},
{PLATFORM: 'cover', TYPE: 'V_STATUS'}],
'S_DIMMER': [LIGHT_DIMMER_SCHEMA, LIGHT_PERCENTAGE_SCHEMA],
'S_RGB_LIGHT': [LIGHT_RGB_SCHEMA],
'S_RGBW_LIGHT': [LIGHT_RGBW_SCHEMA],
'S_INFO': [NOTIFY_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_TEXT'}],
'S_GPS': [
DEVICE_TRACKER_SCHEMA, {PLATFORM: 'sensor', TYPE: 'V_POSITION'}],
'S_TEMP': [{PLATFORM: 'sensor', TYPE: 'V_TEMP'}],
'S_HUM': [{PLATFORM: 'sensor', TYPE: 'V_HUM'}],
'S_BARO': [
{PLATFORM: 'sensor', TYPE: 'V_PRESSURE'},
{PLATFORM: 'sensor', TYPE: 'V_FORECAST'}],
'S_WIND': [
{PLATFORM: 'sensor', TYPE: 'V_WIND'},
{PLATFORM: 'sensor', TYPE: 'V_GUST'},
{PLATFORM: 'sensor', TYPE: 'V_DIRECTION'}],
'S_RAIN': [
{PLATFORM: 'sensor', TYPE: 'V_RAIN'},
{PLATFORM: 'sensor', TYPE: 'V_RAINRATE'}],
'S_UV': [{PLATFORM: 'sensor', TYPE: 'V_UV'}],
'S_WEIGHT': [
{PLATFORM: 'sensor', TYPE: 'V_WEIGHT'},
{PLATFORM: 'sensor', TYPE: 'V_IMPEDANCE'}],
'S_POWER': [
{PLATFORM: 'sensor', TYPE: 'V_WATT'},
{PLATFORM: 'sensor', TYPE: 'V_KWH'},
{PLATFORM: 'sensor', TYPE: 'V_VAR'},
{PLATFORM: 'sensor', TYPE: 'V_VA'},
{PLATFORM: 'sensor', TYPE: 'V_POWER_FACTOR'}],
'S_DISTANCE': [{PLATFORM: 'sensor', TYPE: 'V_DISTANCE'}],
'S_LIGHT_LEVEL': [
{PLATFORM: 'sensor', TYPE: 'V_LIGHT_LEVEL'},
{PLATFORM: 'sensor', TYPE: 'V_LEVEL'}],
'S_IR': [
{PLATFORM: 'sensor', TYPE: 'V_IR_RECEIVE'},
{PLATFORM: 'switch', TYPE: 'V_IR_SEND',
SCHEMA: {'V_IR_SEND': cv.string, 'V_LIGHT': cv.string}}],
'S_WATER': [
{PLATFORM: 'sensor', TYPE: 'V_FLOW'},
{PLATFORM: 'sensor', TYPE: 'V_VOLUME'}],
'S_CUSTOM': [
{PLATFORM: 'sensor', TYPE: 'V_VAR1'},
{PLATFORM: 'sensor', TYPE: 'V_VAR2'},
{PLATFORM: 'sensor', TYPE: 'V_VAR3'},
{PLATFORM: 'sensor', TYPE: 'V_VAR4'},
{PLATFORM: 'sensor', TYPE: 'V_VAR5'},
{PLATFORM: 'sensor', TYPE: 'V_CUSTOM'}],
'S_SCENE_CONTROLLER': [
{PLATFORM: 'sensor', TYPE: 'V_SCENE_ON'},
{PLATFORM: 'sensor', TYPE: 'V_SCENE_OFF'}],
'S_COLOR_SENSOR': [{PLATFORM: 'sensor', TYPE: 'V_RGB'}],
'S_MULTIMETER': [
{PLATFORM: 'sensor', TYPE: 'V_VOLTAGE'},
{PLATFORM: 'sensor', TYPE: 'V_CURRENT'},
{PLATFORM: 'sensor', TYPE: 'V_IMPEDANCE'}],
'S_GAS': [
{PLATFORM: 'sensor', TYPE: 'V_FLOW'},
{PLATFORM: 'sensor', TYPE: 'V_VOLUME'}],
'S_WATER_QUALITY': [
{PLATFORM: 'sensor', TYPE: 'V_TEMP'},
{PLATFORM: 'sensor', TYPE: 'V_PH'},
{PLATFORM: 'sensor', TYPE: 'V_ORP'},
{PLATFORM: 'sensor', TYPE: 'V_EC'},
{PLATFORM: 'switch', TYPE: 'V_STATUS'}],
'S_AIR_QUALITY': DUST_SCHEMA,
'S_DUST': DUST_SCHEMA,
'S_LIGHT': [SWITCH_LIGHT_SCHEMA],
'S_BINARY': [SWITCH_STATUS_SCHEMA],
'S_LOCK': [{PLATFORM: 'switch', TYPE: 'V_LOCK_STATUS'}],
}
def setup(hass, config):
"""Set up the MySensors component."""
import mysensors.mysensors as mysensors
@@ -197,20 +325,14 @@ def setup(hass, config):
# invalid ip address
return
gateway.metric = hass.config.units.is_metric
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC)
gateway = GatewayWrapper(gateway, optimistic, device)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
gateway.optimistic = config[DOMAIN].get(CONF_OPTIMISTIC)
gateway.device = device
gateway.event_callback = gw_callback_factory(hass)
def gw_start(event):
"""Trigger to start of the gateway and any persistence."""
if persistence:
for node_id in gateway.sensors:
node = gateway.sensors[node_id]
for child_id in node.children:
msg = mysensors.Message().modify(
node_id=node_id, child_id=child_id)
gateway.event_callback(msg)
discover_persistent_devices(hass, gateway)
gateway.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
@@ -219,15 +341,8 @@ def setup(hass, config):
return gateway
gateways = hass.data.get(MYSENSORS_GATEWAYS)
if gateways is not None:
_LOGGER.error(
"%s already exists in %s, will not setup %s component",
MYSENSORS_GATEWAYS, hass.data, DOMAIN)
return False
# Setup all devices from config
gateways = []
gateways = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
for index, gway in enumerate(conf_gateways):
@@ -243,7 +358,7 @@ def setup(hass, config):
device, persistence_file, baud_rate, tcp_port, in_prefix,
out_prefix)
if ready_gateway is not None:
gateways.append(ready_gateway)
gateways[id(ready_gateway)] = ready_gateway
if not gateways:
_LOGGER.error(
@@ -252,115 +367,187 @@ def setup(hass, config):
hass.data[MYSENSORS_GATEWAYS] = gateways
for component in ['sensor', 'switch', 'light', 'binary_sensor', 'climate',
'cover']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
discovery.load_platform(
hass, 'device_tracker', DOMAIN, {}, config)
discovery.load_platform(
hass, 'notify', DOMAIN, {CONF_NAME: DOMAIN}, config)
return True
def pf_callback_factory(map_sv_types, devices, entity_class, add_devices=None):
"""Return a new callback for the platform."""
def mysensors_callback(gateway, msg):
"""Run when a message from the gateway arrives."""
if gateway.sensors[msg.node_id].sketch_name is None:
_LOGGER.debug("No sketch_name: node %s", msg.node_id)
return
child = gateway.sensors[msg.node_id].children.get(msg.child_id)
def validate_child(gateway, node_id, child):
"""Validate that a child has the correct values according to schema.
Return a dict of platform with a list of device ids for validated devices.
"""
validated = defaultdict(list)
if not child.values:
_LOGGER.debug(
"No child values for node %s child %s", node_id, child.id)
return validated
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.debug("Node %s is missing sketch name", node_id)
return validated
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
s_name = next(
(member.name for member in pres if member.value == child.type), None)
if s_name not in MYSENSORS_CONST_SCHEMA:
_LOGGER.warning("Child type %s is not supported", s_name)
return validated
child_schemas = MYSENSORS_CONST_SCHEMA[s_name]
def msg(name):
"""Return a message for an invalid schema."""
return "{} requires value_type {}".format(
pres(child.type).name, set_req[name].name)
for schema in child_schemas:
platform = schema[PLATFORM]
v_name = schema[TYPE]
value_type = next(
(member.value for member in set_req if member.name == v_name),
None)
if value_type is None:
continue
_child_schema = child.get_schema(gateway.protocol_version)
vol_schema = _child_schema.extend(
{vol.Required(set_req[key].value, msg=msg(key)):
_child_schema.schema.get(set_req[key].value, val)
for key, val in schema.get(SCHEMA, {v_name: cv.string}).items()},
extra=vol.ALLOW_EXTRA)
try:
vol_schema(child.values)
except vol.Invalid as exc:
level = (logging.WARNING if value_type in child.values
else logging.DEBUG)
_LOGGER.log(
level,
"Invalid values: %s: %s platform: node %s child %s: %s",
child.values, platform, node_id, child.id, exc)
continue
dev_id = id(gateway), node_id, child.id, value_type
validated[platform].append(dev_id)
return validated
def discover_mysensors_platform(hass, platform, new_devices):
"""Discover a mysensors platform."""
discovery.load_platform(
hass, platform, DOMAIN, {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN})
def discover_persistent_devices(hass, gateway):
"""Discover platforms for devices loaded via persistence file."""
new_devices = defaultdict(list)
for node_id in gateway.sensors:
node = gateway.sensors[node_id]
for child in node.children.values():
validated = validate_child(gateway, node_id, child)
for platform, dev_ids in validated.items():
new_devices[platform].extend(dev_ids)
for platform, dev_ids in new_devices.items():
discover_mysensors_platform(hass, platform, dev_ids)
def get_mysensors_devices(hass, domain):
"""Return mysensors devices for a platform."""
if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data:
hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)]
def gw_callback_factory(hass):
"""Return a new callback for the gateway."""
def mysensors_callback(msg):
"""Default callback for a mysensors gateway."""
start = timer()
_LOGGER.debug(
"Node update: node %s child %s", msg.node_id, msg.child_id)
child = msg.gateway.sensors[msg.node_id].children.get(msg.child_id)
if child is None:
_LOGGER.debug(
"Not a child update for node %s", msg.node_id)
return
for value_type in child.values:
key = msg.node_id, child.id, value_type
if child.type not in map_sv_types or \
value_type not in map_sv_types[child.type]:
continue
if key in devices:
if add_devices:
devices[key].schedule_update_ha_state(True)
else:
devices[key].update()
continue
name = '{} {} {}'.format(
gateway.sensors[msg.node_id].sketch_name, msg.node_id,
child.id)
if isinstance(entity_class, dict):
device_class = entity_class[child.type]
else:
device_class = entity_class
devices[key] = device_class(
gateway, msg.node_id, child.id, name, value_type)
if add_devices:
_LOGGER.info("Adding new devices: %s", [devices[key]])
add_devices([devices[key]], True)
else:
devices[key].update()
signals = []
# Update all platforms for the device via dispatcher.
# Add/update entity if schema validates to true.
validated = validate_child(msg.gateway, msg.node_id, child)
for platform, dev_ids in validated.items():
devices = get_mysensors_devices(hass, platform)
for idx, dev_id in enumerate(list(dev_ids)):
if dev_id in devices:
dev_ids.pop(idx)
signals.append(SIGNAL_CALLBACK.format(*dev_id))
if dev_ids:
discover_mysensors_platform(hass, platform, dev_ids)
for signal in set(signals):
# Only one signal per device is needed.
# A device can have multiple platforms, ie multiple schemas.
# FOR LATER: Add timer to not signal if another update comes in.
dispatcher_send(hass, signal)
end = timer()
if end - start > 0.1:
_LOGGER.debug(
"Callback for node %s child %s took %.3f seconds",
msg.node_id, msg.child_id, end - start)
return mysensors_callback
class GatewayWrapper(object):
"""Gateway wrapper class."""
def __init__(self, gateway, optimistic, device):
"""Set up the class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
optimistic (bool): Send values to actuators without feedback state.
device (str): Path to serial port, ip adress or mqtt.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
platform_callbacks (list): Callback functions, one per platform.
optimistic (bool): Send values to actuators without feedback state.
device (str): Device configured as gateway.
__initialised (bool): True if GatewayWrapper is initialised.
"""
self._wrapped_gateway = gateway
self.platform_callbacks = []
self.optimistic = optimistic
self.device = device
self.__initialised = True
def __getattr__(self, name):
"""See if this object has attribute name."""
# Do not use hasattr, it goes into infinite recurrsion
if name in self.__dict__:
# This object has the attribute.
return getattr(self, name)
# The wrapped object has the attribute.
return getattr(self._wrapped_gateway, name)
def __setattr__(self, name, value):
"""See if this object has attribute name then set to value."""
if '_GatewayWrapper__initialised' not in self.__dict__:
return object.__setattr__(self, name, value)
elif name in self.__dict__:
object.__setattr__(self, name, value)
else:
object.__setattr__(self._wrapped_gateway, name, value)
def callback_factory(self):
"""Return a new callback function."""
def node_update(msg):
"""Handle node updates from the MySensors gateway."""
_LOGGER.debug(
"Update: node %s, child %s sub_type %s",
msg.node_id, msg.child_id, msg.sub_type)
for callback in self.platform_callbacks:
callback(self, msg)
return node_update
def get_mysensors_name(gateway, node_id, child_id):
"""Return a name for a node child."""
return '{} {} {}'.format(
gateway.sensors[node_id].sketch_name, node_id, child_id)
class MySensorsDeviceEntity(object):
"""Representation of a MySensors entity."""
def get_mysensors_gateway(hass, gateway_id):
"""Return gateway."""
if MYSENSORS_GATEWAYS not in hass.data:
hass.data[MYSENSORS_GATEWAYS] = {}
gateways = hass.data.get(MYSENSORS_GATEWAYS)
return gateways.get(gateway_id)
def setup_mysensors_platform(
hass, domain, discovery_info, device_class, device_args=None,
add_devices=None):
"""Set up a mysensors platform."""
# Only act if called via mysensors by discovery event.
# Otherwise gateway is not setup.
if not discovery_info:
return
if device_args is None:
device_args = ()
new_devices = []
new_dev_ids = discovery_info[ATTR_DEVICES]
for dev_id in new_dev_ids:
devices = get_mysensors_devices(hass, domain)
if dev_id in devices:
continue
gateway_id, node_id, child_id, value_type = dev_id
gateway = get_mysensors_gateway(hass, gateway_id)
if not gateway:
continue
device_class_copy = device_class
if isinstance(device_class, dict):
child = gateway.sensors[node_id].children[child_id]
s_type = gateway.const.Presentation(child.type).name
device_class_copy = device_class[s_type]
name = get_mysensors_name(gateway, node_id, child_id)
# python 3.4 cannot unpack inside tuple, but combining tuples works
args_copy = device_args + (
gateway, node_id, child_id, name, value_type)
devices[dev_id] = device_class_copy(*args_copy)
new_devices.append(devices[dev_id])
if new_devices:
_LOGGER.info("Adding new devices: %s", new_devices)
if add_devices is not None:
add_devices(new_devices, True)
return new_devices
class MySensorsDevice(object):
"""Representation of a MySensors device."""
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Set up the MySensors device."""
@@ -373,11 +560,6 @@ class MySensorsDeviceEntity(object):
self.child_type = child.type
self._values = {}
@property
def should_poll(self):
"""Mysensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""Return the name of this entity."""
@@ -399,18 +581,9 @@ class MySensorsDeviceEntity(object):
set_req = self.gateway.const.SetReq
for value_type, value in self._values.items():
try:
attr[set_req(value_type).name] = value
except ValueError:
_LOGGER.error("Value_type %s is not valid for mysensors "
"version %s", value_type,
self.gateway.protocol_version)
return attr
attr[set_req(value_type).name] = value
@property
def available(self):
"""Return true if entity is available."""
return self.value_type in self._values
return attr
def update(self):
"""Update the controller with the latest value from a sensor."""
@@ -419,7 +592,8 @@ class MySensorsDeviceEntity(object):
set_req = self.gateway.const.SetReq
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
"Entity update: %s: value_type %s, value = %s",
self._name, value_type, value)
if value_type in (set_req.V_ARMED, set_req.V_LIGHT,
set_req.V_LOCK_STATUS, set_req.V_TRIPPED):
self._values[value_type] = (
@@ -428,3 +602,29 @@ class MySensorsDeviceEntity(object):
self._values[value_type] = int(value)
else:
self._values[value_type] = value
class MySensorsEntity(MySensorsDevice, Entity):
"""Representation of a MySensors entity."""
@property
def should_poll(self):
"""Mysensor gateway pushes its state to HA."""
return False
@property
def available(self):
"""Return true if entity is available."""
return self.value_type in self._values
def _async_update_callback(self):
"""Update the entity."""
self.hass.async_add_job(self.async_update_ha_state(True))
@asyncio.coroutine
def async_added_to_hass(self):
"""Register update callback."""
dev_id = id(self.gateway), self.node_id, self.child_id, self.value_type
async_dispatcher_connect(
self.hass, SIGNAL_CALLBACK.format(*dev_id),
self._async_update_callback)
+3 -4
View File
@@ -14,7 +14,6 @@ from homeassistant.helpers import discovery
from homeassistant.const import (
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
CONF_MONITORED_CONDITIONS)
from homeassistant.loader import get_component
REQUIREMENTS = ['python-nest==3.1.0']
@@ -54,7 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(nest, hass, config):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if 'nest' in _CONFIGURING:
_LOGGER.debug("configurator failed")
configurator.notify_errors(
@@ -68,7 +67,7 @@ def request_configuration(nest, hass, config):
setup_nest(hass, nest, config, pin=pin)
_CONFIGURING['nest'] = configurator.request_config(
hass, "Nest", nest_configuration_callback,
"Nest", nest_configuration_callback,
description=('To configure Nest, click Request Authorization below, '
'log into your Nest account, '
'and then enter the resulting PIN'),
@@ -92,7 +91,7 @@ def setup_nest(hass, nest, config, pin=None):
if 'nest' in _CONFIGURING:
_LOGGER.debug("configuration done")
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('nest'))
_LOGGER.debug("proceeding with setup")
+17 -6
View File
@@ -4,16 +4,18 @@ Discord platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.discord/
"""
import logging
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
PLATFORM_SCHEMA, BaseNotificationService, ATTR_TARGET)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['discord.py==0.16.8']
REQUIREMENTS = ['discord.py==0.16.10']
CONF_TOKEN = 'token'
@@ -42,13 +44,22 @@ class DiscordNotificationService(BaseNotificationService):
import discord
discord_bot = discord.Client(loop=self.hass.loop)
if ATTR_TARGET not in kwargs:
_LOGGER.error("No target specified")
return None
# pylint: disable=unused-variable
@discord_bot.event
@asyncio.coroutine
def on_ready(): # pylint: disable=unused-variable
def on_ready():
"""Send the messages when the bot is ready."""
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
yield from discord_bot.send_message(channel, message)
try:
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
yield from discord_bot.send_message(channel, message)
except (discord.errors.HTTPException,
discord.errors.NotFound) as error:
_LOGGER.warning("Communication error: %s", error)
yield from discord_bot.logout()
yield from discord_bot.close()
@@ -0,0 +1,97 @@
"""
HipChat platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.hipchat/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import CONF_TOKEN, CONF_HOST
REQUIREMENTS = ['hipnotify==1.0.8']
_LOGGER = logging.getLogger(__name__)
CONF_COLOR = 'color'
CONF_ROOM = 'room'
CONF_NOTIFY = 'notify'
CONF_FORMAT = 'format'
DEFAULT_COLOR = 'yellow'
DEFAULT_FORMAT = 'text'
DEFAULT_HOST = 'https://api.hipchat.com/'
DEFAULT_NOTIFY = False
VALID_COLORS = {'yellow', 'green', 'red', 'purple', 'gray', 'random'}
VALID_FORMATS = {'text', 'html'}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ROOM): vol.Coerce(int),
vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_COLOR, default=DEFAULT_COLOR): vol.In(VALID_COLORS),
vol.Optional(CONF_FORMAT, default=DEFAULT_FORMAT): vol.In(VALID_FORMATS),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NOTIFY, default=DEFAULT_NOTIFY): cv.boolean,
})
def get_service(hass, config, discovery_info=None):
"""Get the HipChat notification service."""
return HipchatNotificationService(
config[CONF_TOKEN], config[CONF_ROOM], config[CONF_COLOR],
config[CONF_NOTIFY], config[CONF_FORMAT], config[CONF_HOST])
class HipchatNotificationService(BaseNotificationService):
"""Implement the notification service for HipChat."""
def __init__(self, token, default_room, default_color, default_notify,
default_format, host):
"""Initialize the service."""
self._token = token
self._default_room = default_room
self._default_color = default_color
self._default_notify = default_notify
self._default_format = default_format
self._host = host
self._rooms = {}
self._get_room(self._default_room)
def _get_room(self, room):
"""Get Room object, creating it if necessary."""
from hipnotify import Room
if room not in self._rooms:
self._rooms[room] = Room(
token=self._token, room_id=room, endpoint_url=self._host)
return self._rooms[room]
def send_message(self, message="", **kwargs):
"""Send a message."""
color = self._default_color
notify = self._default_notify
message_format = self._default_format
if kwargs.get(ATTR_DATA) is not None:
data = kwargs.get(ATTR_DATA)
if ((data.get(CONF_COLOR) is not None)
and (data.get(CONF_COLOR) in VALID_COLORS)):
color = data.get(CONF_COLOR)
if ((data.get(CONF_NOTIFY) is not None)
and isinstance(data.get(CONF_NOTIFY), bool)):
notify = data.get(CONF_NOTIFY)
if ((data.get(CONF_FORMAT) is not None)
and (data.get(CONF_FORMAT) in VALID_FORMATS)):
message_format = data.get(CONF_FORMAT)
targets = kwargs.get(ATTR_TARGET, [self._default_room])
for target in targets:
room = self._get_room(target)
room.notify(msg=message, color=color, notify=notify,
message_format=message_format)
+15 -30
View File
@@ -6,35 +6,19 @@ https://home-assistant.io/components/notify.mysensors/
"""
from homeassistant.components import mysensors
from homeassistant.components.notify import (
ATTR_TARGET, BaseNotificationService)
ATTR_TARGET, DOMAIN, BaseNotificationService)
def get_service(hass, config, discovery_info=None):
"""Get the MySensors notification service."""
if discovery_info is None:
new_devices = mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsNotificationDevice)
if not new_devices:
return
platform_devices = []
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
if float(gateway.protocol_version) < 2.0:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_INFO: [set_req.V_TEXT],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsNotificationDevice))
platform_devices.append(devices)
return MySensorsNotificationService(platform_devices)
return MySensorsNotificationService(hass)
class MySensorsNotificationDevice(mysensors.MySensorsDeviceEntity):
class MySensorsNotificationDevice(mysensors.MySensorsDevice):
"""Represent a MySensors Notification device."""
def send_msg(self, msg):
@@ -44,24 +28,25 @@ class MySensorsNotificationDevice(mysensors.MySensorsDeviceEntity):
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, sub_msg)
def __repr__(self):
"""Return the representation."""
return "<MySensorsNotificationDevice {}>".format(self.name)
class MySensorsNotificationService(BaseNotificationService):
"""Implement MySensors notification service."""
"""Implement a MySensors notification service."""
# pylint: disable=too-few-public-methods
def __init__(self, platform_devices):
def __init__(self, hass):
"""Initialize the service."""
self.platform_devices = platform_devices
self.devices = mysensors.get_mysensors_devices(hass, DOMAIN)
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
target_devices = kwargs.get(ATTR_TARGET)
devices = []
for gw_devs in self.platform_devices:
for device in gw_devs.values():
if target_devices is None or device.name in target_devices:
devices.append(device)
devices = [device for device in self.devices.values()
if target_devices is None or device.name in target_devices]
for device in devices:
device.send_msg(message)
+70
View File
@@ -0,0 +1,70 @@
"""
Prowl notification service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.prowl/
"""
import logging
import asyncio
import async_timeout
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'https://api.prowlapp.com/publicapi/'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
})
@asyncio.coroutine
def async_get_service(hass, config, discovery_info=None):
"""Get the Prowl notification service."""
return ProwlNotificationService(hass, config[CONF_API_KEY])
class ProwlNotificationService(BaseNotificationService):
"""Implement the notification service for Prowl."""
def __init__(self, hass, api_key):
"""Initialize the service."""
self._hass = hass
self._api_key = api_key
@asyncio.coroutine
def async_send_message(self, message, **kwargs):
"""Send the message to the user."""
response = None
session = None
url = '{}{}'.format(_RESOURCE, 'add')
data = kwargs.get(ATTR_DATA)
payload = {
'apikey': self._api_key,
'application': 'Home-Assistant',
'event': kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
'description': message,
'priority': data['priority'] if data and 'priority' in data else 0
}
_LOGGER.debug("Attempting call Prowl service at %s", url)
session = async_get_clientsession(self._hass)
try:
with async_timeout.timeout(10, loop=self._hass.loop):
response = yield from session.post(url, data=payload)
result = yield from response.text()
if response.status != 200 or 'error' in result:
_LOGGER.error("Prowl service returned http "
"status %d, response %s",
response.status, result)
except asyncio.TimeoutError:
_LOGGER.error("Timeout accessing Prowl at %s", url)
+14 -19
View File
@@ -89,15 +89,7 @@ class PushBulletNotificationService(BaseNotificationService):
if not targets:
# Backward compatibility, notify all devices in own account
if url:
self.pushbullet.push_link(title, url, body=message)
if self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message)
self._push_data(filepath, message, title, url)
_LOGGER.info("Sent notification to self")
return
@@ -112,16 +104,7 @@ class PushBulletNotificationService(BaseNotificationService):
# Target is email, send directly, don't use a target object
# This also seems works to send to all devices in own account
if ttype == 'email':
if url:
self.pushbullet.push_link(
title, url, body=message, email=tname)
if self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message, email=tname)
self._push_data(filepath, message, title, url, tname)
_LOGGER.info("Sent notification to email %s", tname)
continue
@@ -152,3 +135,15 @@ class PushBulletNotificationService(BaseNotificationService):
except self.pushbullet.errors.PushError:
_LOGGER.error("Notify failed to: %s/%s", ttype, tname)
continue
def _push_data(self, filepath, message, title, url, tname=None):
if url:
self.pushbullet.push_link(
title, url, body=message, email=tname)
elif filepath and self.hass.config.is_allowed_path(filepath):
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
self.pushbullet.push_file(title=title, body=message,
**filedata)
else:
self.pushbullet.push_note(title, message, email=tname)
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-pushover==0.2']
REQUIREMENTS = ['python-pushover==0.3']
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -13,7 +13,7 @@ from homeassistant.components.notify import (
from homeassistant.const import (CONF_API_KEY, CONF_SENDER, CONF_RECIPIENT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['sendgrid==4.2.1']
REQUIREMENTS = ['sendgrid==5.0.0']
_LOGGER = logging.getLogger(__name__)
+25 -29
View File
@@ -5,20 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.slack/
"""
import logging
import requests
from requests.auth import HTTPDigestAuth
from requests.auth import HTTPBasicAuth
import requests
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPDigestAuth
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_TITLE, ATTR_DATA,
PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import (
CONF_API_KEY, CONF_USERNAME, CONF_ICON)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_TITLE, ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_ICON)
REQUIREMENTS = ['slacker==0.9.50']
REQUIREMENTS = ['slacker==0.9.60']
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +33,7 @@ ATTR_FILE_PATH = 'path'
ATTR_FILE_USERNAME = 'username'
ATTR_FILE_PASSWORD = 'password'
ATTR_FILE_AUTH = 'auth'
# Any other value or absense of 'auth' lead to basic authentication being used
# Any other value or absence of 'auth' lead to basic authentication being used
ATTR_FILE_AUTH_DIGEST = 'digest'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -49,14 +48,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_service(hass, config, discovery_info=None):
"""Get the Slack notification service."""
import slacker
channel = config.get(CONF_CHANNEL)
api_key = config.get(CONF_API_KEY)
username = config.get(CONF_USERNAME)
icon = config.get(CONF_ICON)
try:
return SlackNotificationService(
config[CONF_CHANNEL],
config[CONF_API_KEY],
config.get(CONF_USERNAME, None),
config.get(CONF_ICON, None),
hass.config.is_allowed_path)
channel, api_key, username, icon, hass.config.is_allowed_path)
except slacker.Error:
_LOGGER.exception("Authentication failed")
@@ -66,9 +65,8 @@ def get_service(hass, config, discovery_info=None):
class SlackNotificationService(BaseNotificationService):
"""Implement the notification service for Slack."""
def __init__(self, default_channel,
api_token, username,
icon, is_allowed_path):
def __init__(
self, default_channel, api_token, username, icon, is_allowed_path):
"""Initialize the service."""
from slacker import Slacker
self._default_channel = default_channel
@@ -101,7 +99,7 @@ class SlackNotificationService(BaseNotificationService):
for target in targets:
try:
if file is not None:
# Load from file or url
# Load from file or URL
file_as_bytes = self.load_file(
url=file.get(ATTR_FILE_URL),
local_path=file.get(ATTR_FILE_PATH),
@@ -113,7 +111,7 @@ class SlackNotificationService(BaseNotificationService):
filename = file.get(ATTR_FILE_URL)
else:
filename = file.get(ATTR_FILE_PATH)
# Prepare structure for slack API
# Prepare structure for Slack API
data = {
'content': None,
'filetype': None,
@@ -135,35 +133,33 @@ class SlackNotificationService(BaseNotificationService):
except slacker.Error as err:
_LOGGER.error("Could not send notification. Error: %s", err)
def load_file(self, url=None, local_path=None,
username=None, password=None, auth=None):
"""Load image/document/etc from a local path or url."""
def load_file(self, url=None, local_path=None, username=None,
password=None, auth=None):
"""Load image/document/etc from a local path or URL."""
try:
if url is not None:
# check whether authentication parameters are provided
# Check whether authentication parameters are provided
if username is not None and password is not None:
# Use digest or basic authentication
if ATTR_FILE_AUTH_DIGEST == auth:
auth_ = HTTPDigestAuth(username, password)
else:
auth_ = HTTPBasicAuth(username, password)
# load file from url with authentication
# Load file from URL with authentication
req = requests.get(url, auth=auth_, timeout=CONF_TIMEOUT)
else:
# load file from url without authentication
# Load file from URL without authentication
req = requests.get(url, timeout=CONF_TIMEOUT)
return req.content
elif local_path is not None:
# Check whether path is whitelisted in configuration.yaml
if self.is_allowed_path(local_path):
# load file from local path on server
return open(local_path, "rb")
_LOGGER.warning("'%s' is not secure to load data from!",
local_path)
else:
# neither url nor path provided
_LOGGER.warning("Neither url nor local path found in params!")
_LOGGER.warning("Neither URL nor local path found in params!")
except OSError as error:
_LOGGER.error("Can't load from url or local path: %s", error)
+23 -5
View File
@@ -16,11 +16,15 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'octoprint'
CONF_NUMBER_OF_TOOLS = 'number_of_tools'
CONF_BED = 'bed'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NUMBER_OF_TOOLS, default=0): cv.positive_int,
vol.Optional(CONF_BED, default=False): cv.boolean
}),
}, extra=vol.ALLOW_EXTRA)
@@ -29,11 +33,13 @@ def setup(hass, config):
"""Set up the OctoPrint component."""
base_url = 'http://{}/api/'.format(config[DOMAIN][CONF_HOST])
api_key = config[DOMAIN][CONF_API_KEY]
number_of_tools = config[DOMAIN][CONF_NUMBER_OF_TOOLS]
bed = config[DOMAIN][CONF_BED]
hass.data[DOMAIN] = {"api": None}
try:
octoprint_api = OctoPrintAPI(base_url, api_key)
octoprint_api = OctoPrintAPI(base_url, api_key, bed, number_of_tools)
hass.data[DOMAIN]["api"] = octoprint_api
octoprint_api.get('printer')
octoprint_api.get('job')
@@ -46,7 +52,7 @@ def setup(hass, config):
class OctoPrintAPI(object):
"""Simple JSON wrapper for OctoPrint's API."""
def __init__(self, api_url, key):
def __init__(self, api_url, key, bed, number_of_tools):
"""Initialize OctoPrint API and set headers needed later."""
self.api_url = api_url
self.headers = {'content-type': CONTENT_TYPE_JSON,
@@ -58,11 +64,23 @@ class OctoPrintAPI(object):
self.available = False
self.printer_error_logged = False
self.job_error_logged = False
self.bed = bed
self.number_of_tools = number_of_tools
_LOGGER.error(str(bed) + " " + str(number_of_tools))
def get_tools(self):
"""Get the dynamic list of tools that temperature is monitored on."""
tools = self.printer_last_reading[0]['temperature']
return tools.keys()
"""Get the list of tools that temperature is monitored on."""
tools = []
if self.number_of_tools > 0:
for tool_number in range(0, self.number_of_tools):
tools.append("tool" + str(tool_number))
if self.bed:
tools.append('bed')
if not self.bed and self.number_of_tools == 0:
temps = self.printer_last_reading[0].get('temperature')
if temps is not None:
tools = temps.keys()
return tools
def get(self, endpoint):
"""Send a get request, and return the response as a dict."""
+10 -11
View File
@@ -12,16 +12,18 @@ from aiohttp import web
from homeassistant.components.http import HomeAssistantView
from homeassistant.components import recorder
from homeassistant.const import (CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE,
CONF_INCLUDE, EVENT_STATE_CHANGED,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.const import (
CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE, TEMP_CELSIUS,
EVENT_STATE_CHANGED, TEMP_FAHRENHEIT, CONTENT_TYPE_TEXT_PLAIN)
from homeassistant import core as hacore
from homeassistant.helpers import state as state_helper
from homeassistant.util.temperature import fahrenheit_to_celsius
REQUIREMENTS = ['prometheus_client==0.0.19']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['prometheus_client==0.0.19']
API_ENDPOINT = '/api/prometheus'
DOMAIN = 'prometheus'
DEPENDENCIES = ['http']
@@ -30,8 +32,6 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: recorder.FILTER_SCHEMA,
}, extra=vol.ALLOW_EXTRA)
API_ENDPOINT = '/api/prometheus'
def setup(hass, config):
"""Activate Prometheus component."""
@@ -45,11 +45,10 @@ def setup(hass, config):
metrics = Metrics(prometheus_client, exclude, include)
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_event)
return True
class Metrics:
class Metrics(object):
"""Model all of the metrics which should be exposed to Prometheus."""
def __init__(self, prometheus_client, exclude, include):
@@ -81,7 +80,7 @@ class Metrics:
entity_id not in self.include_entities):
return
handler = '_handle_' + domain
handler = '_handle_{}'.format(domain)
if hasattr(self, handler):
getattr(self, handler)(state)
@@ -233,8 +232,8 @@ class PrometheusView(HomeAssistantView):
@asyncio.coroutine
def get(self, request):
"""Handle request for Prometheus metrics."""
_LOGGER.debug('Received Prometheus metrics request')
_LOGGER.debug("Received Prometheus metrics request")
return web.Response(
body=self.prometheus_client.generate_latest(),
content_type="text/plain")
content_type=CONTENT_TYPE_TEXT_PLAIN)
+4 -3
View File
@@ -214,14 +214,15 @@ class HarmonyRemote(remote.RemoteDevice):
if device is None:
_LOGGER.error("Missing required argument: device")
return
params = {}
num_repeats = kwargs.pop(ATTR_NUM_REPEATS, None)
if num_repeats is not None:
kwargs[ATTR_NUM_REPEATS] = num_repeats
params['repeat_num'] = num_repeats
delay_secs = kwargs.pop(ATTR_DELAY_SECS, None)
if delay_secs is not None:
kwargs[ATTR_DELAY_SECS] = delay_secs
params['delay_secs'] = delay_secs
pyharmony.ha_send_commands(
self._token, self.host, self._port, device, command, **kwargs)
self._token, self.host, self._port, device, command, **params)
def sync(self):
"""Sync the Harmony device with the web service."""
+17 -8
View File
@@ -39,13 +39,13 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
_SCRIPT_ENTRY_SCHEMA = vol.Schema({
SCRIPT_ENTRY_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({cv.slug: _SCRIPT_ENTRY_SCHEMA})
DOMAIN: vol.Schema({cv.slug: SCRIPT_ENTRY_SCHEMA})
}, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema(dict)
@@ -62,12 +62,6 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
@bind_hass
def reload(hass):
"""Reload script component."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def turn_on(hass, entity_id, variables=None):
"""Turn script on."""
@@ -88,6 +82,21 @@ def toggle(hass, entity_id):
hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
@bind_hass
def reload(hass):
"""Reload script component."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def async_reload(hass):
"""Reload the scripts from config.
Returns a coroutine object.
"""
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@asyncio.coroutine
def async_setup(hass, config):
"""Load the scripts from the configuration."""
+155 -22
View File
@@ -23,12 +23,14 @@ from homeassistant.helpers.event import (
async_track_point_in_utc_time)
from homeassistant.util import dt as dt_util
REQUIREMENTS = ['buienradar==0.8']
REQUIREMENTS = ['buienradar==0.9']
_LOGGER = logging.getLogger(__name__)
MEASURED_LABEL = 'Measured'
TIMEFRAME_LABEL = 'Timeframe'
SYMBOL = 'symbol'
# Schedule next call after (minutes):
SCHEDULE_OK = 10
# When an error occurred, new call after (minutes):
@@ -38,6 +40,10 @@ SCHEDULE_NOK = 2
# Key: ['label', unit, icon]
SENSOR_TYPES = {
'stationname': ['Stationname', None, None],
'condition': ['Condition', None, None],
'conditioncode': ['Condition code', None, None],
'conditiondetailed': ['Detailed condition', None, None],
'conditionexact': ['Full condition', None, None],
'symbol': ['Symbol', None, None],
'humidity': ['Humidity', '%', 'mdi:water-percent'],
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
@@ -55,7 +61,67 @@ SENSOR_TYPES = {
'precipitation_forecast_average': ['Precipitation forecast average',
'mm/h', 'mdi:weather-pouring'],
'precipitation_forecast_total': ['Precipitation forecast total',
'mm', 'mdi:weather-pouring']
'mm', 'mdi:weather-pouring'],
'temperature_1d': ['Temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_2d': ['Temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_3d': ['Temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_4d': ['Temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
'temperature_5d': ['Temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_1d': ['Minimum temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_2d': ['Minimum temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_3d': ['Minimum temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_4d': ['Minimum temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
'mintemp_5d': ['Minimum temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
'rain_1d': ['Rain 1d', 'mm', 'mdi:weather-pouring'],
'rain_2d': ['Rain 2d', 'mm', 'mdi:weather-pouring'],
'rain_3d': ['Rain 3d', 'mm', 'mdi:weather-pouring'],
'rain_4d': ['Rain 4d', 'mm', 'mdi:weather-pouring'],
'rain_5d': ['Rain 5d', 'mm', 'mdi:weather-pouring'],
'snow_1d': ['Snow 1d', 'cm', 'mdi:snowflake'],
'snow_2d': ['Snow 2d', 'cm', 'mdi:snowflake'],
'snow_3d': ['Snow 3d', 'cm', 'mdi:snowflake'],
'snow_4d': ['Snow 4d', 'cm', 'mdi:snowflake'],
'snow_5d': ['Snow 5d', 'cm', 'mdi:snowflake'],
'rainchance_1d': ['Rainchance 1d', '%', 'mdi:weather-pouring'],
'rainchance_2d': ['Rainchance 2d', '%', 'mdi:weather-pouring'],
'rainchance_3d': ['Rainchance 3d', '%', 'mdi:weather-pouring'],
'rainchance_4d': ['Rainchance 4d', '%', 'mdi:weather-pouring'],
'rainchance_5d': ['Rainchance 5d', '%', 'mdi:weather-pouring'],
'sunchance_1d': ['Sunchance 1d', '%', 'mdi:weather-partlycloudy'],
'sunchance_2d': ['Sunchance 2d', '%', 'mdi:weather-partlycloudy'],
'sunchance_3d': ['Sunchance 3d', '%', 'mdi:weather-partlycloudy'],
'sunchance_4d': ['Sunchance 4d', '%', 'mdi:weather-partlycloudy'],
'sunchance_5d': ['Sunchance 5d', '%', 'mdi:weather-partlycloudy'],
'windforce_1d': ['Wind force 1d', 'Bft', 'mdi:weather-windy'],
'windforce_2d': ['Wind force 2d', 'Bft', 'mdi:weather-windy'],
'windforce_3d': ['Wind force 3d', 'Bft', 'mdi:weather-windy'],
'windforce_4d': ['Wind force 4d', 'Bft', 'mdi:weather-windy'],
'windforce_5d': ['Wind force 5d', 'Bft', 'mdi:weather-windy'],
'condition_1d': ['Condition 1d', None, None],
'condition_2d': ['Condition 2d', None, None],
'condition_3d': ['Condition 3d', None, None],
'condition_4d': ['Condition 4d', None, None],
'condition_5d': ['Condition 5d', None, None],
'conditioncode_1d': ['Condition code 1d', None, None],
'conditioncode_2d': ['Condition code 2d', None, None],
'conditioncode_3d': ['Condition code 3d', None, None],
'conditioncode_4d': ['Condition code 4d', None, None],
'conditioncode_5d': ['Condition code 5d', None, None],
'conditiondetailed_1d': ['Detailed condition 1d', None, None],
'conditiondetailed_2d': ['Detailed condition 2d', None, None],
'conditiondetailed_3d': ['Detailed condition 3d', None, None],
'conditiondetailed_4d': ['Detailed condition 4d', None, None],
'conditiondetailed_5d': ['Detailed condition 5d', None, None],
'conditionexact_1d': ['Full condition 1d', None, None],
'conditionexact_2d': ['Full condition 2d', None, None],
'conditionexact_3d': ['Full condition 3d', None, None],
'conditionexact_4d': ['Full condition 4d', None, None],
'conditionexact_5d': ['Full condition 5d', None, None],
'symbol_1d': ['Symbol 1d', None, None],
'symbol_2d': ['Symbol 2d', None, None],
'symbol_3d': ['Symbol 3d', None, None],
'symbol_4d': ['Symbol 4d', None, None],
'symbol_5d': ['Symbol 5d', None, None],
}
CONF_TIMEFRAME = 'timeframe'
@@ -126,23 +192,86 @@ class BrSensor(Entity):
def load_data(self, data):
"""Load the sensor with relevant data."""
# Find sensor
from buienradar.buienradar import (ATTRIBUTION, IMAGE, MEASURED,
from buienradar.buienradar import (ATTRIBUTION, CONDITION, CONDCODE,
DETAILED, EXACT, EXACTNL, FORECAST,
IMAGE, MEASURED,
PRECIPITATION_FORECAST, STATIONNAME,
SYMBOL, TIMEFRAME)
TIMEFRAME)
self._attribution = data.get(ATTRIBUTION)
self._stationname = data.get(STATIONNAME)
self._measured = data.get(MEASURED)
if self.type == SYMBOL:
# update weather symbol & status text
new_state = data.get(self.type)
img = data.get(IMAGE)
# pylint: disable=protected-access
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
if self.type.endswith('_1d') or \
self.type.endswith('_2d') or \
self.type.endswith('_3d') or \
self.type.endswith('_4d') or \
self.type.endswith('_5d'):
fcday = 0
if self.type.endswith('_2d'):
fcday = 1
if self.type.endswith('_3d'):
fcday = 2
if self.type.endswith('_4d'):
fcday = 3
if self.type.endswith('_5d'):
fcday = 4
# update all other sensors
if self.type.startswith(SYMBOL) or self.type.startswith(CONDITION):
condition = data.get(FORECAST)[fcday].get(CONDITION)
if condition:
new_state = condition.get(CONDITION, None)
if self.type.startswith(SYMBOL):
new_state = condition.get(EXACTNL, None)
if self.type.startswith('conditioncode'):
new_state = condition.get(CONDCODE, None)
if self.type.startswith('conditiondetailed'):
new_state = condition.get(DETAILED, None)
if self.type.startswith('conditionexact'):
new_state = condition.get(EXACT, None)
img = condition.get(IMAGE, None)
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
return False
else:
new_state = data.get(FORECAST)[fcday].get(self.type[:-3])
if new_state != self._state:
self._state = new_state
return True
return False
return False
if self.type == SYMBOL or self.type.startswith(CONDITION):
# update weather symbol & status text
condition = data.get(CONDITION, None)
if condition:
if self.type == SYMBOL:
new_state = condition.get(EXACTNL, None)
if self.type == CONDITION:
new_state = condition.get(CONDITION, None)
if self.type == 'conditioncode':
new_state = condition.get(CONDCODE, None)
if self.type == 'conditiondetailed':
new_state = condition.get(DETAILED, None)
if self.type == 'conditionexact':
new_state = condition.get(EXACT, None)
img = condition.get(IMAGE, None)
# pylint: disable=protected-access
if new_state != self._state or img != self._entity_picture:
self._state = new_state
self._entity_picture = img
return True
return False
if self.type.startswith(PRECIPITATION_FORECAST):
@@ -187,11 +316,6 @@ class BrSensor(Entity):
@property
def entity_picture(self):
"""Weather symbol if type is symbol."""
from buienradar.buienradar import SYMBOL
if self.type != SYMBOL:
return None
return self._entity_picture
@property
@@ -360,8 +484,8 @@ class BrData(object):
@property
def condition(self):
"""Return the condition."""
from buienradar.buienradar import SYMBOL
return self.data.get(SYMBOL)
from buienradar.buienradar import CONDITION
return self.data.get(CONDITION)
@property
def temperature(self):
@@ -390,6 +514,15 @@ class BrData(object):
except (ValueError, TypeError):
return None
@property
def visibility(self):
"""Return the visibility, or None."""
from buienradar.buienradar import VISIBILITY
try:
return int(self.data.get(VISIBILITY))
except (ValueError, TypeError):
return None
@property
def wind_speed(self):
"""Return the windspeed, or None."""
@@ -402,9 +535,9 @@ class BrData(object):
@property
def wind_bearing(self):
"""Return the wind bearing, or None."""
from buienradar.buienradar import WINDDIRECTION
from buienradar.buienradar import WINDAZIMUTH
try:
return int(self.data.get(WINDDIRECTION))
return int(self.data.get(WINDAZIMUTH))
except (ValueError, TypeError):
return None
+19 -9
View File
@@ -4,16 +4,17 @@ Counter for the days till a HTTPS (TLS) certificate will expire.
For more details about this sensor please refer to the documentation at
https://home-assistant.io/components/sensor.cert_expiry/
"""
import datetime
import logging
import socket
import ssl
from datetime import datetime, timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_START)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@@ -21,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SSL Certificate Expiry'
DEFAULT_PORT = 443
SCAN_INTERVAL = datetime.timedelta(hours=12)
SCAN_INTERVAL = timedelta(hours=12)
TIMEOUT = 10.0
@@ -34,11 +35,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up certificate expiry sensor."""
server_name = config.get(CONF_HOST)
server_port = config.get(CONF_PORT)
sensor_name = config.get(CONF_NAME)
def run_setup(event):
"""Wait until Home Assistant is fully initialized before creating.
add_devices([SSLCertificate(sensor_name, server_name, server_port)], True)
Delay the setup until Home Assistant is fully initialized.
"""
server_name = config.get(CONF_HOST)
server_port = config.get(CONF_PORT)
sensor_name = config.get(CONF_NAME)
add_devices([SSLCertificate(sensor_name, server_name, server_port)],
True)
# To allow checking of the HA certificate we must first be running.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
class SSLCertificate(Entity):
@@ -97,6 +107,6 @@ class SSLCertificate(Entity):
return
ts_seconds = ssl.cert_time_to_seconds(cert['notAfter'])
timestamp = datetime.datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.datetime.today()
timestamp = datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.today()
self._state = expiry.days
@@ -30,7 +30,7 @@ UNIT_OF_MEASUREMENT = 'W'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_CHANNEL_ID): cv.string,
vol.Optional(CONF_CHANNEL_ID): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
+1 -1
View File
@@ -19,7 +19,7 @@ from homeassistant.util import Throttle
from homeassistant.util.dt import now, parse_date
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fedexdeliverymanager==1.0.3']
REQUIREMENTS = ['fedexdeliverymanager==1.0.4']
_LOGGER = logging.getLogger(__name__)
+114 -67
View File
@@ -17,10 +17,10 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
from homeassistant.util.icon import icon_for_battery_level
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fitbit==0.2.3']
REQUIREMENTS = ['fitbit==0.3.0']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -32,6 +32,7 @@ ATTR_CLIENT_SECRET = 'client_secret'
ATTR_LAST_SAVED_AT = 'last_saved_at'
CONF_MONITORED_RESOURCES = 'monitored_resources'
CONF_CLOCK_FORMAT = 'clock_format'
CONF_ATTRIBUTION = 'Data provided by Fitbit.com'
DEPENDENCIES = ['http']
@@ -49,40 +50,50 @@ DEFAULT_CONFIG = {
}
FITBIT_RESOURCES_LIST = {
'activities/activityCalories': 'cal',
'activities/calories': 'cal',
'activities/caloriesBMR': 'cal',
'activities/distance': '',
'activities/elevation': '',
'activities/floors': 'floors',
'activities/heart': 'bpm',
'activities/minutesFairlyActive': 'minutes',
'activities/minutesLightlyActive': 'minutes',
'activities/minutesSedentary': 'minutes',
'activities/minutesVeryActive': 'minutes',
'activities/steps': 'steps',
'activities/tracker/activityCalories': 'cal',
'activities/tracker/calories': 'cal',
'activities/tracker/distance': '',
'activities/tracker/elevation': '',
'activities/tracker/floors': 'floors',
'activities/tracker/minutesFairlyActive': 'minutes',
'activities/tracker/minutesLightlyActive': 'minutes',
'activities/tracker/minutesSedentary': 'minutes',
'activities/tracker/minutesVeryActive': 'minutes',
'activities/tracker/steps': 'steps',
'body/bmi': 'BMI',
'body/fat': '%',
'devices/battery': 'level',
'sleep/awakeningsCount': 'times awaken',
'sleep/efficiency': '%',
'sleep/minutesAfterWakeup': 'minutes',
'sleep/minutesAsleep': 'minutes',
'sleep/minutesAwake': 'minutes',
'sleep/minutesToFallAsleep': 'minutes',
'sleep/startTime': 'start time',
'sleep/timeInBed': 'time in bed',
'body/weight': ''
'activities/activityCalories': ['Activity Calories', 'cal', 'fire'],
'activities/calories': ['Calories', 'cal', 'fire'],
'activities/caloriesBMR': ['Calories BMR', 'cal', 'fire'],
'activities/distance': ['Distance', '', 'map-marker'],
'activities/elevation': ['Elevation', '', 'walk'],
'activities/floors': ['Floors', 'floors', 'walk'],
'activities/heart': ['Resting Heart Rate', 'bpm', 'heart-pulse'],
'activities/minutesFairlyActive':
['Minutes Fairly Active', 'minutes', 'walk'],
'activities/minutesLightlyActive':
['Minutes Lightly Active', 'minutes', 'walk'],
'activities/minutesSedentary':
['Minutes Sedentary', 'minutes', 'seat-recline-normal'],
'activities/minutesVeryActive': ['Minutes Very Active', 'minutes', 'run'],
'activities/steps': ['Steps', 'steps', 'walk'],
'activities/tracker/activityCalories':
['Tracker Activity Calories', 'cal', 'fire'],
'activities/tracker/calories': ['Tracker Calories', 'cal', 'fire'],
'activities/tracker/distance': ['Tracker Distance', '', 'map-marker'],
'activities/tracker/elevation': ['Tracker Elevation', '', 'walk'],
'activities/tracker/floors': ['Tracker Floors', 'floors', 'walk'],
'activities/tracker/minutesFairlyActive':
['Tracker Minutes Fairly Active', 'minutes', 'walk'],
'activities/tracker/minutesLightlyActive':
['Tracker Minutes Lightly Active', 'minutes', 'walk'],
'activities/tracker/minutesSedentary':
['Tracker Minutes Sedentary', 'minutes', 'seat-recline-normal'],
'activities/tracker/minutesVeryActive':
['Tracker Minutes Very Active', 'minutes', 'run'],
'activities/tracker/steps': ['Tracker Steps', 'steps', 'walk'],
'body/bmi': ['BMI', 'BMI', 'human'],
'body/fat': ['Body Fat', '%', 'human'],
'body/weight': ['Weight', '', 'human'],
'devices/battery': ['Battery', None, None],
'sleep/awakeningsCount':
['Awakenings Count', 'times awaken', 'sleep'],
'sleep/efficiency': ['Sleep Efficiency', '%', 'sleep'],
'sleep/minutesAfterWakeup': ['Minutes After Wakeup', 'minutes', 'sleep'],
'sleep/minutesAsleep': ['Sleep Minutes Asleep', 'minutes', 'sleep'],
'sleep/minutesAwake': ['Sleep Minutes Awake', 'minutes', 'sleep'],
'sleep/minutesToFallAsleep':
['Sleep Minutes to Fall Asleep', 'minutes', 'sleep'],
'sleep/startTime': ['Sleep Start Time', None, 'clock'],
'sleep/timeInBed': ['Sleep Time in Bed', 'minutes', 'hotel']
}
FITBIT_MEASUREMENTS = {
@@ -121,9 +132,18 @@ FITBIT_MEASUREMENTS = {
}
}
BATTERY_LEVELS = {
'High': 100,
'Medium': 50,
'Low': 20,
'Empty': 0
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES):
vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]),
vol.Optional(CONF_CLOCK_FORMAT, default='24H'):
vol.In(['12H', '24H'])
})
@@ -155,7 +175,7 @@ def config_from_file(filename, config=None):
def request_app_setup(hass, config, add_devices, config_path,
discovery_info=None):
"""Assist user with configuring the Fitbit dev application."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# pylint: disable=unused-argument
def fitbit_configuration_callback(callback_data):
@@ -166,7 +186,8 @@ def request_app_setup(hass, config, add_devices, config_path,
if config_file == DEFAULT_CONFIG:
error_msg = ("You didn't correctly modify fitbit.conf",
" please try again")
configurator.notify_errors(_CONFIGURING['fitbit'], error_msg)
configurator.notify_errors(_CONFIGURING['fitbit'],
error_msg)
else:
setup_platform(hass, config, add_devices, discovery_info)
else:
@@ -187,7 +208,7 @@ def request_app_setup(hass, config, add_devices, config_path,
submit = "I have saved my Client ID and Client Secret into fitbit.conf."
_CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback,
'Fitbit', fitbit_configuration_callback,
description=description, submit_caption=submit,
description_image="/static/images/config_fitbit_app.png"
)
@@ -195,7 +216,7 @@ def request_app_setup(hass, config, add_devices, config_path,
def request_oauth_completion(hass):
"""Request user complete Fitbit OAuth2 flow."""
configurator = get_component('configurator')
configurator = hass.components.configurator
if "fitbit" in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['fitbit'], "Failed to register, please try again.")
@@ -211,7 +232,7 @@ def request_oauth_completion(hass):
description = "Please authorize Fitbit by visiting {}".format(start_url)
_CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback,
'Fitbit', fitbit_configuration_callback,
description=description,
submit_caption="I have authorized Fitbit."
)
@@ -233,7 +254,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
if "fitbit" in _CONFIGURING:
get_component('configurator').request_done(_CONFIGURING.pop("fitbit"))
hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
import fitbit
@@ -257,6 +278,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
registered_devs = authd_client.get_devices()
clock_format = config.get(CONF_CLOCK_FORMAT)
for resource in config.get(CONF_MONITORED_RESOURCES):
# monitor battery for all linked FitBit devices
@@ -264,11 +286,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for dev_extra in registered_devs:
dev.append(FitbitSensor(
authd_client, config_path, resource,
hass.config.units.is_metric, dev_extra))
hass.config.units.is_metric, clock_format, dev_extra))
else:
dev.append(FitbitSensor(
authd_client, config_path, resource,
hass.config.units.is_metric))
hass.config.units.is_metric, clock_format))
add_devices(dev, True)
else:
@@ -361,34 +383,24 @@ class FitbitSensor(Entity):
"""Implementation of a Fitbit sensor."""
def __init__(self, client, config_path, resource_type,
is_metric, extra=None):
is_metric, clock_format, extra=None):
"""Initialize the Fitbit sensor."""
self.client = client
self.config_path = config_path
self.resource_type = resource_type
self.is_metric = is_metric
self.clock_format = clock_format
self.extra = extra
pretty_resource = self.resource_type.replace('activities/', '')
pretty_resource = pretty_resource.replace('/', ' ')
pretty_resource = pretty_resource.title()
if pretty_resource == 'Body Bmi':
pretty_resource = 'BMI'
elif pretty_resource == 'Heart':
pretty_resource = 'Resting Heart Rate'
elif pretty_resource == 'Devices Battery':
if self.extra:
pretty_resource = \
'{0} Battery'.format(self.extra.get('deviceVersion'))
else:
pretty_resource = 'Battery'
self._name = pretty_resource
unit_type = FITBIT_RESOURCES_LIST[self.resource_type]
self._name = FITBIT_RESOURCES_LIST[self.resource_type][0]
if self.extra:
self._name = '{0} Battery'.format(self.extra.get('deviceVersion'))
unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1]
if unit_type == "":
split_resource = self.resource_type.split('/')
try:
measurement_system = FITBIT_MEASUREMENTS[self.client.system]
except KeyError:
if is_metric:
if self.is_metric:
measurement_system = FITBIT_MEASUREMENTS['metric']
else:
measurement_system = FITBIT_MEASUREMENTS['en_US']
@@ -414,9 +426,11 @@ class FitbitSensor(Entity):
@property
def icon(self):
"""Icon to use in the frontend, if any."""
if self.resource_type == 'devices/battery':
return 'mdi:battery-50'
return 'mdi:walk'
if self.resource_type == 'devices/battery' and self.extra:
battery_level = BATTERY_LEVELS[self.extra.get('battery')]
return icon_for_battery_level(battery_level=battery_level,
charging=None)
return 'mdi:{}'.format(FITBIT_RESOURCES_LIST[self.resource_type][2])
@property
def device_state_attributes(self):
@@ -427,7 +441,7 @@ class FitbitSensor(Entity):
if self.extra:
attrs['model'] = self.extra.get('deviceVersion')
attrs['type'] = self.extra.get('type')
attrs['type'] = self.extra.get('type').lower()
return attrs
@@ -438,7 +452,40 @@ class FitbitSensor(Entity):
else:
container = self.resource_type.replace("/", "-")
response = self.client.time_series(self.resource_type, period='7d')
self._state = response[container][-1].get('value')
raw_state = response[container][-1].get('value')
if self.resource_type == 'activities/distance':
self._state = format(float(raw_state), '.2f')
elif self.resource_type == 'activities/tracker/distance':
self._state = format(float(raw_state), '.2f')
elif self.resource_type == 'body/bmi':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'body/fat':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'body/weight':
self._state = format(float(raw_state), '.1f')
elif self.resource_type == 'sleep/startTime':
if raw_state == '':
self._state = '-'
elif self.clock_format == '12H':
hours, minutes = raw_state.split(':')
hours, minutes = int(hours), int(minutes)
setting = 'AM'
if hours > 12:
setting = 'PM'
hours -= 12
elif hours == 0:
hours = 12
self._state = '{}:{} {}'.format(hours, minutes, setting)
else:
self._state = raw_state
else:
if self.is_metric:
self._state = raw_state
else:
try:
self._state = '{0:,}'.format(int(raw_state))
except TypeError:
self._state = raw_state
if self.resource_type == 'activities/heart':
self._state = response[container][-1]. \
@@ -216,7 +216,7 @@ class FritzBoxCallMonitor(object):
self._sensor.set_attributes(att)
elif line[1] == "CONNECT":
self._sensor.set_state(VALUE_CONNECT)
att = {"with": line[4], "device": [3], "accepted": isotime}
att = {"with": line[4], "device": line[3], "accepted": isotime}
att["with_name"] = self._sensor.number_to_name(att["with"])
self._sensor.set_attributes(att)
elif line[1] == "DISCONNECT":
@@ -80,6 +80,8 @@ class Geizwatch(Entity):
@property
def device_state_attributes(self):
"""Return the state attributes."""
while len(self.data.prices) < 4:
self.data.prices.append("None")
attrs = {'device_name': self.data.device_name,
'description': self.description,
'unit_of_measurement': self.data.unit_of_measurement,
+13 -35
View File
@@ -6,6 +6,7 @@ https://home-assistant.io/ecosystem/ios/
"""
from homeassistant.components import ios
from homeassistant.helpers.entity import Entity
from homeassistant.util.icon import icon_for_battery_level
DEPENDENCIES = ['ios']
@@ -83,44 +84,21 @@ class IOSSensor(Entity):
device_battery = self._device[ios.ATTR_BATTERY]
battery_state = device_battery[ios.ATTR_BATTERY_STATE]
battery_level = device_battery[ios.ATTR_BATTERY_LEVEL]
rounded_level = round(battery_level, -1)
returning_icon_level = DEFAULT_ICON_LEVEL
if battery_state == ios.ATTR_BATTERY_STATE_FULL:
returning_icon_level = DEFAULT_ICON_LEVEL
if battery_state == ios.ATTR_BATTERY_STATE_CHARGING:
returning_icon_state = DEFAULT_ICON_STATE
else:
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_state == ios.ATTR_BATTERY_STATE_CHARGING:
# Why is MDI missing 10, 50, 70?
if rounded_level in (20, 30, 40, 60, 80, 90, 100):
returning_icon_level = "{}-charging-{}".format(
DEFAULT_ICON_LEVEL, str(rounded_level))
returning_icon_state = DEFAULT_ICON_STATE
else:
returning_icon_level = "{}-charging".format(
DEFAULT_ICON_LEVEL)
returning_icon_state = DEFAULT_ICON_STATE
elif battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED:
if rounded_level < 10:
returning_icon_level = "{}-outline".format(
DEFAULT_ICON_LEVEL)
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_level > 95:
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
returning_icon_level = "{}-outline".format(
DEFAULT_ICON_LEVEL)
else:
returning_icon_level = "{}-{}".format(DEFAULT_ICON_LEVEL,
str(rounded_level))
returning_icon_state = "{}-off".format(DEFAULT_ICON_STATE)
charging = True
icon_state = DEFAULT_ICON_STATE
if (battery_state == ios.ATTR_BATTERY_STATE_FULL or
battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED):
charging = False
icon_state = "{}-off".format(DEFAULT_ICON_STATE)
elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN:
returning_icon_level = "{}-unknown".format(DEFAULT_ICON_LEVEL)
returning_icon_state = "{}-unknown".format(DEFAULT_ICON_LEVEL)
battery_level = None
charging = False
icon_state = "{}-unknown".format(DEFAULT_ICON_LEVEL)
if self.type == "state":
return returning_icon_state
return returning_icon_level
return icon_state
return icon_for_battery_level(battery_level=battery_level,
charging=charging)
def update(self):
"""Get the latest state of the sensor."""
@@ -0,0 +1,216 @@
"""
Sensor for checking the status of London air.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.london_air/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import requests
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
CONF_LOCATIONS = 'locations'
SCAN_INTERVAL = timedelta(minutes=30)
AUTHORITIES = [
'Barking and Dagenham',
'Bexley',
'Brent',
'Camden',
'City of London',
'Croydon',
'Ealing',
'Enfield',
'Greenwich',
'Hackney',
'Hammersmith and Fulham',
'Haringey',
'Harrow',
'Havering',
'Hillingdon',
'Islington',
'Kensington and Chelsea',
'Kingston',
'Lambeth',
'Lewisham',
'Merton',
'Redbridge',
'Richmond',
'Southwark',
'Sutton',
'Tower Hamlets',
'Wandsworth',
'Westminster']
URL = ('http://api.erg.kcl.ac.uk/AirQuality/Hourly/'
'MonitoringIndex/GroupName=London/Json')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LOCATIONS, default=AUTHORITIES):
vol.All(cv.ensure_list, [vol.In(AUTHORITIES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tube sensor."""
data = APIData()
data.update()
sensors = []
for name in config.get(CONF_LOCATIONS):
sensors.append(AirSensor(name, data))
add_devices(sensors, True)
class APIData(object):
"""Get the latest data for all authorities."""
def __init__(self):
"""Initialize the AirData object."""
self.data = None
# Update only once in scan interval.
@Throttle(SCAN_INTERVAL)
def update(self):
"""Get the latest data from TFL."""
response = requests.get(URL, timeout=10)
if response.status_code != 200:
_LOGGER.warning("Invalid response from API")
else:
self.data = parse_api_response(response.json())
class AirSensor(Entity):
"""Single authority air sensor."""
ICON = 'mdi:cloud-outline'
def __init__(self, name, APIdata):
"""Initialize the sensor."""
self._name = name
self._api_data = APIdata
self._site_data = None
self._state = None
self._updated = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def site_data(self):
"""Return the dict of sites data."""
return self._site_data
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self.ICON
@property
def device_state_attributes(self):
"""Return other details about the sensor state."""
attrs = {}
attrs['updated'] = self._updated
attrs['sites'] = len(self._site_data)
attrs['data'] = self._site_data
return attrs
def update(self):
"""Update the sensor."""
self._api_data.update()
self._site_data = self._api_data.data[self._name]
self._updated = self._site_data[0]['updated']
sites_status = []
for site in self._site_data:
if site['pollutants_status'] != 'no_species_data':
sites_status.append(site['pollutants_status'])
if sites_status:
self._state = max(set(sites_status), key=sites_status.count)
else:
self._state = STATE_UNKNOWN
def parse_species(species_data):
"""Iterate over list of species at each site."""
parsed_species_data = []
quality_list = []
for species in species_data:
if species['@AirQualityBand'] != 'No data':
species_dict = {}
species_dict['description'] = species['@SpeciesDescription']
species_dict['code'] = species['@SpeciesCode']
species_dict['quality'] = species['@AirQualityBand']
species_dict['index'] = species['@AirQualityIndex']
species_dict['summary'] = (species_dict['code'] + ' is '
+ species_dict['quality'])
parsed_species_data.append(species_dict)
quality_list.append(species_dict['quality'])
return parsed_species_data, quality_list
def parse_site(entry_sites_data):
"""Iterate over all sites at an authority."""
authority_data = []
for site in entry_sites_data:
site_data = {}
species_data = []
site_data['updated'] = site['@BulletinDate']
site_data['latitude'] = site['@Latitude']
site_data['longitude'] = site['@Longitude']
site_data['site_code'] = site['@SiteCode']
site_data['site_name'] = site['@SiteName'].split("-")[-1].lstrip()
site_data['site_type'] = site['@SiteType']
if isinstance(site['Species'], dict):
species_data = [site['Species']]
else:
species_data = site['Species']
parsed_species_data, quality_list = parse_species(species_data)
if not parsed_species_data:
parsed_species_data.append('no_species_data')
site_data['pollutants'] = parsed_species_data
if quality_list:
site_data['pollutants_status'] = max(set(quality_list),
key=quality_list.count)
site_data['number_of_pollutants'] = len(quality_list)
else:
site_data['pollutants_status'] = 'no_species_data'
site_data['number_of_pollutants'] = 0
authority_data.append(site_data)
return authority_data
def parse_api_response(response):
"""API can return dict or list of data so need to check."""
data = dict.fromkeys(AUTHORITIES)
for authority in AUTHORITIES:
for entry in response['HourlyAirQualityIndex']['LocalAuthority']:
if entry['@LocalAuthorityName'] == authority:
if isinstance(entry['Site'], dict):
entry_sites_data = [entry['Site']]
else:
entry_sites_data = entry['Site']
data[authority] = parse_site(entry_sites_data)
return data
+4 -75
View File
@@ -4,89 +4,18 @@ Support for MySensors sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.sensor import DOMAIN
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_TEMP: [set_req.V_TEMP],
pres.S_HUM: [set_req.V_HUM],
pres.S_BARO: [set_req.V_PRESSURE, set_req.V_FORECAST],
pres.S_WIND: [set_req.V_WIND, set_req.V_GUST, set_req.V_DIRECTION],
pres.S_RAIN: [set_req.V_RAIN, set_req.V_RAINRATE],
pres.S_UV: [set_req.V_UV],
pres.S_WEIGHT: [set_req.V_WEIGHT, set_req.V_IMPEDANCE],
pres.S_POWER: [set_req.V_WATT, set_req.V_KWH],
pres.S_DISTANCE: [set_req.V_DISTANCE],
pres.S_LIGHT_LEVEL: [set_req.V_LIGHT_LEVEL],
pres.S_IR: [set_req.V_IR_RECEIVE],
pres.S_WATER: [set_req.V_FLOW, set_req.V_VOLUME],
pres.S_CUSTOM: [set_req.V_VAR1,
set_req.V_VAR2,
set_req.V_VAR3,
set_req.V_VAR4,
set_req.V_VAR5],
pres.S_SCENE_CONTROLLER: [set_req.V_SCENE_ON,
set_req.V_SCENE_OFF],
}
if float(gateway.protocol_version) < 1.5:
map_sv_types.update({
pres.S_AIR_QUALITY: [set_req.V_DUST_LEVEL],
pres.S_DUST: [set_req.V_DUST_LEVEL],
})
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COLOR_SENSOR: [set_req.V_RGB],
pres.S_MULTIMETER: [set_req.V_VOLTAGE,
set_req.V_CURRENT,
set_req.V_IMPEDANCE],
pres.S_SOUND: [set_req.V_LEVEL],
pres.S_VIBRATION: [set_req.V_LEVEL],
pres.S_MOISTURE: [set_req.V_LEVEL],
pres.S_AIR_QUALITY: [set_req.V_LEVEL],
pres.S_DUST: [set_req.V_LEVEL],
})
map_sv_types[pres.S_LIGHT_LEVEL].append(set_req.V_LEVEL)
if float(gateway.protocol_version) >= 2.0:
map_sv_types.update({
pres.S_INFO: [set_req.V_TEXT],
pres.S_GAS: [set_req.V_FLOW, set_req.V_VOLUME],
pres.S_GPS: [set_req.V_POSITION],
pres.S_WATER_QUALITY: [set_req.V_TEMP, set_req.V_PH,
set_req.V_ORP, set_req.V_EC]
})
map_sv_types[pres.S_CUSTOM].append(set_req.V_CUSTOM)
map_sv_types[pres.S_POWER].extend(
[set_req.V_VAR, set_req.V_VA, set_req.V_POWER_FACTOR])
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsSensor, add_devices))
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsSensor, add_devices=add_devices)
class MySensorsSensor(mysensors.MySensorsDeviceEntity, Entity):
class MySensorsSensor(mysensors.MySensorsEntity):
"""Representation of a MySensors Sensor child node."""
@property
+2 -1
View File
@@ -38,11 +38,12 @@ SENSOR_TYPES = {
'running', 0],
'processes_blocked': ['Processes Blocked', 'Count', 'system.processes',
'blocked', 0],
'system_load': ['System Load', '15 min', 'system.processes', 'running', 2],
'system_load': ['System Load', '15 min', 'system.load', 'load15', 2],
'system_io_in': ['System IO In', 'Count', 'system.io', 'in', 0],
'system_io_out': ['System IO Out', 'Count', 'system.io', 'out', 0],
'ipv4_in': ['IPv4 In', 'kb/s', 'system.ipv4', 'received', 0],
'ipv4_out': ['IPv4 Out', 'kb/s', 'system.ipv4', 'sent', 0],
'disk_free': ['Disk Free', 'GiB', 'disk_space._', 'avail', 2],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+17 -1
View File
@@ -20,6 +20,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DOMAIN = "octoprint"
DEFAULT_NAME = 'OctoPrint'
NOTIFICATION_ID = 'octoprint_notification'
NOTIFICATION_TITLE = 'OctoPrint sensor setup error'
SENSOR_TYPES = {
'Temperatures': ['printer', 'temperature', '*', TEMP_CELSIUS],
@@ -42,12 +44,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
octoprint_api = hass.data[DOMAIN]["api"]
name = config.get(CONF_NAME)
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)
tools = octoprint_api.get_tools()
_LOGGER.error(str(tools))
if "Temperatures" in monitored_conditions:
if not tools:
hass.components.persistent_notification.create(
'Your printer appears to be offline.<br />'
'If you do not want to have your printer on <br />'
' at all times, and you would like to monitor <br /> '
'temperatures, please add <br />'
'bed and/or number&#95of&#95tools to your config <br />'
'and restart.',
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
devices = []
types = ["actual", "target"]
for octo_type in monitored_conditions:
if octo_type == "Temperatures":
for tool in octoprint_api.get_tools():
for tool in tools:
for temp_type in types:
new_sensor = OctoPrintSensor(
octoprint_api, temp_type, temp_type, name,
+11 -12
View File
@@ -18,7 +18,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import get_component
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'
@@ -88,7 +87,7 @@ def setup_sabnzbd(base_url, apikey, name, hass, config, add_devices, sab_api):
def request_configuration(host, name, hass, config, add_devices, sab_api):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host],
@@ -114,7 +113,6 @@ def request_configuration(host, name, hass, config, add_devices, sab_api):
hass.async_add_job(success)
_CONFIGURING[host] = configurator.request_config(
hass,
DEFAULT_NAME,
sabnzbd_configuration_callback,
description=('Enter the API Key'),
@@ -130,15 +128,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SABnzbd platform."""
from pysabnzbd import SabnzbdApi
host = config.get(CONF_HOST) or discovery_info.get(CONF_HOST)
port = config.get(CONF_PORT) or discovery_info.get(CONF_PORT)
name = config.get(CONF_NAME, DEFAULT_NAME)
use_ssl = DEFAULT_SSL
if config.get(CONF_SSL):
use_ssl = True
elif discovery_info.get('properties', {}).get('https', '0') == '1':
use_ssl = True
if discovery_info is not None:
host = discovery_info.get(CONF_HOST)
port = discovery_info.get(CONF_PORT)
name = DEFAULT_NAME
use_ssl = discovery_info.get('properties', {}).get('https', '0') == '1'
else:
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
name = config.get(CONF_NAME, DEFAULT_NAME)
use_ssl = config.get(CONF_SSL)
uri_scheme = 'https://' if use_ssl else 'http://'
base_url = "{}{}:{}/".format(uri_scheme, host, port)
+39 -10
View File
@@ -13,7 +13,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_UNIT_OF_MEASUREMENT)
CONF_HOST, CONF_NAME, CONF_PORT, CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN,
CONF_VALUE_TEMPLATE)
REQUIREMENTS = ['pysnmp==4.3.9']
@@ -22,6 +23,8 @@ _LOGGER = logging.getLogger(__name__)
CONF_BASEOID = 'baseoid'
CONF_COMMUNITY = 'community'
CONF_VERSION = 'version'
CONF_ACCEPT_ERRORS = 'accept_errors'
CONF_DEFAULT_VALUE = 'default_value'
DEFAULT_COMMUNITY = 'public'
DEFAULT_HOST = 'localhost'
@@ -45,6 +48,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION):
vol.In(SNMP_VERSIONS),
vol.Optional(CONF_ACCEPT_ERRORS, default=False): cv.boolean,
vol.Optional(CONF_DEFAULT_VALUE): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template
})
@@ -61,6 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
baseoid = config.get(CONF_BASEOID)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
version = config.get(CONF_VERSION)
accept_errors = config.get(CONF_ACCEPT_ERRORS)
default_value = config.get(CONF_DEFAULT_VALUE)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
errindication, _, _, _ = next(
getCmd(SnmpEngine(),
@@ -69,23 +81,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
ContextData(),
ObjectType(ObjectIdentity(baseoid))))
if errindication:
if errindication and not accept_errors:
_LOGGER.error("Please check the details in the configuration file")
return False
else:
data = SnmpData(host, port, community, baseoid, version)
add_devices([SnmpSensor(data, name, unit)], True)
data = SnmpData(
host, port, community, baseoid, version, accept_errors,
default_value)
add_devices([SnmpSensor(data, name, unit, value_template)], True)
class SnmpSensor(Entity):
"""Representation of a SNMP sensor."""
def __init__(self, data, name, unit_of_measurement):
def __init__(self, data, name, unit_of_measurement,
value_template):
"""Initialize the sensor."""
self.data = data
self._name = name
self._state = None
self._unit_of_measurement = unit_of_measurement
self._value_template = value_template
@property
def name(self):
@@ -105,19 +121,30 @@ class SnmpSensor(Entity):
def update(self):
"""Get the latest data and updates the states."""
self.data.update()
self._state = self.data.value
value = self.data.value
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
value = self._value_template.render_with_possible_json_value(
value, STATE_UNKNOWN)
self._state = value
class SnmpData(object):
"""Get the latest data and update the states."""
def __init__(self, host, port, community, baseoid, version):
def __init__(self, host, port, community, baseoid, version, accept_errors,
default_value):
"""Initialize the data object."""
self._host = host
self._port = port
self._community = community
self._baseoid = baseoid
self._version = SNMP_VERSIONS[version]
self._accept_errors = accept_errors
self._default_value = default_value
self.value = None
def update(self):
@@ -133,11 +160,13 @@ class SnmpData(object):
ObjectType(ObjectIdentity(self._baseoid)))
)
if errindication:
if errindication and not self._accept_errors:
_LOGGER.error("SNMP error: %s", errindication)
elif errstatus:
elif errstatus and not self._accept_errors:
_LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(),
errindex and restable[-1][int(errindex) - 1] or '?')
elif (errindication or errstatus) and self._accept_errors:
self.value = self._default_value
else:
for resrow in restable:
self.value = resrow[-1]
self.value = str(resrow[-1])
@@ -136,7 +136,7 @@ class PublicTransportData(object):
'fields[]=connections/from/departureTimestamp/&' +
'fields[]=connections/',
timeout=10)
connections = response.json()['connections'][:2]
connections = response.json()['connections'][1:3]
try:
self.times = [
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
REQUIREMENTS = ['uber_rides==0.4.1']
REQUIREMENTS = ['uber_rides==0.5.1']
_LOGGER = logging.getLogger(__name__)
@@ -180,9 +180,12 @@ class UkTransportLiveBusTimeSensor(UkTransportSensor):
'estimated': departure['best_departure_estimate']
})
self._state = min(map(
_delta_mins, [bus['scheduled'] for bus in self._next_buses]
))
if self._next_buses:
self._state = min(
_delta_mins(bus['scheduled'])
for bus in self._next_buses)
else:
self._state = None
@property
def device_state_attributes(self):
@@ -242,10 +245,12 @@ class UkTransportLiveTrainTimeSensor(UkTransportSensor):
'operator_name': departure['operator_name']
})
self._state = min(map(
_delta_mins,
[train['scheduled'] for train in self._next_trains]
))
if self._next_trains:
self._state = min(
_delta_mins(train['scheduled'])
for train in self._next_trains)
else:
self._state = None
@property
def device_state_attributes(self):
+42 -47
View File
@@ -6,65 +6,44 @@ https://home-assistant.io/components/sensor.usps/
"""
from collections import defaultdict
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
ATTR_ATTRIBUTION)
from homeassistant.components.usps import DATA_USPS
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DATE
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from homeassistant.util.dt import now, parse_datetime
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['myusps==1.1.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'usps'
SCAN_INTERVAL = timedelta(minutes=30)
COOKIE = 'usps_cookies.pickle'
DEPENDENCIES = ['usps']
STATUS_DELIVERED = 'delivered'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME): cv.string
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the USPS platform."""
import myusps
try:
cookie = hass.config.path(COOKIE)
session = myusps.get_session(
config.get(CONF_USERNAME), config.get(CONF_PASSWORD),
cookie_path=cookie)
except myusps.USPSError:
_LOGGER.exception('Could not connect to My USPS')
return False
if discovery_info is None:
return
add_devices([USPSPackageSensor(session, config.get(CONF_NAME)),
USPSMailSensor(session, config.get(CONF_NAME))], True)
usps = hass.data[DATA_USPS]
add_devices([USPSPackageSensor(usps),
USPSMailSensor(usps)], True)
class USPSPackageSensor(Entity):
"""USPS Package Sensor."""
def __init__(self, session, name):
def __init__(self, usps):
"""Initialize the sensor."""
self._session = session
self._name = name
self._usps = usps
self._name = self._usps.name
self._attributes = None
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return '{} packages'.format(self._name or DOMAIN)
return '{} packages'.format(self._name)
@property
def state(self):
@@ -73,16 +52,16 @@ class USPSPackageSensor(Entity):
def update(self):
"""Update device state."""
import myusps
self._usps.update()
status_counts = defaultdict(int)
for package in myusps.get_packages(self._session):
for package in self._usps.packages:
status = slugify(package['primary_status'])
if status == STATUS_DELIVERED and \
parse_datetime(package['date']).date() < now().date():
continue
status_counts[status] += 1
self._attributes = {
ATTR_ATTRIBUTION: myusps.ATTRIBUTION
ATTR_ATTRIBUTION: self._usps.attribution
}
self._attributes.update(status_counts)
self._state = sum(status_counts.values())
@@ -97,21 +76,26 @@ class USPSPackageSensor(Entity):
"""Icon to use in the frontend."""
return 'mdi:package-variant-closed'
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return 'packages'
class USPSMailSensor(Entity):
"""USPS Mail Sensor."""
def __init__(self, session, name):
def __init__(self, usps):
"""Initialize the sensor."""
self._session = session
self._name = name
self._usps = usps
self._name = self._usps.name
self._attributes = None
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return '{} mail'.format(self._name or DOMAIN)
return '{} mail'.format(self._name)
@property
def state(self):
@@ -120,18 +104,29 @@ class USPSMailSensor(Entity):
def update(self):
"""Update device state."""
import myusps
self._state = len(myusps.get_mail(self._session))
self._usps.update()
if self._usps.mail is not None:
self._state = len(self._usps.mail)
else:
self._state = 0
@property
def device_state_attributes(self):
"""Return the state attributes."""
import myusps
return {
ATTR_ATTRIBUTION: myusps.ATTRIBUTION
}
attr = {}
attr[ATTR_ATTRIBUTION] = self._usps.attribution
try:
attr[ATTR_DATE] = self._usps.mail[0]['date']
except IndexError:
pass
return attr
@property
def icon(self):
"""Icon to use in the frontend."""
return 'mdi:mailbox'
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return 'pieces'
@@ -0,0 +1,55 @@
"""
Support for displaying the current version of Home Assistant.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.version/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import __version__, CONF_NAME
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Current Version"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Version sensor platform."""
name = config.get(CONF_NAME)
async_add_devices([VersionSensor(name)])
class VersionSensor(Entity):
"""Representation of a Home Assistant version sensor."""
def __init__(self, name):
"""Initialize the Version sensor."""
self._name = name
self._state = __version__
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@@ -0,0 +1,113 @@
"""
This component provides HA sensor support for the worldtides.info API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.worldtidesinfo/
"""
import logging
import time
from datetime import timedelta
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE,
CONF_NAME, STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'WorldTidesInfo'
SCAN_INTERVAL = timedelta(seconds=3600)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the WorldTidesInfo sensor."""
name = config.get(CONF_NAME)
lat = config.get(CONF_LATITUDE, hass.config.latitude)
lon = config.get(CONF_LONGITUDE, hass.config.longitude)
key = config.get(CONF_API_KEY)
if None in (lat, lon):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
add_devices([WorldTidesInfoSensor(name, lat, lon, key)], True)
class WorldTidesInfoSensor(Entity):
"""Representation of a WorldTidesInfo sensor."""
def __init__(self, name, lat, lon, key):
"""Initialize the sensor."""
self._name = name
self._lat = lat
self._lon = lon
self._key = key
self.data = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def device_state_attributes(self):
"""Return the state attributes of this device."""
attr = {}
if "High" in str(self.data['extremes'][0]['type']):
attr['high_tide_time_utc'] = self.data['extremes'][0]['date']
attr['high_tide_height'] = self.data['extremes'][0]['height']
attr['low_tide_time_utc'] = self.data['extremes'][1]['date']
attr['low_tide_height'] = self.data['extremes'][1]['height']
elif "Low" in str(self.data['extremes'][0]['type']):
attr['high_tide_time_utc'] = self.data['extremes'][1]['date']
attr['high_tide_height'] = self.data['extremes'][1]['height']
attr['low_tide_time_utc'] = self.data['extremes'][0]['date']
attr['low_tide_height'] = self.data['extremes'][0]['height']
return attr
@property
def state(self):
"""Return the state of the device."""
if self.data:
if "High" in str(self.data['extremes'][0]['type']):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][0]['dt']))
return "High tide at %s" % (tidetime)
elif "Low" in str(self.data['extremes'][0]['type']):
tidetime = time.strftime('%I:%M %p', time.localtime(
self.data['extremes'][1]['dt']))
return "Low tide at %s" % (tidetime)
else:
return STATE_UNKNOWN
else:
return STATE_UNKNOWN
def update(self):
"""Get the latest data from WorldTidesInfo API."""
start = int(time.time())
resource = 'https://www.worldtides.info/api?extremes&length=86400' \
'&key=%s&lat=%s&lon=%s&start=%s' % (self._key, self._lat,
self._lon, start)
try:
self.data = requests.get(resource, timeout=10).json()
_LOGGER.debug("Data = %s", self.data)
_LOGGER.info("Tide data queried with start time set to: %s",
(start))
except ValueError as err:
_LOGGER.error("Check WorldTidesInfo %s", err.args)
self.data = None
raise
@@ -10,7 +10,6 @@ import os
from datetime import timedelta
from homeassistant.components.switch import SwitchDevice
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
@@ -51,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(
device_id, insteonhub, model, hass, add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
@@ -66,7 +65,7 @@ def request_configuration(
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon Switch ' + model + ' addr: ' + device_id,
'Insteon Switch ' + model + ' addr: ' + device_id,
insteon_switch_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
@@ -79,7 +78,7 @@ def setup_switch(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the switch."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator = hass.components.configurator
configurator.request_done(request_id)
_LOGGER.info("Device configuration done")

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