Compare commits

..

386 Commits

Author SHA1 Message Date
Paulus Schoutsen
db3bfad0b5 Merge pull request #3359 from home-assistant/hotfix-0-28-2
0.28.2
2016-09-12 21:28:57 -07:00
Teagan Glenn
2b1416c514 Automatic polling (#3360)
* Test updating automatic

* Scan interval

* Schedule scan every time delta

* Pass around has

* Recursive issue

* Method invocation

* Oops

* Set up poll

* Default argument value

* Unused import

* Semicolon

* Fix tests

* Linting

* Unneeded throttle as it's handled by time event

* Use track time change event listener

* Disable lint rule

* Attribute removed - removing test

* Debug instead of info

* Unused import
2016-09-12 19:59:53 -07:00
Paulus Schoutsen
b9154158e8 Version bump to 0.28.2 2016-09-12 18:33:36 -07:00
Nick Vella
b43bf62347 Add open/closed state for open_cover and close_cover in SERVICE_TO_STATE (#3180)
* Add open/closed state mapping for open_cover and close_cover

* Add 'open', 'closed' for open/close_cover_tilt

* Revert "Add 'open', 'closed' for open/close_cover_tilt"

This reverts commit e45582d439.
2016-09-12 18:33:11 -07:00
John Arild Berentsen
c6b6ab1b79 Bugfix ecobee: inverted high and low temps and enforce int to temps (#3325)
* inverted high and low temps

* Looks like somethings are mixed up

* Added debugentires

* Added debugentires 2

* Enforce int on temperatures
2016-09-12 18:33:11 -07:00
John Arild Berentsen
07148fc580 Missing garage door detection (#3349) 2016-09-12 18:33:11 -07:00
Pascal Vizeli
bc600b8f32 Bugfix voluptuous on recorder (#3350) 2016-09-12 18:33:11 -07:00
David-Leon Pohl
dd4611064f Bugfix pilight component (#3355)
* BUG Message data cannot be changed thus use voluptuous to ensure format

* Pilight daemon expects JSON serializable data

Thus dict is needed and not a mapping proxy.

* Add explanation why dict as message data is needed

* Use more obvious voluptuous validation scheme

* Pylint:  Trailing whitespace
2016-09-12 18:33:11 -07:00
Paulus Schoutsen
71aa1a2f3c Merge pull request #3332 from home-assistant/hotfix-0-28-1
Hotfix 0 28 1
2016-09-11 22:42:16 -07:00
Paulus Schoutsen
0cfa5e5f67 Version bump to 0.28.1 2016-09-11 22:28:53 -07:00
Paulus Schoutsen
f0ec51711c Bugfix group order (#3323)
* Add ordered dict config validator

* Have group component use ordered dict config validator

* Improve config_validation testing

* update doc string config_validation.ordered_dict

* validate full dict entries

* Further simplify ordered_dict validator.

* Lint fix
2016-09-11 22:27:47 -07:00
Teagan Glenn
d6ca930427 Automatic Device Tracker Bug Fix (#3330)
* Iterate over items

* Pass display name as host name
2016-09-11 22:27:47 -07:00
John Arild Berentsen
7bce8bc33f Revert only add 1 device (#3324) 2016-09-11 22:27:47 -07:00
Marcelo Moreira de Mello
36785296ce Fixed voluptuous to accept string instead positive_int for CODE on Simplisafe (#3310) 2016-09-11 22:27:47 -07:00
Fabian Affolter
1b46ed5045 0.28 (#3288)
* Backend support for importing waypoints from owntracks as HA zones

* Added test for Owntracks waypoints import

* Backend support for importing waypoints from owntracks as HA zones

* Added test for Owntracks waypoints import

* Removed redundant assignment to CONF_WAYPOINT_IMPORT_USER

* Fixed zone test break and code style issues

* Fixed style issues

* Fixed variable scope issues for entities

* Fixed E302

* Do not install pip packages in tests

* EventBus: return function to unlisten

* Convert automation to entities with services

* Refactored zone creation based on code review feedback, enhanced configuration

* Added unit test to enhance waypoint_whitelist coverage

* Fix JSON encoder issue in recorder

* Fix tests docstring

* * Improved zone naming in waypoint import
* Added more test coverage for owntracks and zone

* Back to 0.28.0.dev0

* Code review feedback from @pavoni

* Added bitfield of features for flux_led since we are supporting effects

* Host should be optional for apcupsd component (#3072)

* Use voluptuous for file (#3049)

* Zwave climate Bugfix: if some setpoints have different units, we should fetch the o… (#3078)

* Bugfix: if some setpoints have different units, we should fetch the one that are active.

* Move order of population for first time detection

* Default to config if None unit_of_measurement

* unit fix (#3083)

* humidity slider (#3088)

* If device was off target temp was null. Default to Heating setpoint (#3091)

* Fix linting

* Upgrade pyuserinput to 0.1.11 (#3068)

* Upgrade pyowm to 2.4.0 (#3067)

* improve isfile validation check (#3101)

* Refactor notification titles to allow for them to be None, this also includes a change in Telegram to only include the title if it's present, and to use a Markdown parse mode for messages (#3100)

* Fix broken test

* rfxtrx sensor clean up

* Bitcoin sensor use warning instead of error (#3103)

* Use voluptuous for HDMI CEC & CONF_DEVICES constants (#3107)

* Update voluptuous for nest (#3109)

* Update configuration check
* Extend platform

* Fix for BLE device tracker (#3019)

* Bug fix tracked devices
* Added scan_duration configuration parameter

* fix homematic climate implementation (#3114)

* Allow 'None' MAC to be loaded from known_devices (#3102)

* Use voluptuous for xmpp (#3127)

* Use voluptuous for twitter (#3126)

* Use voluptuous for Fritzbox and DDWRT (#3122)

* Use Voluptuous for BT Home Hub (#3121)

* Use voluptuous for syslog (#3120)

* Use voluptuous for Aruba (#3119)

* Use constants, update configuration check, and ordering (Pilight) (#3118)

* Use contants, update configuration check, and ordering

* Fix pylint issue

* Migrate to voluptuous (#3113)

* Fix typo (#3108)

* Migrate to voluptuous (#3106)

* Update voluptuous (#3104)

* Climate and cover bugfix (#3097)

* Avoid None comparison for zwave cover.

* Just rely on unit from config for unit_of_measurement

* Explicit return None

* Mqtt (#11)

* Explicit return None

* Missing service and wrong service name defined

* Mqtt state was inverted, and never triggering

* Migrate to voluptuous (#3096)

* Migrate to voluptuous (#3084)

* Fixed Homematic cover (#3116)

* Migrate to voluptuous (#3069)

🐬

* Migrate to voluptuous (#3066)

🐬

* snapcast update (#3012)

* snapcast update

* snapcast update

* validate config

* use conf constants

* orvibo updates (#3006)

🐬

* Update frontend

* move units to temperature for climate zwave. wrong state was sent to mqtt cove

* Use voluptuous for instapush (#3132)

* Use voluptuous for Octoprint (#3111)

* Migrate to voluptuous

* Fix pylint issues

* Add missing docstrings (fix PEP257 issues) (#3098)

* Add missing docstrings (fix PEP257 issues)

* Finish sentence

* Updated braviatv's braviarc version to 0.3.4 (#2997)

* Updated braviarc version to 0.3.4

* Updated braviarc version to requirements_all.txt

* Use voluptuous for Acer projector switch (#3077)

🐬

* Use voluptuous for twilio (#3134)

* Use voluptuous for webostv (#3135)

* Use voluptuous for Command line platforms (#2968)

* Migrate to voluptuous

* Fix pylint issues

* Remove FIXME

* Split setup test

* Test with bootstrap

* Remove lon and lat

* Fix pylint issues

* Add coinmarketcap sensor (#3064)

* Migrate to voluptuous (#3142)

🐬

* Back out insteon hub and fan changes (#3062)

* Move details to docs (#3146)

* Update frontend

* Use constants (#3148)

* Update ordering (#3149)

* Migrate to voluptuous (#3092)

* Display the error instead of the traceback (notify.slack) (#3079)

* Display the error instead of the traceback

* Remove name for check

* Automatic ODB device tracker & device tracker attributes (#3035)

* Migrate to voluptuous (#3173)

* Add voluptuous for tomato and SNMP (#3172)

* Improve voluptuous and login errors for Asus device tracker (#3170)

* Add exclude option to nmap device tracker (#2983)

* Add exclude option to nmap device tracker

Adds an optional exclude paramater to nmap device tracker.
Devices specified in the exclude list will never be scanned
by nmap. This can help to reduce log spam.

ex:
```
device_tracker:
  - platform: nmap_tracker
    hosts: 10.0.0.1/24
    home_interval: 1
    interval_seconds: 12
    consider_home: 120
    track_new_devices: yes
    exclude:
      - 10.0.0.2
      - 10.0.0.1
```

* Handle optional exclude

* Style fixed

* Added Xbox Live component (#3013)

* Added Xbox Live component

* Added Xbox Live sensor to coveralls

* Added init success checks

* Added entity id

* Adding link_names to post.message call (#3167)

If you do not turn link_names on, Slack will not highlight @channel and @username messages.

* Allow https (fixes #3150) (#3155)

* Use constants (#3156)

* Bugfix: ctach Runtime errors (#3153)

"RuntimeError: Disable scan failed" has been seen in a live installation

* Migrate to voluptuous (#3166)

🐬

* Migrate to voluptuous (#3164)

🐬

* Migrate to voluptuous (#3163)

🐬

* Migrate to voluptuous (#3162)

🐬 and 🍪 for fixing quotes!

* Exclude www_static from pydocstyle linting (#3175)

🐬

* Migrate to voluptuous (#3174)

* Migrate to voluptuous (#3171)

* Use voluptuous for mFi switch (#3168)

* Migrate to voluptuous

* Take change configuration into account

* Migrate to voluptuous (#3144)

🐬

* Add the occupancy sensor_class (#3176)

Such a complicated PR

* Update frontend

* Use voluptuous for Unifi, Ubus (#3125)

* Using alert with Hue maintains prior state (#3147)

* When using flash with hue, dont change the on/off state of the light so that it will naturally return to its previous state once flash is complete

* ATTR_FLASH not ATTR_EFFECT

* MQTT fan platform (#3095)

* Add fan.mqtt, allow brightness to be passed and mapped to a fan speed for compatibility with emulated_hue

* Pylint/Flake8 fixes

* Remove brightness

* Add more features, like custom oscillation/speed payloads and setting the speed list

* Flake8 fixes

* flake8/pylint fixes

* Use constants

* block fan.mqtt from coverage

* Fix oscillating comment

* Add Sphinx API doc generation (#3029)

* add's sphinx project to docs/ dir
* include core/helpers autodocs for API reference

* Allow reloading automation without restarting HA (#3002)

* Migrate to voluptuous (#3182)

🐬

* Migrate to voluptuous (#3179)

🐬

* Added scale and offset to the Temper component (#2853)

🐬

* Use voluptuous for BT and Owntracks device trackers (#3187)

🐬

* Correct binary_sensor.ecobee docs URL

* Use voluptuous for Hikvisioncam switch (#3184)

* Migrate to voluptuous

* Use vol.Optional

* Use voluptuous for Edimax (#3178)

🐬

* Use voluptuous for Bravia TV (#3165)

🐬

* Added support to 'effect: random' to Osram Lightify lights (#3192)

* Added support to 'effect: random' to Osram Lightify lights

* removed extra line not required

* Use voluptuous for message_bird, sendgrid (#3136)

* Try out the RTD theme

* Doc updates

* Update voluptuous for existing notify platforms (#3133)

* Update voluptuous for exists notify platforms

* fix constants

* Simple trend sensor. (#3073)

* First cut of trend sensor.

* Tidy.

* Migrate to voluptuous (#3193)

* Migrate to voluptuous (#3194)

🐬

* Migrate to voluptuous (#3197)

* Migrate to voluptuous (#3198)

🐬

* Use extend of PLATFORM_SCHEMA (#3199)

* Migrate to voluptuous (#3202)

🐬

* Updated to use the occupancy sensor_class (#3204)

🐬

* Migrate to voluptuous (#3206)

* Migrate to voluptuous (#3207)

* Migrate to voluptuous (#3208)

🐬

* Migrate to voluptuous (#3209)

🐬

* Migrate to voluptuous (#3214)

* Use voluptuous for SqueezeBox (#3212)

* Migrate to voluptuous

* Remove name

* Migrate to voluptuous and upgrade uber_rides to 0.2.5 (#3181)

* Migrate to voluptuous (#3200)

🐬

* Use Voluptuous for Luci and Netgear device trackers (#3123)

* Use Voluptuous for Luci and NEtgear device trackers

* str_schema shortcut

* Undo str_schema

* change update handling with variable for breack CCU2 (#3215)

* Update ordering (#3216)

* Docs update

* Flake8/pylint

* Add new docs requirements

* Update email validation (#3228)

🐬

* Fix email validation (fixes #3138) (#3227)

* Upgrade slacker to 0.9.25 (#3224)

* Upgrade psutil to 4.3.1 (#3223)

* Upgrade gps3 to 0.33.3 (#3222)

* Upgrade Werkzeug to 0.11.11 (#3220)

* Upgrade sendgrid to 3.4.0 (#3226)

* Bluetooth: keep looking for new devices (#3201)

* keep looking for new devices

* Update bluetooth_tracker.py

* change default value for tracking new devices

* remove commented code

* dlink switch added device state attributes and support for legacy firmware (#3211)

* Use voluptuous for free mobile (#3236)

* Use voluptuous for nma (#3241)

* Improve 1-Wire device family detection and error checking. Use volupt… (#3233)

* Improve 1-Wire device family detection and error checking. Use voluptuous

* Fix detection of gpio connected devices

* Replace rollershutter and garage door with cover, add fan (#3242)

* Use voluptuous for Alarm.com (#3229)

* Use voluptuous for gntp (#3237)

* Use voluptuous for pushbullet, pushetta and pushover (#3240)

* Migrate to voluptuous (#3230)

🐬

* Fix mFi sensors in uninitialized state (#3246)

If mFi sensors are identified but not fully assigned they can
have no tag value, and mficlient throws a ValueError to signal this.
This patch handles that case by considering such devices to always
be STATE_OFF.

* Use voluptuous for PulseAudio Loopback (#3160)

* Migrate to voluptuous

* Fix conf var

* Use voluptuous for Verisure (#3169)

* Migrate to voluptuous

* Update type and add missing config variable

* thread safe modbus (#3188)

*  Upgraded fitbit to version 0.2.3 which fixed oauthlib.oauth2.rfc6749.errors.TokenExpiredError: (token_expired) (#3244)

* update ffmpeg version to 0.10 add get image to camera (#3235)

* Migrate to voluptuous (#3234)

* fix bugfix with unique_id (#3217)

* Zwave climate fix and wink cover. (#3205)

* Fixes setpoint get was done outside loop

* zxt_120

* Wink not migrated to cover

* Clarifying debug

* too long line

* Only add 1 device entity

* Owntracks voluptuous fix (#3191)

* Zwave set temperature fix (#3221)

* If device was off set target temp would not work.

* Changed to use a workaround just for Horstmann HRT4-ZW Zwave Thermostat

* Wrong Horseman id

* style changes

* Change PR to suggestion on gitter (#3243)

* Reload groups (#3203)

* Allow reloading groups without restart

* Test to make sure automation listeners are removed.

* Remove unused imports for group tests

* Simplify group config validation

* Add prepare_reload function to entity component

* Migrate group to use entity_component.prepare_reload

* Migrate automation to use entity_component.prepare_reload

* Clean up group.get_entity_ids

* Use cv.boolean for group config validation

* fix remove listener (#3196)

* Add linux battery sensor (#3238)

* protect service data for changes in calls (#3249)

* protect service data for changes in calls

* change handling

* move MappingProxyType to service call

* Fix issue #3250 (#3253)

* Minor Ecobee changes (#3131)

* Update configuration check, ordering, and constants

* Make API key optional

* issue #3250

* Add voluptuous to ecobee (#3257)

* Use constants and update ordering (#3261)

* Add support for complex template structures to data_template (#3255)

* Improve yaml fault tolerance and handle check_config border cases (#3159)

* Use voluptuous for nx584 alarm (#3231)

* Migrate to voluptuous

* Fix pylint issue

* fastdotcom from pypi (#3269)

* Use constants and update ordering (#3268)

🐬

* Use constants and update ordering (#3267)

🐬

* Add additional template for custom date formats (#3262)

I can live with a few visual line breaks 🐬

* Use constants and update ordering (#3266)

* Updated  braviatv's braviarc version to 0.3.5 (#3271)

* Use voluptuous for Device Sun Light Trigger (#3105)

* Migrate to voluptuous

* Use default

* Point to master till archive is back (#3285)

* Pi-Hole statistics sensor (#3158)

* Add Pi-Hole sensor

* Update docstrings and remove print()

* Use None for payload

* Added stuff for support range setting (#3189)

* cleanup Homematic code (#3291)

* cleanup old code

* cleanup round 2

* remove unwanted platforms

* Update frontend

* Hotfix for #3100 (#3302)

* Fix TP-Link Archer C7 long passwords (#3225)

* Fix tplink C7 long passwords

Fixes an issue where passwords longer than 15 chars could not log in to Archer C7 routers.

* Truncate in correct place

* Add comment about TP-Link C7 pass truncation

* Fix lint error

* Truncate comment at 79 chars not 80

* modbus write registers service (#3252)

* Fix bloomsky platform discovery (#3303)

* Remove dev tag
2016-09-10 18:22:58 -07:00
Robbie Trencheny
64cc4a47ec 0.27.2 (#3151)
* Host should be optional for apcupsd component (#3072)

* Zwave climate Bugfix: if some setpoints have different units, we should fetch the o… (#3078)

* Bugfix: if some setpoints have different units, we should fetch the one that are active.

* Move order of population for first time detection

* Default to config if None unit_of_measurement

* unit fix (#3083)

* humidity slider (#3088)

* If device was off target temp was null. Default to Heating setpoint (#3091)

* Fix for BLE device tracker (#3019)

* Bug fix tracked devices
* Added scan_duration configuration parameter

* fix homematic climate implementation (#3114)

* Allow 'None' MAC to be loaded from known_devices (#3102)

* Climate and cover bugfix (#3097)

* Avoid None comparison for zwave cover.

* Just rely on unit from config for unit_of_measurement

* Explicit return None

* Mqtt (#11)

* Explicit return None

* Missing service and wrong service name defined

* Mqtt state was inverted, and never triggering

* Fixed Homematic cover (#3116)

* Add missing docstrings (fix PEP257 issues) (#3098)

* Add missing docstrings (fix PEP257 issues)

* Finish sentence

* Merge pull request #3130 from turbokongen/zwave_fixes

Bugfix. climate and covermqt

* Back out insteon hub and fan changes (#3062)

* Bump version

* Special frontend build for 0.27.2
2016-09-03 15:59:20 -07:00
Robbie Trencheny
dfc38b76a4 Merge pull request #3060 from home-assistant/dev
0.27.1
2016-08-30 14:22:01 -07:00
Robbie Trencheny
e9354bb1e8 Make pep8 happy 2016-08-30 13:58:53 -07:00
Robbie Trencheny
d907902af8 0.27.1 NOT 0.28.1, thanks for the catch @arsaboo 2016-08-30 13:44:35 -07:00
Robbie Trencheny
9a4447ca13 0.28.1 2016-08-30 13:37:47 -07:00
Fabian Affolter
eec96ea137 Migrate to voluptuous (#2954) 2016-08-30 21:34:33 +02:00
John Arild Berentsen
7ceb22a08b Ecobee (#3055)
* Added in list for opreation

* Added attribute to reflect the old operation

* fix humidity
2016-08-30 21:04:53 +02:00
Pascal Vizeli
cf9b49ac03 update ha-ffmpeg version to 0.9 (#3059) 2016-08-30 20:58:37 +02:00
Johann Kellerman
55d305359e Device tracker component & platform validation. No more home_range. (#2908)
* Device tracker component & platform validation. No more home_range.

* Mock, bluetooth

* Renamed _CONFIG_SCHEMA. Raise warning for #1606

* test duplicates

* Fix assert

* Coverage

* Typing

* T fixes
2016-08-30 10:22:52 -06:00
Daniel Høyer Iversen
16e0187fcc Merge pull request #3051 from tchellomello/added_random_effect_flux_led
Added option to use effect:random for Flux Led light bulbs
2016-08-30 09:52:50 +02:00
Marcelo Moreira de Mello
650ec1a337 Added option to use effect:random for Flux Led light bulbs 2016-08-29 19:55:01 -04:00
Pascal Vizeli
4e044361c3 Use voluptuous for smtp (#3048)
Make note of the breaking change in release notes
2016-08-29 16:56:40 -06:00
Fabian Affolter
c1794d111e Upgrade sendgrid to 3.2.10 (#3044) 2016-08-29 14:16:18 -06:00
Fabian Affolter
008e3000bb Upgrade TwitterAPI to 2.4.2 (#3043) 2016-08-29 14:16:10 -06:00
Greg Dowling
1b718c62a3 Fix bug in wemo discovery caused by voluptuous addition. (#3027) 2016-08-29 07:45:48 -06:00
Daniel Høyer Iversen
6275cffab4 Merge pull request #3036 from home-assistant/bug_fix_asuwrt
Bug fix for asuswrt device_tracker. Issue #3015
2016-08-29 08:36:42 +02:00
Daniel
62bbda1f82 Bug fix for asuswrt device_tracker. Issue #3015 2016-08-29 08:23:20 +02:00
arsaboo
39402aff2e Remove units for humidity in Wundeground sensor (#3018)
* Remove units for humidity

Wunderground returns the information with the units.

* Trim the % from the return value of humidity
2016-08-28 21:05:28 -06:00
Martin Hjelmare
1699885907 Fix media_player descriptions and select_source (#3030)
🐬
2016-08-28 20:00:43 -06:00
Teagan Glenn
b6ad0bfbea Allow user to configure server id to perform speed test against (#3008)
* Allow user to configure server id to perform speed test against

* Don't overwrite list

* Type-o

* Convert to string

* Append lists

* str(None) => 'None' did not realize that.
2016-08-28 19:09:34 -06:00
Daniel Høyer Iversen
821b3d7fac Bug fix for asuswrt device_tracker. Issue #3015 (#3016)
🐬
2016-08-28 17:34:01 -06:00
John Arild Berentsen
2d8bc754c8 Ecobee (#3025)
* fix ecobee mode

* Fixup
2016-08-28 22:51:56 +02:00
John Arild Berentsen
2a5ca1c873 Map Modes to setpoint indexes (#3023)
* Map Modes to setpoint indexes

* Fixes devices with no thermostat mode

* another try to fix devices without mode

* another try to fix devices without mode 2

* another try to fix devices without mode 3

* fix setting setpoint for devices with no mode

* fix setting setpoint for devices with no mode
2016-08-28 22:41:48 +02:00
Pascal Vizeli
3313995c4c fix voluptuous and cover autodiscovery (#3022) 2016-08-28 20:00:44 +02:00
John Arild Berentsen
17a57d3b47 Fixes wrong statevalue and problem with zwave setpoint (#3017)
* Fixes wrong statevalue and problem with zwave setpoint

* Fix demo test to match bugfix (#10)
2016-08-28 17:58:50 +02:00
Robbie Trencheny
7940648725 0.28.0.dev0 2016-08-27 21:07:55 -07:00
Robbie Trencheny
7b2f0e709b Merge pull request #2952 from home-assistant/dev
0.27
2016-08-27 20:27:52 -07:00
Robbie Trencheny
78675ed3f8 Version bump to 0.27.0 2016-08-27 20:07:20 -07:00
Robbie Trencheny
43555b646c update frontend 2016-08-27 20:05:44 -07:00
Teagan Glenn
fdb6de4d23 Fan demo (#2976)
* Update attr to property and default state method

* State prop is defined in parent class

* Demo platform fan

* PyDoc

* Copy-pasta artifact

* PyDoc

* Linting

* Raise error if turn_off and turn_on not implemented

* Update demo platform

* Initial unit test commit

* Readability

* Unneeded typing

* Should inherit from fan entity

* Turn off polling

* Initial oscillating flag

* Pass HASS into demo

* Typing

* Invoke set_speed instead of setting directly

* Service update

* Update demo tests

* Forgot to block after service call.

* linting

* Test to make sure not implemented is thrown

* Is On Method test

* Update const to match string

* Update services yaml

* Toggle method

* Toggle service

* Typing

* TYPE O

* Attribute check

* Type-o

* Type-o

* Put typing back

* ToggleEntity

* Linting

* Linting

* Oops

* Stale prints

* Demo support
2016-08-27 14:53:12 -06:00
Fabian Affolter
a4b90c9879 Use voluptuous for Raspberry Pi and local file camera (#2988)
* Migrate to voluptuous

* Update const.py

* Migrate to voluptuous

* Remove duplicate _LOGGER entry
2016-08-27 14:44:22 -06:00
Fabian Affolter
2accc15d41 Migrate to voluptuous (#2991)
🐬
2016-08-27 14:43:33 -06:00
Fabian Affolter
e6b9d5f5b3 Migrate to voluptuous (#2989)
🍪 🐬
2016-08-27 14:42:34 -06:00
Martin Hjelmare
6acaf25b0d Use voluptuous for mysensors (#2992)
* Add voluptuous config validation for mysensors
* Remove and clean up parts that are not needed for pymysensors 0.7.
2016-08-27 14:41:21 -06:00
Johann Kellerman
6f1c97b9d3 Voluptuous for AsusWRT (#2998)
* Voluptuous for AsusWRT
2016-08-27 22:30:06 +02:00
John Arild Berentsen
f863efdaca Use COMMAND_CLASS_THERMOSTAT_SETPOINT to get unit_of_measurement instad of COMMAND_CLASS_SENSOR_MULTILEVEL. Not all devices have multilevel sensor. (#3003) 2016-08-27 13:39:22 +02:00
Robbie Trencheny
04f0fec352 Merge pull request #2970 from persandstrom/modbus_write
modbus write register service
2016-08-27 03:07:34 -07:00
Per Sandström
2c26514c95 modbus sensor value scaling (#2972) 2016-08-27 02:49:49 -07:00
Matthew Bowen
c05d27d214 Completely local control of entities via Alexa (#2942)
* Initial code for alexa_local_control.

* Added support for creating a dummy username.

* Move SSDP responses to local variables.

* Added config validation via Voluptuous.

* Modify and remove unnecessary returned emulated bridge values.

* Remove script and scene domains from default exposed domains.

* Replaced Flask with HomeAssistantWSGI.

* Fix lint errors.

* Correcting grammar and spelling in docs and comments.

* Rename alexa_local_control to emulated_hue.

* Rename emulated_hue attributes.

* Fix a bug where something marked not exposed is exposed by default.

* Make sure the UPNP responder thread cleanly stops when HASS stops.

Also fix some config loading and lint errors.

* Fixed unexposed entities still having individual state exposed.

* Started writing tests for emulated_hue.

* Fix being able to set state of non-exposed entity.

* Another test for emulated_hue.

* More tests for emulated_hue.

Also slightly simplified emulated_hue's PUT handler.

* Fix bad test, sorry :/

* Third time's the charm.

* Fix lint and value validation tests.

* Rename emulated_hue bridge name.

* Remove license and documentation from header.

* Combine two if statements.

* Style changes.

* Fixed various issues and added some constants
2016-08-27 01:23:40 -07:00
icovada
a4b8c3cab0 Update telegram.py add send_document (#2937)
🐬
2016-08-26 18:52:44 -06:00
Johann Kellerman
4aad83d60b Voluptuous for pushover (#3000) 2016-08-26 17:43:59 -07:00
Johann Kellerman
37048919bf Check config requirement fix (#2999)
* Check config requirement fix
2016-08-27 01:33:57 +02:00
Fabian Affolter
5cc672ea59 Migrate to voluptuous (#2990)
🐬
2016-08-26 14:50:32 -06:00
Per Sandström
ead0559661 Merge pull request #2995 from persandstrom/vsure_10.2
bump vsure version
2016-08-26 21:53:31 +02:00
Per Sandström
4ee37cb8c8 bump vsure version 2016-08-26 21:38:49 +02:00
Pascal Vizeli
9ab2ac766e add motion sensor / rewrite ffmpeg binary sensor (#2969)
🐬 

* use const flags
2016-08-26 06:48:17 -06:00
Pascal Vizeli
d2bb61ad9e Change variable to poll for ccu/homegear (#2987) 2016-08-26 12:17:50 +02:00
Pascal Vizeli
95b98f6752 Full homematic system variable support (#2986) 2016-08-26 10:25:56 +02:00
Martin Hjelmare
877dc9c7b5 Merge pull request #2960 from MartinHjelmare/mysensors-mqtt
Add MQTT gateway for MySensors
2016-08-26 08:10:57 +02:00
Roi Dayan
d611010a6e Fix reading dht config values (#2956)
🐬
2016-08-25 20:09:48 -06:00
Pascal Vizeli
2eadae2039 add homematic hub device with variable support / update pyhomematic with new device / add cover support (#2981) 2016-08-25 21:55:03 +02:00
Daniel Høyer Iversen
354f4b4740 Upgrade rfxtrx lib (#2974) 2016-08-25 10:52:48 -07:00
MartinHjelmare
d1e94b958f Extract mqtt string into constant and add log 2016-08-25 19:07:22 +02:00
Landrash
34f57ebdc9 Fix reference to wrong components in tests for cameras (#1) (#2975)
🐬 👍
2016-08-25 10:55:37 -06:00
Teagan Glenn
826ec9b9d7 Add a Fan component and support for an Insteon Hub Fan (#2964)
* Fan component and service definitions
* Insteon Hub fan support
2016-08-25 14:47:07 +02:00
Per SandstrÃom
be1981ca5d modbus write register service 2016-08-25 08:20:08 +00:00
Johann Kellerman
17631cd728 Check config script: various fixes (#2967)
🐬
2016-08-24 23:18:32 -06:00
Fabian Affolter
b199c61c88 Migrate to voluptuous (#2955)
🐬
2016-08-24 22:36:41 -06:00
Fabian Affolter
9219d65c3e Migrate to voluptuous (#2958) 2016-08-24 22:35:09 -06:00
John Arild Berentsen
d9322b81f3 Bugfixing DemoCover NotImplemented Was not raised for is_closed (#2965)
* NotImplemented was not raised when is_closed was missing

* Bugfixing Demo
2016-08-24 20:36:43 +02:00
Open Home Automation
cc358a5dde Corrected sensor name from HM-Z19 to MH-Z19 (#2963)
Approved
2016-08-24 10:54:34 -06:00
John Arild Berentsen
5d2d9af8e3 Add deprecated warning to thermostat and hvac (#2962)
* Add deprecated warning for thermostat and hvac
2016-08-24 16:30:14 +02:00
Daniel Høyer Iversen
daa066c036 Merge pull request #2959 from home-assistant/rfxtrx
minor bug in rfxtrx sensor
2016-08-24 14:25:07 +02:00
John Arild Berentsen
e5abf6074c Cover (#2957)
* current_position was set, it should be optional

* Update mqtt test to match
2016-08-24 11:53:02 +02:00
Daniel
99796e559e minor bug in rfxtrx sensor 2016-08-24 11:33:57 +02:00
MartinHjelmare
e7b206da0c Add MQTT gateway for MySensors
* Use mqtt component to enable a MySensors MQTT gateway.
* Setup the MQTT gateway if mysensors config has mqtt as a value for
	the key	device in the list of gateways.
* Simplify two lines in the mqtt component.
2016-08-24 10:48:55 +02:00
Greg Dowling
4795122463 Add voluptuous to binary template sensor (#2938)
* Add voluptuous to binary template sensor / update failing test.

* Update tests.

* Quick fixes to remove duplicate variables
2016-08-24 01:16:26 -07:00
Nolan Gilley
61ef2683c5 Add volume and seek control to gpmdp (#2953) 2016-08-23 23:32:00 -07:00
Carter
52acb2e6f0 adding pull mode and relay time for you garage door (#2896)
* adding pull mode and relay time

* fixing failing tests

* removed unused vars, removed trailing whitespace

* removed white space

* split line in 2

* removed whitespace and fixed indent

* undid line break

* Update rpi_gpio.py

new line so its not too long

* back to no new line

* Moved long method to a new line

* Moved comment

* moved comment to above method

* adding required blank line

* fixed variables and made them optional

misunderstood the logic at first.

* removed line for lint and removed vars that were not required

* added second blank line for class

* added new configs to platform_schema - still have same error on load

* changing string to int

* added code to covers rpi as well
2016-08-23 23:28:49 -07:00
Robby Grossman
78b2c87b54 Implement support for NEST structures. (#2736)
* Implement support for NEST structures.

* Conform to balloobbot style preferences.

* Log to debug level rather than info level.

* Use config validation to coerce list format if supplied as string.

* Use list comprehension for more succinct code.

* Conform to project linting standards.
2016-08-23 22:47:53 -07:00
Robbie Trencheny
5d4dc713f2 Append the travel mode to the sensor name for Google Travel Time 2016-08-23 21:01:31 -07:00
Paulus Schoutsen
21fb18e5aa pep257 fixes 2016-08-23 20:25:52 -07:00
Paulus Schoutsen
c4b53039c1 Merge remote-tracking branch 'origin/master' into dev 2016-08-23 19:39:03 -07:00
Paulus Schoutsen
63e3d20260 update frontend 2016-08-23 19:36:45 -07:00
Nolan Gilley
0c91ba4a50 improve gpmdp (#2951) 2016-08-23 19:09:43 -07:00
Pascal Vizeli
c5fd665151 add ffmpeg noise detection sensor (#2950) 2016-08-23 19:08:20 -07:00
Fabian Affolter
98364248d4 Use voluptuous for graphite (#2929)
* Migrate to voluptuous

* Update tests

* Fix tests and check if Graphite instance is reachable
2016-08-23 19:01:46 -07:00
Fabian Affolter
6f27d58188 Use voluptuous for Splunk (#2931)
* Migrate to voluptuous

* Update tests
2016-08-23 18:58:59 -07:00
John Arild Berentsen
cf832499cd Combine garage_door and rollershutter to cover (#2891)
* First draft for cover component

* Efficiency from @martinhjelmare

* migrate demo

* migrate demo test

* migrate command_line rollershutter

* migrate command_line test

* migrate rpi_gpio garage_door

* make some abstract methods optional

* migrate homematic

* migrate scsgate

* migrate rfxtrx and test

* migrate zwave

* migrate wink

* migrate mqtt rollershutter and test

* requirements

* coverage

* Update mqtt with garage door

* Naming and cleanup

* update test_demo.py

* update demo and core

* Add deprecated warning to rollershutter and garage_door

* Naming again

* Update

* String constants

* Make sure set_position works properly in demo too

* Make sure position is not set if not available.

* Naming, and is_closed

* Update zwave.py

* requirements

* Update test_rfxtrx.py

* fix mqtt

* requirements

* fix wink version

* Fixed demo test

* naming
2016-08-23 18:23:18 -07:00
Fabian Affolter
a43ea81d8e Migrate to voluptuous (#2927) 2016-08-23 17:27:54 -07:00
Paulus Schoutsen
2b4f0cb5a1 Fix broken template sensor tests 2016-08-23 00:14:45 -07:00
Paulus Schoutsen
88573667fa Update frontend 2016-08-23 00:06:58 -07:00
Greg Dowling
dfd76fc0e6 Minor tidy of voluptuous. (#2945) 2016-08-22 23:57:07 -07:00
Greg Dowling
5abb46a809 Tidy voluptuous. (#2946) 2016-08-22 23:56:39 -07:00
Robbie Trencheny
82de1cd6fe change const.py to use single quotes 2016-08-22 23:15:22 -07:00
Paulus Schoutsen
c9d5d1a417 Remove debug print 2016-08-22 21:44:58 -07:00
Johann Kellerman
14b034f452 Check config script (#2657)
* Add check_config, yaml linting script

* WIP: Start reusing some bootstrap methods for validation

* Start outputs

* Secrets, files and failed config

* requirements_all

* Fixes

* formatting

* Fix unit test after formatting
2016-08-22 21:42:05 -07:00
William Scanlon
f00cdc50df Updated python-wink version to fix color/temp detection (#2935) 2016-08-22 21:31:17 -07:00
Johann Kellerman
0def842231 Quick lint script for changed files (#2941) 2016-08-22 20:52:31 -07:00
Greg Dowling
dfca2476bd Add voluptuous to efergy. (#2943) 2016-08-22 20:51:17 -07:00
Greg Dowling
9fcfc213c7 Bump pywemo. (#2944) 2016-08-22 20:50:05 -07:00
Greg Dowling
eac67fd971 Add voluptuous to template switch (#2940)
* Add voluptuous to template switch / revise tests.
2016-08-23 00:05:45 +02:00
Fabian Affolter
e5969f0733 Clean-up (#2933) 2016-08-22 14:20:04 +02:00
Fabian Affolter
fb639e08d7 Fix schemas and update ordering (#2932) 2016-08-22 14:19:19 +02:00
Fabian Affolter
b6da4a53d5 Use voluptuous for dweet and arduino (#2926)
* Migrate to voluptuous

* Migrate to voluptuous

* One import is enough
2016-08-22 11:28:58 +02:00
Greg Dowling
32318c6f19 Add voluptuous validation to template sensor. (#2886) 2016-08-22 01:11:16 -07:00
Fabian Affolter
5d816b5eb5 Use voluptuous for OhmConnect (#2906)
* Migrate to voluptuous

* Remove string
2016-08-22 08:20:31 +02:00
Paulus Schoutsen
0d7d125344 Update frontend 2016-08-21 16:58:42 -07:00
Paulus Schoutsen
7598de90cb Allow unregistering a push subscription (#2921)
* Allow unregistering a push subscription

* Update frontend

* ps - HTML5 tests DRY 🍾
2016-08-21 16:01:24 -07:00
Teagan Glenn
d2f7b3c7db Merge pull request #2922 from dpford/tplink-5g
Add tplink Archer C7 device tracking support for 5Ghz networks
2016-08-21 16:49:19 -06:00
Jesse Newland
520d4d5dc0 Add zwave.rename_node service (#2923)
* Add zwave.rename_node service

* Validate service data

* Better schema
2016-08-21 14:36:44 -07:00
Dan Ford
2b4980ae5d Add tplink Archer C7 device tracking support for 5Ghz networks 2016-08-21 13:09:44 -07:00
Josh Nichols
d70d1e1303 Add support for notifying with Slack attachments. (#2914)
* Add support for notifying with Slack messages.

When creating notifications, this allows you to pass in `attachments`
with the `data`. It's an array of attachments as defined in
https://api.slack.com/docs/message-attachments

When passing in attachments, message is still required, but it's okay to
be a blank string.

* Split over multiple lines

* Make sure attachments gets assigned, even if there isn't attachment data
2016-08-21 11:54:28 -07:00
Paulus Schoutsen
f802d6bfa3 Update test packages (#2918) 2016-08-21 11:44:40 -07:00
Nolan Gilley
635e5c8eba Add voluptuous to ecobee, speedtest.net, fast.com, actiontec, forecast.io (#2872)
* add voluptuous

* fixes for comments

* str to cv.string
2016-08-21 10:29:13 -07:00
Martin Hjelmare
fa3d83118a Merge pull request #2917 from hensing/update_mysensors
Update pymysensors version to 0.7.1
2016-08-21 13:16:49 +02:00
Henning Dickten
a12dadab5e Update pymysensors version to 0.7.1 2016-08-21 12:47:40 +02:00
Paulus Schoutsen
23e86fc8ea Update frontend 2016-08-20 23:44:31 -07:00
Paulus Schoutsen
aa6a0523ef Add template support to generic camera + local file tests (#2881)
* Add template support to generic camera

* Add tests for local file
2016-08-20 23:04:55 -07:00
Teagan Glenn
9cfad34866 Merge pull request #2916 from Teagan42/YamlSecret-LoadBeforeBreak
Approved by @balloob via gitter
2016-08-20 22:20:11 -06:00
Teagan M. Glenn
af22aeeba8 Apparently, doesn't load the root config secret 2016-08-20 22:07:21 -06:00
Heiko Rothe
6aa0789e38 MQTT room presence detection (#2913)
* Added room presence tracker

* Fixed room/device discovery bugs

* Added tests for room tracker

* Fixed some formatting mistakes

* Fixed a tiny bug with the track new option

* Converted device tracker into sensor

* Removed leftover service entry

* Changed name to mqtt_room

* Changed payload validation to voluptuous

* Fixed validation

* Removed sleep from tests
2016-08-20 20:49:38 -07:00
Open Home Automation
46dcfb3d70 Serial CO2 sensor support (#2885)
* Added support for serial HM-Z19 CO2 sensor

* Minor pylint bug fixes

* Added new files to .coveragerc

* Removed newline

* Changes in requirements after change of pmsensor library

* Change the implementation of default name

* Check if serial interface is working before adding the sensor

* Maximum sensor value is 5000ppm
2016-08-20 16:35:10 -07:00
Fabian Affolter
5f508b6afa Use voluptuous for REST platforms (#2887)
* Initial step to migrate to voluptuous

* Migrate to voluptuous

* Add schema for sensor_classes
2016-08-20 16:28:45 -07:00
Roi Dayan
b62c3ac56c Update dht sensor dependency Adafruit_DHT to v1.3.0 (#2900)
The repository already merged the pull request adding python3 support.
root is no longer required to use the gpio.

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-08-20 15:41:58 -07:00
Fabian Affolter
18fd17fdf3 Migrate to voluptuous (#2901) 2016-08-20 15:41:14 -07:00
Fabian Affolter
e8c6e4d561 Clean-up, ordering, constants, and extend of schema (#2903)
* Clean-up, ordering, constants, and extend of schema

* Put REQUIREMENTS back and re-add line breaks

* Clean-up, ordering, constants, and extend of schema

* Extend platform
2016-08-20 15:40:16 -07:00
Fabian Affolter
8fc27cbe43 Migrate to voluptuous (#2905) 2016-08-20 15:28:26 -07:00
Fabian Affolter
502c65ca32 Migrate to voluptuous (#2907) 2016-08-20 15:25:11 -07:00
Paulus Schoutsen
3fae4fefbf Bust cache for new media player covers (#2882) 2016-08-20 15:14:57 -07:00
Daniel Perna
2558501235 Added LlamaLab Automate notify platform (#2863)
* Addod LlamaLab Automate notify platform

* Added platform to .coveragerc

* Added device-option and switched to voluptuous

* Fixed voluptuous usage
2016-08-20 15:11:37 -07:00
Martin Hjelmare
b7eee6fbb3 Merge pull request #2910 from hensing/update_mysensors
Update pymysensors version to 0.7
2016-08-20 23:32:17 +02:00
Henning Dickten
7d0c50a106 Update pymysensors version to 0.7 2016-08-20 22:45:55 +02:00
Teagan Glenn
8d1a9d86ea Yaml secret fallback to parent folders (#2878)
* Move secret cache out of loader so it can be referenced by other folders
* Unit test to verify secrets from another folder work & see if it overrides parent secret
* Clear secret cache after load
2016-08-20 21:39:56 +02:00
Per Sandström
ca75e66c1a Merge pull request #2902 from persandstrom/vsure_10.1
vsure 10.1
2016-08-20 17:23:07 +02:00
Per Sandström
b5cc145a92 bump vsure version 2016-08-20 17:08:42 +02:00
Greg Dowling
a46230b830 Merge pull request #2892 from home-assistant/vera_voluptuous
Add voluptuous to Vera.
2016-08-20 16:05:51 +01:00
pavoni
c0cd2d749f Tidy. 2016-08-20 15:43:07 +01:00
Greg Dowling
2df85242f9 Merge pull request #2890 from home-assistant/loop_energy_voluptuous
Add voluptuous to Loopenergy
2016-08-20 15:34:53 +01:00
Josh Nichols
8eb66ac2b8 Ensure Slack messages appear as correct user (#2893)
Current documentation suggests to use personal API tokens. This isn't
ideal because for a few reasons:

* messages will come as your own user, so it's hard to tell it's coming
  from hass
* it's harder to manage if multiple people are using Slack and home
* assistant, since you'd have to coordinate rolling of it

It is possible to use Slack bot users already. Just make a new one from https://your-team.slack.com/apps/build/custom-integration, and use the token for that. You can even add an icon from the web frontend for home assistant.

However, the message will appear as a bot without a name or icon. This pull requests fixes this by passing the as_user parameter, which uses the bot user's name and icon.

One caveat is you need to invite the bot user into the room you want to
post to. This probably was an issue before though.

🎩 to @jnewland who pointed me to this in his branch
2016-08-20 14:36:28 +02:00
Greg Dowling
482f32bb87 Add voluptuous to wemo. (#2895) 2016-08-20 14:03:57 +02:00
John Arild Berentsen
6e672b7bee More generic use of up and down commands plus a workaround for SOMFY controller. (#2852)
* Support older devices

* Update with workaround

* Inverted up and down

* Make dual arguments
2016-08-20 13:59:23 +02:00
Pascal Vizeli
a50463d2f1 Improve homematic climate support (#2894) 2016-08-20 00:20:41 +02:00
pavoni
712f1498ae Add voluptuous to Vera. 2016-08-19 22:43:40 +01:00
pavoni
d1a31b3e0c Change CONFIG to CONF for consistency. 2016-08-19 21:47:07 +01:00
pavoni
337b2e3f77 Add voluptuous
.# Please enter the commit message for your changes. Lines starting
2016-08-19 20:07:09 +01:00
Fabian Affolter
4f1712c933 Use voluptuous for system monitoring sensors (#2813)
* Use voluptuous for system monitoring sensors

* Extent platform, ordering, and consts

* Add resource/resources
2016-08-19 14:57:14 +02:00
Open Home Automation
def9bbf827 Upgrade pmsensor to 0.3 (#2883) 2016-08-19 14:56:10 +02:00
Fabian Affolter
ca1de9cac1 Add url to validation (#2874)
* Add url to validation

* Fix pylint issue

* Clean-up
2016-08-19 13:41:01 +02:00
Fabian Affolter
c74e167a7b Use voluptuous for dweet, transmission, and twitch sensor (#2802)
* Use voluptuous for dweet, transmission, and twitch sensor

* Extent platform, ordering, and consts

* Clean-up
2016-08-19 00:18:45 -07:00
John Arild Berentsen
ada4de3ffb Migrate Thermostat and HVAC component to climate component (#2825)
* First draft for climate

* Updates for thermostats
2016-08-19 00:17:28 -07:00
Fabian Affolter
0abc50e844 Use voluptuous for transport sensors (#2867) 2016-08-19 00:12:56 -07:00
Nolan Gilley
2a563e1604 binary occupancy sensor (#2869) 2016-08-19 00:11:56 -07:00
Paulus Schoutsen
a031d64a44 Merge pull request #2880 from home-assistant/hotfix-026-3
Hotfix 026 3
2016-08-18 22:48:02 -07:00
Paulus Schoutsen
a548eb6c7f Version bump to 0.26.3 2016-08-18 22:46:34 -07:00
Paulus Schoutsen
2b10a1ac20 Fix media player art (#2879) 2016-08-18 22:46:17 -07:00
Paulus Schoutsen
bafc9413a3 Fix media player art (#2879) 2016-08-18 22:45:42 -07:00
Nolan Gilley
9bfac590f6 fix 2862 (#2868) 2016-08-18 16:21:01 -07:00
Teagan Glenn
85d632c272 Merge pull request #2870 from Teagan42/AddFixToWunderground
Fix PyDoc and other issues with Wunderground - approved by robbie
2016-08-18 17:20:15 -06:00
Teagan Glenn
297fca9351 Type-o 2016-08-18 16:39:16 -06:00
Teagan Glenn
cb3a37691f Type-o 2016-08-18 16:28:19 -06:00
Pascal Vizeli
df4a9ea1da add move_postion support for HA rollershutter / CONFIG_SCHEMA (#2873) 2016-08-18 23:14:14 +02:00
Teagan M. Glenn
5bdcf60a21 Extend platform schema 2016-08-18 10:47:52 -06:00
Teagan M. Glenn
90449a90f1 Use string templating 2016-08-18 10:46:24 -06:00
Teagan M. Glenn
25840f97c2 Consistent use of WUnderground 2016-08-18 10:46:04 -06:00
Teagan M. Glenn
c2b75140bf Fix config validation import to make things more readable 2016-08-18 10:40:28 -06:00
Teagan M. Glenn
ec5e20f0d9 Use string constant 2016-08-18 10:38:34 -06:00
Teagan M. Glenn
db2d9ec854 Unused property 2016-08-18 10:37:39 -06:00
Teagan M. Glenn
ddec28da4b Use schema validators already avaialble 2016-08-18 10:37:26 -06:00
Teagan M. Glenn
6f57d36134 Add doc link to header of file 2016-08-18 10:37:00 -06:00
Teagan M. Glenn
0490fe832a Unneeded validation removed 2016-08-18 10:32:19 -06:00
Teagan M. Glenn
2b8e2a3d36 Remove print lines 2016-08-18 10:27:53 -06:00
Teagan M. Glenn
41f84d9e20 Pydoc for unit test methods 2016-08-18 10:27:38 -06:00
Teagan Glenn
c1653d2fca Merge pull request #2861 from arsaboo/patch-2
Wunderground weather sensor
2016-08-18 09:40:00 -06:00
arsaboo
230dde4b57 Removed blank line (linting error) 2016-08-18 10:12:56 -04:00
arsaboo
90fdc89838 Updated to address @balloob's comments 2016-08-18 09:59:41 -04:00
Nolan Gilley
09d531b3b9 fix gpmdp (#2864)
* fix gpmdp

* fix balloobs comments

* move create_connection
2016-08-18 00:08:58 -07:00
Emil Horpen Hetty
053a55bc5f Added name support for Forecast.io (#2638)
* Added support for name

Added name support and changed default name to "Forecast.io" since "Weather" had conflict with Yahoo weather and Open weather map

* Update forecast.py
2016-08-17 23:54:08 -07:00
Open Home Automation
ccd8f51253 Ble tracker (#2810)
* Added Bluetooth Low Energy device tracker

* Added new file(s)

* Fixed pylint errors

* Remove traling zeros from device names

* recreated deleted file

* Added requirements

* Renamed to bluetooth_le tracker
Removed gattlib from tests
Minor code cleanup

* - fixed .coveragerc bug
- changed discovery algorithm, new devices will only be added if seen 5 times to make sure
  HA doesn't blow the database with devices just passing by
2016-08-17 23:41:05 -07:00
Roi Dayan
98f236c754 Add webos customize option to add custom sources (#2561)
Currently there are only hw inputs in the sources list.
Other interesting inputs can be live tv (dvbt) and vod apps.

* add customize option for webos
* add short names for livetv, youtube, mako apps
* add current app as a source
* use large icon (largeIcon is the same as icon if doesn't exists)
* filter out hw inputs that are not connected

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-08-17 23:39:37 -07:00
Robbie Trencheny
a5f144cb7c HTML5 notify actions (#2855)
* Add action and callback support to html5 (#2855).

Remove registrations from the callback view since we always get the latest anyway.

We dont put an audience in the claims so we will never hit this error.

Bring tests back up to where they were before callbacks.

Only import jwt where necessary

Fix bracket spacing errors

Fix JWT decode check for loop

Remove stale comment.

Add tests for the callback system.

Shorten line

Disable pylint broad-except and change e to jwt_decode_error.

Verify expiration

Remove duplicate jwt.exceptions.DecodeError

Catch no keys matched and return False

* Switch to using registrations for callbackview instead of json_path

* Only check for URL and such if the data object actually exists

* raise instead of return

* cleanup decode_jwt

* Clean up JWT errors

* Correctly set status_code to 401

* Improve JWT by adding target to claims and attempting to check the given target for a decode match first, as well as pass the target through in the event payload.

* Add tag support and fix formatting issues

* Pass through any keys that dont apply to the payload into the notification.data dictionary

* Remove stale print

* Pass back the data dictionary if it exists

* Actually put the default url even if a notify payload dictionary doesnt exist

* pylint, flake8

* Add subscription validation

* Add validation for the callback event payload and use constants where possible

* Use HTTP_UNAUTHORIZED instead of 401

* Change callback dictionary to dict instead of cv.match_all

* Fix up tests and make subscription required

* Whoops, that test was supposed to fail

* Use the result of CALLBACK_EVENT_PAYLOAD_SCHEMA as event_payload

* Add a test for html5 callback decode_jwt where the device has been renamed since notification has been sent.

* Remove the loop through logic, assume that target is always in JWT

* Always return something instead of possibly None.

* Update frontend
2016-08-17 22:34:12 -07:00
Teagan M. Glenn
a5fd04f215 Unit tests around wunderground 2016-08-17 22:33:39 -06:00
Teagan M. Glenn
4e586c18ff Check for error and pull obvservation 2016-08-17 22:32:42 -06:00
Teagan M. Glenn
87f81bf3b4 Use url builder helper 2016-08-17 22:32:19 -06:00
Teagan M. Glenn
d2ba8ee0a7 Reraise exception 2016-08-17 22:31:58 -06:00
Teagan M. Glenn
466dd35f3d Don't set state on update - state already handles this 2016-08-17 22:31:47 -06:00
Teagan M. Glenn
e54ba5ff72 No need no need to set variable 2016-08-17 22:31:28 -06:00
Teagan M. Glenn
dd14f90afb Error handling on state 2016-08-17 22:30:23 -06:00
Teagan M. Glenn
ecb4eb843b Don't call update on init of sensor 2016-08-17 22:30:03 -06:00
Teagan M. Glenn
afef255a25 Condition is already a string 2016-08-17 22:29:49 -06:00
Teagan M. Glenn
417711d665 Refactoring 2016-08-17 22:29:37 -06:00
Teagan M. Glenn
31237a891c Catch exception from update on initial platform setup 2016-08-17 22:29:25 -06:00
Teagan M. Glenn
62b00e1294 Update invocation of WUndergroundData 2016-08-17 22:29:00 -06:00
Teagan M. Glenn
563154c3c2 Validate configuration 2016-08-17 22:28:18 -06:00
Teagan M. Glenn
1a8e17ce41 Pass hass to constructor 2016-08-17 22:28:05 -06:00
Teagan M. Glenn
42caa31067 Unused variable 2016-08-17 22:22:29 -06:00
Teagan M. Glenn
e4abecd359 Build url helper method 2016-08-17 22:22:11 -06:00
Teagan M. Glenn
53b97feb3c Rename constant - make valid for lat/long too 2016-08-17 22:20:22 -06:00
Teagan M. Glenn
a09baf1d5a Not using payload 2016-08-17 22:19:57 -06:00
Teagan M. Glenn
b7809675eb Config schema 2016-08-17 22:19:13 -06:00
Teagan M. Glenn
333e3ba822 Add imports 2016-08-17 22:18:37 -06:00
David Straub
49998272db Added daily temp/precip forecast values to forecast.io (#2846) 2016-08-17 19:48:51 -07:00
Paulus Schoutsen
8088322c43 Consider core running while starting (#2858) 2016-08-17 18:58:00 -07:00
Johann Kellerman
244f60d6cd Fix script help (#2860)
Allow `--help` to filter down to the script
2016-08-17 18:57:52 -07:00
arsaboo
a0bcd33b71 Update wunderground.py 2016-08-17 17:48:37 -04:00
arsaboo
be57cd55c5 Update wunderground.py 2016-08-17 17:25:42 -04:00
arsaboo
4dff42e8bb Update wunderground.py 2016-08-17 17:04:11 -04:00
arsaboo
75cd1f8063 Update wunderground.py 2016-08-17 16:50:32 -04:00
arsaboo
fae9267701 Update wunderground.py 2016-08-17 16:41:22 -04:00
arsaboo
1a34bc5301 Removed lynting issues 2016-08-17 16:31:36 -04:00
arsaboo
aabeda2b60 Update wunderground.py 2016-08-17 16:15:07 -04:00
arsaboo
469d095827 Create initial Wunderground weather sensor 2016-08-17 15:06:12 -04:00
Matthias Grawinkel
8a3c511a04 Adding Digest Auth for webcam image retrieval (#2821)
* Adding Digest Auth for webcam image retrieval

* Update generic.py

* Update mjpeg.py

* Update generic.py

* Update mjpeg.py

* Update generic.py

* Update mjpeg.py
2016-08-17 19:08:47 +02:00
Paulus Schoutsen
2237189c86 Add default badge to push notification (#2859) 2016-08-16 23:05:00 -07:00
Paulus Schoutsen
7720a17c18 Add badge 2016-08-16 23:02:19 -07:00
Pascal Vizeli
4a847dbd91 new yahooweather version and fix update function (#2848) 2016-08-16 22:40:20 -07:00
Robbie Trencheny
848781fbb7 Add a group notify platform (#2842)
* Add a group notify platform which allows sending a single notification to multiple platforms.

* Correctly sort group.py

* Clean up the payload logic

* Make name and entity id required in the schema

* Deep update the dictionary to fix a bug where data wasnt merging.

* Add notify.group tests.

* Improve docstrings.

* Change entities to services and entity_id to service

* Make service a slug without a default value

* Update tests for entities->services, entity_id->service

* vol.Any(cv.slug) -> cv.slug
2016-08-16 22:14:04 -07:00
Robbie Trencheny
c1ce6855c5 Expose notify platform targets as individual services (#2837)
* First pass on providing individual services for all possible targets that a notification platform supports.

* Add a quite hacky first version of notification groups

* Add a docstring for get_targets

* Register group service under notifygroup/ and safely check for notifygroups in config

* Remove notifygroups, because it belongs in its own PR

* Make @balloob requested changes

* get_targets()->targets

* Add tests for notify targets exposed as individual services

* If we dont have a platform name set in configuration, lets use the name of the platform instead of notify

* Fix test docstring.

* Dont use a dictionary for just one value

* No need to double slugify

* targets is now just a list of strings instead of a dict
2016-08-16 22:05:41 -07:00
Robbie Trencheny
37561765ff Add Gravatar support to device_tracker (#2836)
* Support passing an email address linked to Gravatar as the picture in known_devices.

* Add a dedicated field for Gravatar

* Bring tests back up to where they were before Gravatar.

* Add tests for Gravatar.
2016-08-16 21:08:57 -07:00
Pascal Vizeli
4fcfffc172 add tcp/udp port to config validation (#2854) 2016-08-16 20:55:29 -07:00
Kevin Gottsman
781fe9c54e Fix logging message for disarm (#2857) 2016-08-16 20:53:59 -07:00
Nolan Gilley
324ddfdaeb fix flux_update service (#2792)
* fix flux_update service

* fix tests

* give update service unique name

* remove unnecessary param

* Revert "fix tests"

This reverts commit 2fd7760455.

* fix flux_update
2016-08-16 20:53:00 -07:00
Maggi Trymbill
f668a88485 Fixed typo (#2856)
Pretty sure this is a typo ... it made finding the plist and logs a bit of a headache for me at least :)
2016-08-16 18:24:37 -07:00
Robbie Trencheny
72fc526ee8 Html5 notifications improvements (#2840)
* Retry sending the push for 1 day instead of failing instantly if the target is unavailable

* Add timestamp to push payload

* Correctly use the title and body fields for their intended purposes

* Add callback support

* Revert changes to frontend files.

* Add default URL which will open Home Assistant. Also put all the data into the data object of the payload so it is accessible in the browser. Without doing this, things like URL wouldnt be accessible.

* Flake8 and pylint fixes

* event->type

* Dont send the default url if actions exist

* flake8/pylint fixes again

* Update html5 tests

* Remove callbacks from this branch, will re-stage on a different branch

* Remove remnant of callbacks

* Add url to data dictionary if it exists instead of copying the entire data dictionary in

* flake8 fix
2016-08-16 14:26:01 -07:00
Fabian Affolter
822b7f8770 Use voluptuous for exchange sensors (#2801)
* Use voluptuous for exchange sensors

* Remove additional checks
2016-08-16 22:22:55 +02:00
Fabian Affolter
dab5a78f88 Use voluptuous for time/date sensors (#2799)
* Use voluptuous for time/date sensors

* Extend platform

* Remove additional checks
2016-08-16 21:43:56 +02:00
Fabian Affolter
1c140de0dc Use voluptuous for NZB sensors (#2847) 2016-08-16 21:42:43 +02:00
Greg Dowling
91e24de3d5 Merge pull request #2833 from home-assistant/fix_owntracks_beacon_accuracy_bug3
Handle accuracy zero correctly in enter/leave events.
2016-08-16 10:34:27 +01:00
pavoni
41dad9a8f7 Tidy warnings. 2016-08-16 09:48:13 +01:00
Nuno Sousa
7762365b3f Add position to zwave rollershutter (#2772) 2016-08-16 00:37:58 -07:00
Open Home Automation
693098ff00 Bugfix in pknx library (#2835)
* Bugfix in pknx library

* Version pinned to 0.3.3
2016-08-15 23:42:45 -07:00
David Straub
83a043a0ea Add FritzBox call monitor sensor (#2791)
* Add FritzBox call monitor sensor

* Correct docstrings and suppress too few public methods warning

* Remove blank lines after docstrings

* Add blank lines after class docstrings

* Remove trailing white space

* Make daemon; add reconnect on disconnect
2016-08-15 23:22:54 -07:00
Juggels
a7f218f712 HP ILO component (#2844)
* HP ILO component

* HP ILO component

* Add Onboard Administrator sensor

* Add Onboard Administrator sensor

* Add period to first line

Fix D400 error on line 1
2016-08-15 23:19:11 -07:00
Assaf Inbal
72ad1d8d7c Added support for exposing light features (#2828) 2016-08-15 23:07:07 -07:00
Nolan Gilley
d281a7260d check for runtime error during db query (#2834) 2016-08-15 22:48:42 -07:00
John Arild Berentsen
27e27ee156 Exit when command_classes are missing thermostat Zwave (#2824) 2016-08-15 22:13:49 -07:00
Martin Hjelmare
9afb1d8c0d Fix unit log message (#2823)
* Fix log message for deprecated temp key

* Use string formatting and pass constant variables as arguments in log
	message to show correct name of config keys.

* Fix import order
2016-08-15 22:12:43 -07:00
Paulus Schoutsen
7594cf3c94 Merge pull request #2734 from tobiebooth/manual-alarm-improvements
Return to previous alarm state after trigger (#2580)
2016-08-15 22:10:32 -07:00
pavoni
a7703f27d8 Add missed docstring. 2016-08-15 13:14:07 +01:00
pavoni
c0b1ff0eaf Handle accuracy zero correctly in enter/leave events. 2016-08-15 13:08:30 +01:00
Paulus Schoutsen
f61f0623f8 Merge pull request #2827 from home-assistant/hotfix-0-26-2
Hotfix 0 26 2
2016-08-14 21:25:11 -07:00
Paulus Schoutsen
c4b714a10d Version bump to 0.26.2 2016-08-14 21:22:28 -07:00
Greg Dowling
8a8551132f Bump to pywemo 0.4.5 - fixes bug with requests 2.11.0 (#2818) 2016-08-14 21:22:06 -07:00
Greg Dowling
6fd0fe05f9 Bump to pywemo 0.4.5 - fixes bug with requests 2.11.0 (#2818) 2016-08-14 11:42:43 -07:00
Paulus Schoutsen
a6bbd749e4 Merge pull request #2817 from home-assistant/hotfix-0-26-1
Hotfix 0 26 1
2016-08-14 01:26:19 -07:00
Paulus Schoutsen
32051c042c Version bump to 0.26.1 2016-08-14 01:21:57 -07:00
John Arild Berentsen
c16a29b930 Fix unknown unit of measurement for hvac and thermostat component (#2816)
* Fix unknown unit of measurement for hvac and thermostat component

* Simplify
2016-08-14 01:21:36 -07:00
Heiko Rothe
12ce3deffc Check for existence of system mode on Honeywell thermostats (#2815)
* Check for existence of system mode on Honeywell thermostats

* Return None instead of undefined

* Use getattr instead of if/else
2016-08-14 01:21:28 -07:00
Open Home Automation
7c041f0797 Bugfix: removed conf_platform (#2811)
* Bugfix: removed conf_platform

* Remove unused import

* Fix for wrong update
2016-08-14 01:21:18 -07:00
Heiko Rothe
8210d65850 Check for existence of system mode on Honeywell thermostats (#2815)
* Check for existence of system mode on Honeywell thermostats

* Return None instead of undefined

* Use getattr instead of if/else
2016-08-14 01:20:28 -07:00
John Arild Berentsen
bb14239d91 Fix unknown unit of measurement for hvac and thermostat component (#2816)
* Fix unknown unit of measurement for hvac and thermostat component

* Simplify
2016-08-14 01:19:54 -07:00
Paulus Schoutsen
dc68f61261 Html5 push notifications notify platform (#2807)
* Initial work to add Chrome Push Notification support

* Remove push.js from home-assistant since it is now in Polymer

* Chrome->HTML5, general cleanup/fixes

* Make html5 generic, move manifest.json into frontend so that we can dynamically add the gcm_sender_id

* Pylint, flake8, pydocstyle frontend init

* HTML5 push fixes

* Update polymer

* Remove crypto req

* Add notify default platform.

* Fix HTML5 push

* Registration fixes

* Linting fix

* pep257 fix

* Add tests

* pep257 fix

* Update frontend
2016-08-14 01:10:07 -07:00
Open Home Automation
c6f67a5203 Implemented range checking for temperature and humidity. Out-of-range… (#2805)
* Implemented range checking for temperature and humidity. Out-of-range values will be ignored

* Removed unused import

* Use celsius_to_fahrenheit conversion method
2016-08-14 01:02:26 -07:00
Open Home Automation
8329472c72 Bugfix: removed conf_platform (#2811)
* Bugfix: removed conf_platform

* Remove unused import

* Fix for wrong update
2016-08-13 14:16:06 -07:00
Paulus Schoutsen
8ba85effd4 Version bump to 0.27.0.dev0 2016-08-13 12:02:13 -07:00
Paulus Schoutsen
0270ae05e9 Merge pull request #2760 from home-assistant/dev
0.26
2016-08-13 12:01:56 -07:00
Paulus Schoutsen
9c0b9b9ad6 Version bump to 0.26.0 2016-08-13 12:01:34 -07:00
Robbie Trencheny
7882ce1afd Add CORS fixes to support OPTIONS preflight requests. (#2773)
* Add CORS fixes to support OPTIONS preflight requests.

* Add CORS tests

* Fix formatting
2016-08-13 11:49:44 -07:00
Tobie Booth
abaffc2d8c add disarm_after_trigger to manual alarm panel 2016-08-13 12:53:32 -05:00
Tobie Booth
1e3f7ad9a4 Return to previous alarm state after trigger (#2580) 2016-08-13 12:53:32 -05:00
Paulus Schoutsen
176a078b3c Update .coveragerc 2016-08-13 10:39:13 -07:00
Fabian Affolter
5baed6acfb Add support for GPSD (#2254)
* Add support for GPSD

* Add gpsd.py

* Check if socket is open

* Fix pylint issue

* Rename file to be a sensor

* Update for being a sensor

* Rework for being a sensor
2016-08-13 10:37:12 -07:00
Paulus Schoutsen
f845893f8f Update frontend 2016-08-13 10:21:54 -07:00
Tomi Tuhkanen
9c636ab6fd Fix for braviatv get mac regex none case (#2808)
* Fix for braviatv get mac regex none case

* E128 fix
2016-08-13 09:45:49 -07:00
Brent Hughes
0df229773f Removed error log on roku connection error (#2809) 2016-08-13 09:45:09 -07:00
Paulus Schoutsen
0b404cc0be Update frontend 2016-08-13 09:41:23 -07:00
Paulus Schoutsen
18829daa65 Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	homeassistant/components/recorder/__init__.py
	homeassistant/const.py
	requirements_all.txt
	setup.py
2016-08-12 18:57:15 -07:00
Pascal Vizeli
f0a138dd51 update yahooweather version (#2796) 2016-08-12 18:47:45 -07:00
Daniel Høyer Iversen
b28114fb5a Merge pull request #2804 from home-assistant/rfxtrx_log
improve logging from rfxtrx component
2016-08-12 20:56:19 +02:00
Daniel
6d83ebc5e4 improve logging from rfxtrx component 2016-08-12 20:46:54 +02:00
Daniel Høyer Iversen
29bd9b4587 Merge pull request #2803 from home-assistant/rfxtrx_log
improve logging from rfxtrx component
2016-08-12 19:35:44 +02:00
Daniel
5ed22f3ef0 improve logging from rfxtrx component 2016-08-12 19:21:12 +02:00
Daniel Høyer Iversen
a14995ed27 Merge pull request #2798 from home-assistant/flux_led_minor_bug
Fix minor bug in flux led
2016-08-12 15:41:22 +02:00
Daniel
0a78b69ee2 Fix minor bug in flux led 2016-08-12 15:21:51 +02:00
Daniel Høyer Iversen
b7ebf3b1eb Merge pull request #2789 from home-assistant/rfxtrx_lib
update rfxtrx lib
2016-08-11 14:34:50 +02:00
Daniel
2493155f2b update rfxtrx lib 2016-08-11 14:18:23 +02:00
Fabian Affolter
e06ff95107 Remove pylint disable (#2785) 2016-08-11 12:00:37 +02:00
Daniel Høyer Iversen
eea7824a7e Merge pull request #2786 from home-assistant/rfxtrx_lib
update rfxtrx lib
2016-08-11 11:41:49 +02:00
Daniel
a3c2db70e2 update rfxtrx lib 2016-08-11 11:25:50 +02:00
Fabian Affolter
a784f48022 Minor changes (#2784)
* Update link to docs

* Use fast.com

* Update docstring

* Add link to docs

* Add link to docs

* Update docstrings

* Update docstrings

* Fix typo
2016-08-11 11:14:24 +02:00
Paulus Schoutsen
e926426af9 Recorder: Increase size of the entity column in states table (#2778)
Fixes https://github.com/home-assistant/home-assistant/issues/2697
2016-08-10 17:40:52 -07:00
Johann Kellerman
bf21d6b4e1 Update unit tests for remote.py (#2782)
* Update remote unit tests

* Sleep again
2016-08-10 17:40:35 -07:00
Nolan Gilley
dcf4fc5e9b fast.com speedtest sensor (#2783)
* fast.com speedtest sensor

* update for fastdotcom
2016-08-10 17:39:52 -07:00
Johann Kellerman
f3376ba276 Script requirement logging, db_migrator REQUIREMENTS (#2781) 2016-08-10 13:32:07 -07:00
Adam Mills
1a327d682d Fix farcy failure for logbook test (#2780) 2016-08-10 08:07:50 -07:00
Corban Mailloux
9c851790dc Add support for new mqtt_json light platform. (#2777)
* Add support for new mqtt_json light platform.

* Fix W503 errors.

* Bring in feedback from @balloob.

* Add test coverage for invalid color and brightness data.

* Add coverage for transition in turn_off.
2016-08-09 23:55:10 -07:00
Johann Kellerman
aadf6a7750 Handle requirements for scripts (#2765) 2016-08-09 23:54:34 -07:00
John Arild Berentsen
a03691455b Various fixes for missing components and rollershutter. (#2698)
* Various fixes for missing components, rollershutter.

* Setting up different method for catching value of correct type.
2016-08-10 08:31:44 +02:00
Paulus Schoutsen
1726c4b45a Merge pull request #2776 from home-assistant/add-notify-test
Add notify demo data test
2016-08-09 21:47:11 -07:00
Paulus Schoutsen
9fa1328111 Move config validation exception logging to bootstrap + humanize 2016-08-09 21:38:44 -07:00
Paulus Schoutsen
f904d06c9a Add test for new template validation logic 2016-08-09 21:09:56 -07:00
Paulus Schoutsen
253628da11 Add notify test being called from a YAML/script combi 2016-08-09 21:03:06 -07:00
Paulus Schoutsen
e773526714 Template config validator should not allow dictionaries 2016-08-09 20:58:27 -07:00
Paulus Schoutsen
6dc49ff123 Humanize service call config validation errors 2016-08-09 20:58:08 -07:00
Paulus Schoutsen
6bb6a6ebe9 Add notify demo data test 2016-08-09 20:26:17 -07:00
Paulus Schoutsen
492ade7b1a Update frontend 2016-08-09 20:12:20 -07:00
D.-L.Pohl
dc9f990ad2 Pilight component (#2742)
* New component to interface with a pilight-daemon for RF send/receive

* Fix bug that changed the received data, add connected flag, clean up

* New pilight switch component

* New optional whitelist filter to filter uninteressting devices

* Add pilight

* PEP8: too long lines, white spaces

* To keep up the good coverage ...

* PEP 257

* pylint enhancements

* pylint enhancements

* PEP 257

* Better HA config validation and cleanup following code review for #2742

* Fix requirenments to require fixed pilight version

* Change config validation to use voluptuous

* Pilight switch exclude not needed due to wildcard pilight exclude

* Enhance configuration parsing using voluptuous
2016-08-09 19:45:40 -07:00
Paulus Schoutsen
d80c05b6b6 Enforce lower case for services and warn if local unknown service called (#2764) 2016-08-09 19:41:45 -07:00
Per Sandström
180a7ec295 add changed_by attribute to lock (#2766) 2016-08-09 19:37:46 -07:00
Pascal Vizeli
431f0fd236 update pyhomematic to version 0.1.11 (#2770) 2016-08-09 22:55:25 +02:00
schneefux
3d2830278a Hyperion: backwards compatibility (#2769) 2016-08-09 08:46:47 -07:00
Adam Mills
3ac9aaf025 Filter continuous values from logbook (#2761)
* Filter continuous values from logbook

* Test filter continuous values from logbook
2016-08-09 08:01:02 -07:00
Paulus Schoutsen
0b7b0e54ba Move unit system to util (#2763) 2016-08-08 20:42:25 -07:00
Paulus Schoutsen
640a8b5a7f Limit dependencies of HA core (#2762) 2016-08-08 20:21:40 -07:00
Teagan Glenn
915b9cb3eb Fix pydoc strings 2016-08-08 20:19:56 -06:00
Phil Hansen
88734f05c6 garage door rpi_gpio.py fix (#2759) 2016-08-08 19:03:23 -07:00
Robbie Trencheny
8e6dd62853 Add an OhmConnect sensor (#2758)
* Add an OhmConnect sensor

* use .get
2016-08-08 17:54:59 -07:00
Paulus Schoutsen
9948587401 Merge branch 'pr/2726' into dev 2016-08-08 17:42:34 -07:00
Nick Touran
3c2b4f5128 Added optional embedded image attachments to notify.smtp. (#2738)
* Added optional embedded image attachments to notify.smtp.

Also restructured a bit to minimize code duplication and add some tests.

* Fixed formatting errors.

* SMTP cleanups thanks to code review.
2016-08-08 17:36:49 -07:00
Johann Kellerman
efe754636a Script to manage secrets stored in the keyring (#2743)
* Keyring script to get, set and delete secrets

* Add info & keyring version
2016-08-08 17:36:11 -07:00
Paulus Schoutsen
8081fe794e Add panel custom to load any webcomponent (#2747) 2016-08-08 17:35:46 -07:00
Paulus Schoutsen
9a575eb6d6 Link prefetch panels (#2748)
* Add link=prefetch to index.html

* Improve http request logging
2016-08-08 17:35:27 -07:00
Pascal Vizeli
98c77dc08f Add ffmpeg camera platform support (#2755) 2016-08-08 17:34:46 -07:00
Robbie Trencheny
991e292d7e Foursquare Component (#2723)
* Add a Foursquare component which accepts push notifications from Foursquare and provides a user checkin service

* @balloob requested fixes

* Sort .coveragerc list of components by name

* Revert "Sort .coveragerc list of components by name"

This reverts commit 997ae22576.

* Only sort Foursquare since I get conflicts otherwise

* Add Foursquare checkin service to services.yaml
2016-08-08 17:34:29 -07:00
Teagan Glenn
7e37634b54 Honeywell hvac mode (#2757)
* Add set hvac mode to honeywell us thermostat

* Add hvac service to the HoneywellRound entity

* Fix pydoc

* Add typing

* Typing to unit test
2016-08-08 17:32:53 -07:00
Pascal Vizeli
5445aafee7 update yahooweather to 0.5 (#2756) 2016-08-08 12:43:15 -07:00
John Arild Berentsen
7077103c4f General logmessage cleanup (#2753) 2016-08-08 20:05:45 +02:00
Daniel Høyer Iversen
fc101fbbcb Merge pull request #2754 from home-assistant/flux_led_lib
update flux led library
2016-08-08 20:03:07 +02:00
Daniel
21ffe2ed9b update flux led library 2016-08-08 19:43:04 +02:00
Paulus Schoutsen
19fae75669 Fix broken remote test 2016-08-08 09:11:15 -07:00
Per Sandström
8568773e7d add changed_by attribute to alarm control panel (#2737) 2016-08-08 09:00:20 -07:00
Dean Camera
5ff9e59b79 Update to latest Plex API, add music support. (#2739)
* Update to latest Plex API, add music support.

* Fix PyLint errors.

* Update Plex sensor module to latest PlexAPI.

* Oops - update Python sensor import.

* According to PlexAPI docs, this is the new API for Plex Pass members.

* More pylint STFUs.

* Move pylint suppression.

* Use plexapi NA type directly.

* Pylint objects to short variable names.
2016-08-08 08:55:58 -07:00
Daniel Høyer Iversen
689939ab9d Add support color and brightness for flux light (#2750) 2016-08-08 08:47:02 -07:00
Sean Dague
8daaee702b bump proliphix library to 0.3.1 (#2751)
The 0.3.1 version of the library includes fixes for time syncing the
thermostat under the covers when needed. All changes are done on the
library side, we just need to bump the required level in home
assistant.
2016-08-08 08:03:12 -07:00
John Arild Berentsen
e6ad2e8d91 Handling and improvements for zwave network (#2728) 2016-08-08 16:52:28 +02:00
Paulus Schoutsen
dd0b9f2f36 Update frontend 2016-08-08 00:41:32 -07:00
Marcelo Moreira de Mello
0383da7af1 This patch makes use of the unit_system global configuration parameter to determine the mesurement system between 'metric' or 'imperial' for Fibit component. It also supports the fitbit accept-language when en_GB measurement is desired. (#2745) 2016-08-07 21:58:16 -07:00
Paulus Schoutsen
b9b1d95514 Tweak panel parameters (#2746) 2016-08-07 21:56:17 -07:00
Daniel Høyer Iversen
23472cb44d Handle numeric device id for rfxtrx devices (#2740) 2016-08-07 17:15:39 -07:00
Fabian Heredia Montiel
0377338a81 Improvement typing (#2735)
* Fix: Circular dependencies of internal files

* Change: dt.date for Date and dt.datetime for DateTime

* Use NewType if available

* FIX: Wrong version test

* Remove: Date and DateTime types due to error

* Change to HomeAssistantType

* General Improvement of Typing

* Improve typing config_validation

* Improve typing script

* General Typing Improvements

* Improve NewType check

* Improve typing db_migrator

* Improve util/__init__ typing

* Improve helpers/location typing

* Regroup imports and remove pylint: disable=ungrouped-imports

* General typing improvements
2016-08-07 16:26:35 -07:00
Open Home Automation
a3ca3e878b Added support for serial particulate matters sensors - serial_pm (#2571) 2016-08-07 22:14:01 +02:00
Paulus Schoutsen
d1107a9cf3 Merge pull request #2731 from home-assistant/teagan-unit-system
Teagan unit system
2016-08-04 22:53:44 -07:00
Paulus Schoutsen
231656916c Address last comments 2016-08-04 22:44:37 -07:00
Teagan M. Glenn
26526ca57a Add unit system support
Add unit symbol constants

Initial unit system object

Import more constants

Pydoc for unit system file

Import constants for configuration validation

Unit system validation method

Typing for constants

Inches are valid lengths too

Typings

Change base class to dict - needed for remote api call serialization

Validation

Use dictionary keys

Defined unit systems

Update location util to use metric instead of us fahrenheit

Update constant imports

Import defined unit systems

Update configuration to use unit system

Update schema to use unit system

Update constants

Add imports to core for unit system and distance

Type for config

Default unit system

Convert distance from HASS instance

Update temperature conversion to use unit system

Update temperature conversion

Set unit system based on configuration

Set info unit system

Return unit system dictionary with config dictionary

Auto discover unit system

Update location test for use metric

Update forecast unit system

Update mold indicator unit system

Update thermostat unit system

Update thermostat demo test

Unit tests around unit system

Update test common hass configuration

Update configuration unit tests

There should always be a unit system!

Update core unit tests

Constants typing

Linting issues

Remove unused import

Update fitbit sensor to use application unit system

Update google travel time to use application unit system

Update configuration example

Update dht sensor

Update DHT temperature conversion to use the utility function

Update swagger config

Update my sensors metric flag

Update hvac component temperature conversion

HVAC conversion for temperature

Pull unit from sensor type map

Pull unit from sensor type map

Update the temper sensor unit

Update yWeather sensor unit

Update hvac demo unit test

Set unit test config unit system to metric

Use hass unit system length for default in proximity

Use the name of the system instead of temperature

Use constants from const

Unused import

Forecasted temperature

Fix calculation in case furthest distance is greater than 1000000 units

Remove unneeded constants

Set default length to km or miles

Use constants

Linting doesn't like importing just for typing

Fix reference

Test is expecting meters - set config to meters

Use constant

Use constant

PyDoc for unit test

Should be not in

Rename to units

Change unit system to be an object - not a dictionary

Return tuple in conversion

Move convert to temperature util

Temperature conversion is now in unit system

Update imports

Rename to units

Units is now an object

Use temperature util conversion

Unit system is now an object

Validate and convert unit system config

Return the scalar value in template distance

Test is expecting meters

Update unit tests around unit system

Distance util returns tuple

Fix location info test

Set units

Update unit tests

Convert distance

DOH

Pull out the scalar from the vector

Linting

I really hate python linting

Linting again

BLARG

Unit test documentation

Unit test around is metric flag

Break ternary statement into if/else blocks

Don't use dictionary - use members

is metric flag

Rename constants

Use is metric flag

Move constants to CONST file

Move to const file

Raise error if unit is not expected

Typing

No need to return unit since only performing conversion if it can work

Use constants

Line wrapping

Raise error if invalid value

Remove subscripts from conversion as they are no longer returned as tuples

No longer tuples

No longer tuples

Check for numeric type

Fix string format to use correct variable

Typing

Assert errors raised

Remove subscript

Only convert temperature if we know the unit

If no unit of measurement set - default to HASS config

Convert only if we know the unit

Remove subscription

Fix not in clause

Linting fixes

Wants a boolean

Clearer if-block

Check if the key is in the config first

Missed a couple expecting tuples

Backwards compatibility

No like-y ternary!

Error handling around state setting

Pretty unit system configuration validation

More tuple crap

Use is metric flag

Error handling around min/max temp

Explode if no unit

Pull unit from config

Celsius has a decimal

Unused import

Check if it's a temperature before we try to convert it to a temperature

Linting says too many statements - combine lat/long in a fairly reasonable manner

Backwards compatibility unit test

Better doc
2016-08-04 22:02:19 -07:00
Robby Grossman
dfad8aa6dc Remove 'remove node (secure)' service; is not a specialized implementation in Python-OZW and standard removal works fine. (#2730) 2016-08-04 21:04:08 -07:00
Matthew Treinish
496972a587 Add option to heat_control component to set min cycle duration
This commit adds a new config option to the heat_control thermostat
component, min_cycle_duration. Some heaters and/or ACs don't like
being constantly cycled on and off. Prior to this patch the
heat_control component can end up cycling the switch quite
frequently. (depending on how quickly the temperature changes) The
new option added is used for setting a minimum duration that must
have elapsed in either the on or off state before the thermostat will
send the service call to cycle the switch. This should enable users to
hand tune how frequently heat_control can switch the device on or off
to best suit the device being used.
2016-08-04 12:37:08 -04:00
mmello
ef3e7b28a9 Added whitelist option to InfluxDB to select the only entities that will be logged on InfluxDB (#2727) 2016-08-04 08:35:01 -07:00
Paulus Schoutsen
792154a6a7 Update frontend 2016-08-03 08:22:47 -07:00
Johann Kellerman
09262a36c4 Hide NewType ImportErrors (#2717)
* Hide NewType ImportErrors

* No more NewType
2016-08-03 08:21:30 -07:00
Matthew Treinish
94acda2a31 Add AC mode to heat_control component (#2719)
This commit adds a new option to the heat_control component, ac_mode.
When set to true, this treats the toggle device as a cooler instead
of a heater. The concept being if you have a window or in-wall ac
unit that doesn't have a built-in thermostat having the home assistant
implemented thermostat would be as useful as for space heaters.
2016-08-02 21:56:08 -07:00
William Scanlon
b8492832a6 Convert null to 0 for temp % sensors (#2710) 2016-08-02 21:53:26 -07:00
Assaf Inbal
bb22ad3064 Proxy requests to the media player's media image (#2693)
This is needed when the media server and UI client are not on the same network.
2016-08-02 18:31:15 -07:00
John Arild Berentsen
e36c6b24ee Add secure inclusion of nodes for zwave network (#2715)
* Add secure inclusion of nodes for zwave network

* Add secure inclusion of nodes for zwave network
2016-08-02 20:17:10 +02:00
John Arild Berentsen
ad0224e9aa Add start and stop for zwave network (#2709) 2016-08-02 19:08:04 +02:00
John Arild Berentsen
40d7361828 Implement of BARRIER_OPERATOR for garage door (#2712) 2016-08-02 18:05:38 +02:00
Paulus Schoutsen
ab377f169d Upgrade to voluptuous 0.9.2 (#2692) 2016-08-02 00:14:13 -07:00
Tomi Tuhkanen
434a7d6975 Added VS Code config folder to gitignore (#2707) 2016-08-01 23:59:09 -07:00
Paulus Schoutsen
992be38b94 Upgrade netdisco (#2706) 2016-08-01 23:50:01 -07:00
Paulus Schoutsen
f50c30bbba Merge pull request #2704 from home-assistant/hotfix-0-25-2
Hotfix 0 25 2
2016-08-01 20:58:27 -07:00
Paulus Schoutsen
b1b14f0e83 Version bump to 0.25.2 2016-08-01 20:56:59 -07:00
Tobie Booth
b51ba85a15 Reverts changes to ZWave lock status update (#2595) (#2696) 2016-08-01 20:56:43 -07:00
Paulus Schoutsen
29dbeeb41e Remove SQLAlchemy as core dependency (#2702) 2016-08-01 18:37:00 -07:00
Tobie Booth
8b57fd008f Reverts changes to ZWave lock status update (#2595) (#2696) 2016-08-01 08:08:24 -07:00
Assaf Inbal
51d5268f9f Added a screenshot to LG Netcast TVs (#2694) 2016-07-31 21:58:55 -07:00
Paulus Schoutsen
6f23869a89 Fix Mac OS install script (#2691) 2016-07-31 20:58:39 -07:00
Sean Dague
483b0045fc support cooling season in proliphix thermostat (#2689)
Instead of always assuming we want to change the heat, instead use the
setback attribute which sets heat / cool setback based on current HVAC
mode. This means that the proliphix thermostat will do sensible things
during cooling season.
2016-07-31 19:13:36 -07:00
Paulus Schoutsen
1856e0110b Update frontend 2016-07-31 19:07:06 -07:00
Paulus Schoutsen
e7ead73fad Rollback voluptuous to 0.8.9 (#2687) 2016-07-31 17:20:08 -07:00
HBDK
a73c2e57a8 Added mired and kelvin mode to flux (#2665)
* Added mired and kelvin mode to flux

* changed as requested

* Renamed varible

* attempt to add test for new method in flux.py

* removed line to fix lint error
2016-07-31 16:55:48 -07:00
Paulus Schoutsen
c39c10a088 update frontend 2016-07-31 16:39:07 -07:00
Fabian Affolter
72fc77b84d Upgrade fuzzywuzzy to 0.11.1 (#2685) 2016-07-31 15:00:52 -07:00
Paulus Schoutsen
f4d6ce08e4 Update frontend 2016-07-31 14:47:01 -07:00
Johann Kellerman
e9bd5d54ad Recorder typing & ensure DB ready on query (#2680)
* Recorder typing & wait on DB ready
2016-07-31 22:56:57 +02:00
Fabian Affolter
2871ab6bb0 Upgrade sendgrid to 3.1.10 (#2684) 2016-07-31 13:49:01 -07:00
Jesse Newland
cfa69fef1e Add Docker test runner (#2673)
* Add docker test runner

* Move test Dockerfile into virtualization folder

* Don't build zwave in test environment
2016-07-31 13:48:41 -07:00
Fabian Affolter
5faba21b8c Upgrade python-nmap to 0.6.1 (#2681) 2016-07-31 13:47:46 -07:00
Fabian Affolter
ca1cf44194 Upgrade cherrypy to 7.1.0 (#2682) 2016-07-31 13:47:34 -07:00
Stephen Hoekstra
125059c5ac Add 5 second timeout to Kodi connections (#2683) 2016-07-31 13:47:24 -07:00
Robbie Trencheny
63ba5044b3 Kill celcius with fire, replacing it with celsius, finally finishing what #1860 started (#2679) 2016-07-31 12:18:40 -07:00
Robbie Trencheny
a93195610a Add alarm control panel services.yaml
...because I was almost done being bored
2016-07-31 11:49:30 -07:00
Robbie Trencheny
d48f6676ab Update lock services.yaml
Was so bored I forgot some things :(
2016-07-31 11:45:57 -07:00
Robbie Trencheny
794205ad8d Add garage door services.yaml
...because I was somehow still bored
2016-07-31 11:34:18 -07:00
Robbie Trencheny
0e367ceec6 Add lock services.yaml
...because I was still bored
2016-07-31 11:31:50 -07:00
Robbie Trencheny
44b9771d8a Add rollershutter services.yaml
...because I was bored
2016-07-31 11:27:57 -07:00
Teagan Glenn
122581da7f Proximity unit of measure (#2659)
* Allow multiple proximities

* Distance conversion

* Add unit of measurement and conversion to proximity

* Shorten attribute name

* Fix get unit of measurement

* Fix the km <-> m conversion

* Add type check and errors

* first path unit test around distance utility

* Fix numeric type check

* Fix conversion type-os

* Actually set the exception thrown flag

* Test for exact conversion

* More descriptive variable names

* Update method invocation to match change in method name

* Missed a couple variables

* Line continuation

* Fix linting too many return issue

* Break out proximity setup for list of proximity and for single proximity device

* Pass hass to setup function

* Check if setup succeeded for each proximity component

* Change variable name

* Break out branches in convert to avoid too many branches linting error

* Remove disable lint line

* Variables for default properties

* Combine logic

* Test loading multiple proximities for 100% code coverage on proximity component

* Unit test to reach 100%
Fail to configure proximities missing devices

* Fail first before processing

* Combine return statements

* lstrip = bad Teagan

* Utilize string formating instead of concatenation

* Fix variable reference

* Typeo

* Clean up conversion to reduce complexity

* Update unit tests to match code changes on distance util

* Test non numeric value

* Private methods, value type has already been checked.
2016-07-31 10:20:56 -07:00
Johann Kellerman
de7e27c92c Close session after execute. (#2677) 2016-07-31 10:10:30 -07:00
Paulus Schoutsen
89ec39f629 Update frontend 2016-07-31 00:43:28 -07:00
Jesse Newland
e0cbb92c05 Bring back delayed zwave value update behavior (#2674) 2016-07-31 09:09:00 +02:00
Paulus Schoutsen
b35c44ce04 Merge pull request #2671 from home-assistant/deflake-discovery-tests
Try to deflake discovery tests
2016-07-30 22:05:22 -07:00
Paulus Schoutsen
bbff13afee Try to deflake discovery tests 2016-07-30 19:58:14 -07:00
Paulus Schoutsen
af7f3bd455 Version bump to 0.26.0.dev0 2016-07-30 12:42:42 -07:00
545 changed files with 26381 additions and 5764 deletions

View File

@@ -4,6 +4,7 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
homeassistant/helpers/typing.py
# omit pieces of code that rely on external devices being present
homeassistant/components/apcupsd.py
@@ -88,6 +89,9 @@ omit =
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/knx.py
homeassistant/components/switch/knx.py
homeassistant/components/binary_sensor/knx.py
@@ -97,17 +101,29 @@ omit =
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/ffmpeg.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/generic.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/scsgate.py
homeassistant/components/cover/wink.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
@@ -122,9 +138,11 @@ omit =
homeassistant/components/device_tracker/ubus.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/garage_door/wink.py
homeassistant/components/foursquare.py
homeassistant/components/garage_door/rpi_gpio.py
homeassistant/components/garage_door/wink.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/joaoapps_join.py
@@ -167,8 +185,10 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
@@ -186,29 +206,40 @@ omit =
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
@@ -223,6 +254,7 @@ omit =
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/arest.py

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
.tox
.git

13
.gitignore vendored
View File

@@ -7,7 +7,9 @@ config/custom_components/*
!config/custom_components/example.py
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
!config/custom_components/react_panel
!config/panels
config/panels/*
!config/panels/react.html
tests/testing_config/deps
tests/testing_config/home-assistant.log
@@ -52,7 +54,8 @@ develop-eggs
lib
lib64
# Installer logs
# Logs
*.log
pip-log.txt
# Unit test / coverage reports
@@ -91,3 +94,9 @@ ctags.tmp
virtualization/vagrant/setup_done
virtualization/vagrant/.vagrant
virtualization/vagrant/config
# Visual Studio Code
.vscode
# Built docs
docs/build

View File

@@ -10,8 +10,8 @@ homeassistant:
# Impacts weather/sunrise data
elevation: 665
# C for Celsius, F for Fahrenheit
temperature_unit: C
# 'metric' for Metric System, 'imperial' for imperial system
unit_system: metric
# Pick yours from here:
# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones

View File

@@ -1,30 +0,0 @@
"""
Custom panel example showing TodoMVC using React.
Will add a panel to control lights and switches using React. Allows configuring
the title via configuration.yaml:
react_panel:
title: 'home'
"""
import os
from homeassistant.components.frontend import register_panel
DOMAIN = 'react_panel'
DEPENDENCIES = ['frontend']
PANEL_PATH = os.path.join(os.path.dirname(__file__), 'panel.html')
def setup(hass, config):
"""Initialize custom panel."""
title = config.get(DOMAIN, {}).get('title')
config = None if title is None else {'title': title}
register_panel(hass, 'react', PANEL_PATH,
title='TodoMVC', icon='mdi:checkbox-marked-outline',
config=config)
return True

View File

@@ -1,3 +1,20 @@
<!--
Custom Home Assistant panel example.
Currently only works in Firefox and Chrome because it uses ES6.
Make sure this file is in <config>/panels/react.html
Add to your configuration.yaml:
panel_custom:
- name: react
sidebar_title: TodoMVC
sidebar_icon: mdi:checkbox-marked-outline
config:
title: Wow hello!
-->
<script src="https://fb.me/react-15.2.1.min.js"></script>
<script src="https://fb.me/react-dom-15.2.1.min.js"></script>

230
docs/Makefile Normal file
View File

@@ -0,0 +1,230 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " livehtml to make standalone HTML files via sphinx-autobuild"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: livehtml
livehtml:
sphinx-autobuild -z ../homeassistant/ --port 0 -B -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Home-Assistant.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Home-Assistant.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Home-Assistant"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Home-Assistant"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."

0
docs/build/.empty vendored Normal file
View File

281
docs/make.bat Normal file
View File

@@ -0,0 +1,281 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
set I18NSPHINXOPTS=%SPHINXOPTS% source
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub3 to make an epub3
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
echo. dummy to check syntax errors of document sources
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Home-Assistant.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Home-Assistant.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "epub3" (
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
if "%1" == "dummy" (
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
if errorlevel 1 exit /b 1
echo.
echo.Build finished. Dummy builder generates no files.
goto end
)
:end

View File

@@ -0,0 +1,45 @@
"""
Sphinx extension to add ReadTheDocs-style "Edit on GitHub" links to the
sidebar.
Loosely based on https://github.com/astropy/astropy/pull/347
"""
import os
import warnings
__licence__ = 'BSD (3 clause)'
def get_github_url(app, view, path):
github_fmt = 'https://github.com/{}/{}/{}/{}{}'
return (
github_fmt.format(app.config.edit_on_github_project, view,
app.config.edit_on_github_branch,
app.config.edit_on_github_src_path, path))
def html_page_context(app, pagename, templatename, context, doctree):
if templatename != 'page.html':
return
if not app.config.edit_on_github_project:
warnings.warn("edit_on_github_project not specified")
return
if not doctree:
warnings.warn("doctree is None")
return
path = os.path.relpath(doctree.get('source'), app.builder.srcdir)
show_url = get_github_url(app, 'blob', path)
edit_url = get_github_url(app, 'edit', path)
context['show_on_github_url'] = show_url
context['edit_on_github_url'] = edit_url
def setup(app):
app.add_config_value('edit_on_github_project', '', True)
app.add_config_value('edit_on_github_branch', 'master', True)
app.add_config_value('edit_on_github_src_path', '', True) # 'eg' "docs/"
app.connect('html-page-context', html_page_context)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,8 @@
<ul>
<li><a href="https://community.home-assistant.io">📌 Community Forums</a></li>
<li><a href="https://github.com/home-assistant/home-assistant">🚀 GitHub</a></li>
<li><a href="https://home-assistant.io/">🏡 Homepage</a></li>
<li><a href="https://gitter.im/home-assistant/home-assistant">💬 Gitter</a></li>
<li><a href="https://pypi.python.org/pypi/homeassistant">💾 Download Releases</a></li>
</ul>
<hr>

View File

@@ -0,0 +1,13 @@
{%- if show_source and has_source and sourcename %}
<h3>{{ _('This Page') }}</h3>
<ul class="this-page-menu">
{%- if show_on_github_url %}
<li><a href="{{ show_on_github_url }}"
rel="nofollow">{{ _('Show on GitHub') }}</a></li>
{%- endif %}
{%- if edit_on_github_url %}
<li><a href="{{ edit_on_github_url }}"
rel="nofollow">{{ _('Edit on GitHub') }}</a></li>
{%- endif %}
</ul>
{%- endif %}

View File

@@ -0,0 +1,7 @@
.. _bootstrap_module:
:mod:`homeassistant.bootstrap`
-------------------------
.. automodule:: homeassistant.bootstrap
:members:

18
docs/source/api/core.rst Normal file
View File

@@ -0,0 +1,18 @@
.. _core_module:
:mod:`homeassistant.core`
-------------------------
.. automodule:: homeassistant.core
.. autoclass:: Config
:members:
.. autoclass:: EventBus
:members:
.. autoclass:: StateMachine
:members:
.. autoclass:: ServiceRegistry
:members:

View File

@@ -0,0 +1,10 @@
.. _components_device_tracker_module:
:mod:`homeassistant.components.device_tracker`
----------------------------------------------
.. automodule:: homeassistant.components.device_tracker
:members:
.. autoclass:: Device
:members:

View File

@@ -0,0 +1,12 @@
.. _helpers_entity_module:
:mod:`homeassistant.helpers.entity`
-----------------------------------
.. automodule:: homeassistant.helpers.entity
.. autoclass:: Entity
:members:
.. autoclass:: ToggleEntity
:members:

20
docs/source/api/event.rst Normal file
View File

@@ -0,0 +1,20 @@
.. _helpers_event_module:
:mod:`homeassistant.helpers.event`
----------------------------------
.. automodule:: homeassistant.helpers.event
.. autofunction:: track_state_change
.. autofunction:: track_point_in_time
.. autofunction:: track_point_in_utc_time
.. autofunction:: track_sunrise
.. autofunction:: track_sunset
.. autofunction:: track_utc_time_change
.. autofunction:: track_time_change

118
docs/source/api/helpers.rst Normal file
View File

@@ -0,0 +1,118 @@
homeassistant.helpers package
=============================
Submodules
----------
homeassistant.helpers.condition module
--------------------------------------
.. automodule:: homeassistant.helpers.condition
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.config_validation module
----------------------------------------------
.. automodule:: homeassistant.helpers.config_validation
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.discovery module
--------------------------------------
.. automodule:: homeassistant.helpers.discovery
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity module
-----------------------------------
.. automodule:: homeassistant.helpers.entity
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.entity_component module
---------------------------------------------
.. automodule:: homeassistant.helpers.entity_component
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.event module
----------------------------------
.. automodule:: homeassistant.helpers.event
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.event_decorators module
---------------------------------------------
.. automodule:: homeassistant.helpers.event_decorators
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.location module
-------------------------------------
.. automodule:: homeassistant.helpers.location
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.script module
-----------------------------------
.. automodule:: homeassistant.helpers.script
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.service module
------------------------------------
.. automodule:: homeassistant.helpers.service
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.state module
----------------------------------
.. automodule:: homeassistant.helpers.state
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.template module
-------------------------------------
.. automodule:: homeassistant.helpers.template
:members:
:undoc-members:
:show-inheritance:
homeassistant.helpers.typing module
-----------------------------------
.. automodule:: homeassistant.helpers.typing
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: homeassistant.helpers
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,78 @@
homeassistant package
=====================
Subpackages
-----------
.. toctree::
helpers
util
Submodules
----------
bootstrap module
------------------------------
.. automodule:: homeassistant.bootstrap
:members:
:undoc-members:
:show-inheritance:
config module
---------------------------
.. automodule:: homeassistant.config
:members:
:undoc-members:
:show-inheritance:
const module
--------------------------
.. automodule:: homeassistant.const
:members:
:undoc-members:
:show-inheritance:
core module
-------------------------
.. automodule:: homeassistant.core
:members:
:undoc-members:
:show-inheritance:
exceptions module
-------------------------------
.. automodule:: homeassistant.exceptions
:members:
:undoc-members:
:show-inheritance:
loader module
---------------------------
.. automodule:: homeassistant.loader
:members:
:undoc-members:
:show-inheritance:
remote module
---------------------------
.. automodule:: homeassistant.remote
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: homeassistant
:members:
:undoc-members:
:show-inheritance:

78
docs/source/api/util.rst Normal file
View File

@@ -0,0 +1,78 @@
homeassistant.util package
==========================
Submodules
----------
homeassistant.util.color module
-------------------------------
.. automodule:: homeassistant.util.color
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.distance module
----------------------------------
.. automodule:: homeassistant.util.distance
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.dt module
----------------------------
.. automodule:: homeassistant.util.dt
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.location module
----------------------------------
.. automodule:: homeassistant.util.location
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.package module
---------------------------------
.. automodule:: homeassistant.util.package
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.temperature module
-------------------------------------
.. automodule:: homeassistant.util.temperature
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.unit_system module
-------------------------------------
.. automodule:: homeassistant.util.unit_system
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.yaml module
------------------------------
.. automodule:: homeassistant.util.yaml
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: homeassistant.util
:members:
:undoc-members:
:show-inheritance:

419
docs/source/conf.py Normal file
View File

@@ -0,0 +1,419 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Home-Assistant documentation build configuration file, created by
# sphinx-quickstart on Sun Aug 28 13:13:10 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import sys
import os
from os.path import relpath
import inspect
from homeassistant.const import (__version__, __short_version__, PROJECT_NAME,
PROJECT_LONG_DESCRIPTION,
PROJECT_COPYRIGHT, PROJECT_AUTHOR,
PROJECT_GITHUB_USERNAME,
PROJECT_GITHUB_REPOSITORY,
GITHUB_PATH, GITHUB_URL)
sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('../homeassistant'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.linkcode',
'sphinx_autodoc_annotation',
'edit_on_github'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = PROJECT_NAME
copyright = PROJECT_COPYRIGHT
author = PROJECT_AUTHOR
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __short_version__
# The full version, including alpha/beta/rc tags.
release = __version__
code_branch = 'dev' if 'dev' in __version__ else 'master'
# Edit on Github config
edit_on_github_project = GITHUB_PATH
edit_on_github_branch = code_branch
edit_on_github_src_path = 'docs/source/'
def linkcode_resolve(domain, info):
"""
Determine the URL corresponding to Python object
"""
if domain != 'py':
return None
modname = info['module']
fullname = info['fullname']
submod = sys.modules.get(modname)
if submod is None:
return None
obj = submod
for part in fullname.split('.'):
try:
obj = getattr(obj, part)
except:
return None
try:
fn = inspect.getsourcefile(obj)
except:
fn = None
if not fn:
return None
try:
source, lineno = inspect.findsource(obj)
except:
lineno = None
if lineno:
linespec = "#L%d" % (lineno + 1)
else:
linespec = ""
fn = relpath(fn, start='../')
return '{}/blob/{}/{}{}'.format(GITHUB_URL, code_branch, fn, linespec)
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'logo': 'logo.png',
'logo_name': PROJECT_NAME,
'description': PROJECT_LONG_DESCRIPTION,
'github_user': PROJECT_GITHUB_USERNAME,
'github_repo': PROJECT_GITHUB_REPOSITORY,
'github_type': 'star',
'github_banner': True,
'travis_button': True,
'touch_icon': 'logo-apple.png',
# 'fixed_sidebar': True, # Re-enable when we have more content
}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'Home-Assistant v0.27.0'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = '_static/logo.png'
# The name of an image file (relative to this directory) to use as a favicon of
# the docs.
# This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
html_favicon = '_static/favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
html_sidebars = {
'**': [
'about.html',
'links.html',
'searchbox.html',
'sourcelink.html',
'navigation.html',
'relations.html'
]
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Home-Assistantdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Home-Assistant.tex', 'Home-Assistant Documentation',
'Home-Assistant Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'home-assistant', 'Home-Assistant Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Home-Assistant', 'Home-Assistant Documentation',
author, 'Home-Assistant', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False

22
docs/source/index.rst Normal file
View File

@@ -0,0 +1,22 @@
================================
Home Assistant API Documentation
================================
Public API documentation for `Home Assistant developers`_.
Contents:
.. toctree::
:maxdepth: 2
:glob:
api/*
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _Home Assistant developers: https://home-assistant.io/developers/

View File

@@ -419,8 +419,9 @@ definitions:
description: Longitude of Home Assistant server
location_name:
type: string
temperature_unit:
unit_system:
type: string
description: The system for measurement units
time_zone:
type: string
version:

View File

@@ -274,7 +274,7 @@ def try_to_restart() -> None:
# thread left (which is us). Nothing we really do with it, but it might be
# useful when debugging shutdown/restart issues.
try:
nthreads = sum(thread.isAlive() and not thread.isDaemon()
nthreads = sum(thread.is_alive() and not thread.daemon
for thread in threading.enumerate())
if nthreads > 1:
sys.stderr.write(

View File

@@ -11,14 +11,15 @@ from types import ModuleType
from typing import Any, Optional, Dict
import voluptuous as vol
from voluptuous.humanize import humanize_error
import homeassistant.components as core_components
from homeassistant.components import group, persistent_notification
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
import homeassistant.util.package as pkg_util
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
@@ -89,67 +90,12 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
domain, domain)
return False
config = prepare_setup_component(hass, config, domain)
if config is None:
return False
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
cv.log_exception(_LOGGER, ex, domain, config)
return False
elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
cv.log_exception(_LOGGER, ex, domain, p_config)
return False
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue
platform = prepare_setup_platform(hass, config, domain,
p_name)
if platform is None:
return False
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
cv.log_exception(_LOGGER, ex, '{}.{}'
.format(domain, p_name), p_validated)
return False
platforms.append(p_validated)
# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms
if not _handle_requirements(hass, component, domain):
return False
_CURRENT_SETUP.append(domain)
try:
@@ -172,7 +118,7 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
if 'group' not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker()
hass.bus.fire(
@@ -181,6 +127,74 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
return True
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config."""
# pylint: disable=too-many-return-statements
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return None
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, config)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, p_config)
return None
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue
platform = prepare_setup_platform(hass, config, domain,
p_name)
if platform is None:
return None
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return None
platforms.append(p_validated)
# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms
if not _handle_requirements(hass, component, domain):
return None
return config
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
@@ -232,14 +246,14 @@ def from_config_dict(config: Dict[str, Any],
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
_mount_local_lib_path(config_dir)
mount_local_lib_path(config_dir)
core_config = config.get(core.DOMAIN, {})
try:
conf_util.process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
log_exception(ex, 'homeassistant', core_config)
return None
conf_util.process_ha_config_upgrade(hass)
@@ -300,7 +314,7 @@ def from_config_file(config_path: str,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
_mount_local_lib_path(config_dir)
mount_local_lib_path(config_dir)
enable_logging(hass, verbose, log_rotate_days)
@@ -308,6 +322,8 @@ def from_config_file(config_path: str,
config_dict = conf_util.load_yaml_config_file(config_path)
except HomeAssistantError:
return None
finally:
clear_secret_cache()
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)
@@ -371,6 +387,26 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
loader.prepare(hass)
def _mount_local_lib_path(config_dir: str) -> None:
def log_exception(ex, domain, config):
"""Generate log exception for config validation."""
message = 'Invalid config for [{}]: '.format(domain)
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join('%s' % m for m in ex.path))
else:
message += humanize_error(config, ex)
if hasattr(config, '__line__'):
message += " (See {}:{})".format(config.__config_file__,
config.__line__ or '?')
_LOGGER.error(message)
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))
deps_dir = os.path.join(config_dir, 'deps')
if deps_dir not in sys.path:
sys.path.insert(0, os.path.join(config_dir, 'deps'))
return deps_dir

View File

@@ -11,7 +11,6 @@ import itertools as it
import logging
import homeassistant.core as ha
from homeassistant.helpers.entity import split_entity_id
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import get_component
from homeassistant.const import (
@@ -35,7 +34,7 @@ def is_on(hass, entity_id=None):
entity_ids = hass.states.entity_ids()
for entity_id in entity_ids:
domain = split_entity_id(entity_id)[0]
domain = ha.split_entity_id(entity_id)[0]
module = get_component(domain)
@@ -95,7 +94,7 @@ def setup(hass, config):
# Group entity_ids by domain. groupby requires sorted data.
by_domain = it.groupby(sorted(entity_ids),
lambda item: split_entity_id(item)[0])
lambda item: ha.split_entity_id(item)[0])
for domain, ent_ids in by_domain:
# We want to block for all calls and only return when all calls

View File

@@ -20,6 +20,7 @@ from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel'
SCAN_INTERVAL = 30
ATTR_CHANGED_BY = 'changed_by'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -124,6 +125,11 @@ class AlarmControlPanel(Entity):
"""Regex for code format or None if no code is required."""
return None
@property
def changed_by(self):
"""Last change triggered by."""
return None
def alarm_disarm(self, code=None):
"""Send disarm command."""
raise NotImplementedError()
@@ -145,5 +151,6 @@ class AlarmControlPanel(Entity):
"""Return the state attributes."""
state_attr = {
ATTR_CODE_FORMAT: self.code_format,
ATTR_CHANGED_BY: self.changed_by
}
return state_attr

View File

@@ -6,34 +6,40 @@ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import logging
import voluptuous as vol
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)
_LOGGER = logging.getLogger(__name__)
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom'
'/archive/0.1.1.zip'
'#pyalarmdotcom==0.1.1']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Alarm.com'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_CODE): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup an Alarm.com control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify username and password!')
return False
add_devices([AlarmDotCom(hass,
config.get('name', DEFAULT_NAME),
config.get('code'),
username,
password)])
add_devices([AlarmDotCom(hass, name, code, username, password)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
@@ -80,7 +86,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, 'arming home'):
if not self._validate_code(code, 'disarming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command

View File

@@ -10,5 +10,5 @@ import homeassistant.components.alarm_control_panel.manual as manual
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo alarm control panel platform."""
add_devices([
manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10),
manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10, False),
])

View File

@@ -7,28 +7,46 @@ https://home-assistant.io/components/alarm_control_panel.manual/
import datetime
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
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
_LOGGER = logging.getLogger(__name__)
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
DEFAULT_DISARM_AFTER_TRIGGER = False
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'manual',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the manual alarm platform."""
add_devices([ManualAlarm(
hass,
config.get('name', DEFAULT_ALARM_NAME),
config.get('code'),
config.get('pending_time', DEFAULT_PENDING_TIME),
config.get('trigger_time', DEFAULT_TRIGGER_TIME),
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_PENDING_TIME, DEFAULT_PENDING_TIME),
config.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER)
)])
@@ -40,10 +58,12 @@ class ManualAlarm(alarm.AlarmControlPanel):
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to disarmed.
triggered for 'trigger_time', after that we return to the previous state
or disarm if `disarm_after_trigger` is true.
"""
def __init__(self, hass, name, code, pending_time, trigger_time):
def __init__(self, hass, name, code, pending_time,
trigger_time, disarm_after_trigger):
"""Initalize the manual alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
@@ -51,6 +71,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._code = str(code) if code else None
self._pending_time = datetime.timedelta(seconds=pending_time)
self._trigger_time = datetime.timedelta(seconds=trigger_time)
self._disarm_after_trigger = disarm_after_trigger
self._pre_trigger_state = self._state
self._state_ts = None
@property
@@ -77,7 +99,10 @@ class ManualAlarm(alarm.AlarmControlPanel):
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time +
self._trigger_time) < dt_util.utcnow():
return STATE_ALARM_DISARMED
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
return self._pre_trigger_state
return self._state
@@ -125,6 +150,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.update_ha_state()

View File

@@ -13,33 +13,31 @@ import homeassistant.components.mqtt as mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME)
CONF_NAME, CONF_CODE)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_CODE = 'code'
DEFAULT_NAME = "MQTT Alarm"
DEFAULT_DISARM = "DISARM"
DEFAULT_ARM_HOME = "ARM_HOME"
DEFAULT_ARM_AWAY = "ARM_AWAY"
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
DEFAULT_DISARM = 'DISARM'
DEFAULT_NAME = 'MQTT Alarm'
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
})
@@ -47,20 +45,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT platform."""
add_devices([MqttAlarm(
hass,
config[CONF_NAME],
config[CONF_STATE_TOPIC],
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_PAYLOAD_DISARM],
config[CONF_PAYLOAD_ARM_HOME],
config[CONF_PAYLOAD_ARM_AWAY],
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
config.get(CONF_QOS),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_CODE))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel):
"""Represent a MQTT alarm status."""
"""Representation of a MQTT alarm status."""
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away, code):

View File

@@ -7,22 +7,40 @@ https://home-assistant.io/components/alarm_control_panel.nx584/
import logging
import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'NX584'
DEFAULT_PORT = 5007
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup nx584 platform."""
host = config.get('host', 'localhost:5007')
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
url = 'http://{}:{}'.format(host, port)
try:
add_devices([NX584Alarm(hass, host, config.get('name', 'NX584'))])
add_devices([NX584Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
return False
@@ -31,13 +49,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NX584Alarm(alarm.AlarmControlPanel):
"""Represents the NX584-based alarm panel."""
def __init__(self, hass, host, name):
def __init__(self, hass, url, name):
"""Initalize the nx584 alarm panel."""
from nx584 import client
self._hass = hass
self._host = host
self._name = name
self._alarm = client.Client('http://%s' % host)
self._url = url
self._alarm = client.Client(self._url)
# Do an initial list operation so that we will try to actually
# talk to the API and trigger a requests exception for setup_platform()
# to catch
@@ -66,7 +84,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
zones = self._alarm.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._host, reason=ex))
dict(host=self._url, reason=ex))
return STATE_UNKNOWN
except IndexError:
_LOGGER.error('nx584 reports no partitions')

View File

@@ -0,0 +1,43 @@
alarm_disarm:
description: Send the alarm the command for disarm
fields:
entity_id:
description: Name of alarm control panel to disarm
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to disarm the alarm control panel with
example: 1234
alarm_arm_home:
description: Send the alarm the command for arm home
fields:
entity_id:
description: Name of alarm control panel to arm home
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm home the alarm control panel with
example: 1234
alarm_arm_away:
description: Send the alarm the command for arm away
fields:
entity_id:
description: Name of alarm control panel to arm away
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm away the alarm control panel with
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger
fields:
entity_id:
description: Name of alarm control panel to trigger
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to trigger the alarm control panel with
example: 1234

View File

@@ -6,32 +6,39 @@ https://home-assistant.io/components/alarm_control_panel.simplisafe/
"""
import logging
import voluptuous as vol
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_UNKNOWN,
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/w1ll1am23/simplisafe-python/archive/'
'586fede0e85fd69e56e516aaa8e97eb644ca8866.zip#'
'simplisafe-python==0.0.1']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SimpliSafe platform."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify username and password!')
return False
add_devices([SimpliSafeAlarm(
config.get('name', "SimpliSafe"),
username,
password,
config.get('code'))])
add_devices([SimpliSafeAlarm(name, username, password, code)])
# pylint: disable=abstract-method

View File

@@ -8,7 +8,7 @@ import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import (CONF_ALARM, CONF_CODE_DIGITS)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
@@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Verisure platform."""
alarms = []
if int(hub.config.get('alarm', '1')):
if int(hub.config.get(CONF_ALARM, 1)):
hub.update_alarms()
alarms.extend([
VerisureAlarm(value.id)
@@ -36,7 +36,8 @@ class VerisureAlarm(alarm.AlarmControlPanel):
"""Initalize the Verisure alarm panel."""
self._id = device_id
self._state = STATE_UNKNOWN
self._digits = int(hub.config.get('code_digits', '4'))
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
@property
def name(self):
@@ -58,6 +59,11 @@ class VerisureAlarm(alarm.AlarmControlPanel):
"""The code format as regex."""
return '^\\d{%s}$' % self._digits
@property
def changed_by(self):
"""Last change triggered by."""
return self._changed_by
def update(self):
"""Update alarm status."""
hub.update_alarms()
@@ -72,6 +78,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
_LOGGER.error(
'Unknown alarm state %s',
hub.alarm_status[self._id].status)
self._changed_by = hub.alarm_status[self._id].name
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -11,17 +11,17 @@ from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script
from homeassistant.components.http import HomeAssistantView
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
API_ENDPOINT = '/api/alexa'
CONF_INTENTS = 'intents'
CONF_CARD = 'card'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
CONF_CARD = 'card'
CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
def setup(hass, config):

View File

@@ -7,35 +7,43 @@ https://home-assistant.io/components/apcupsd/
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
DOMAIN = "apcupsd"
REQUIREMENTS = ("apcaccess==0.0.4",)
REQUIREMENTS = ['apcaccess==0.0.4']
CONF_HOST = "host"
CONF_PORT = "port"
CONF_TYPE = "type"
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = "localhost"
CONF_TYPE = 'type'
DATA = None
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 3551
DOMAIN = 'apcupsd'
KEY_STATUS = "STATUS"
VALUE_ONLINE = "ONLINE"
KEY_STATUS = 'STATUS'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
DATA = None
VALUE_ONLINE = 'ONLINE'
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Use config values to set up a function enabling status retrieval."""
global DATA
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
port = config[DOMAIN].get(CONF_PORT, DEFAULT_PORT)
conf = config[DOMAIN]
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
DATA = APCUPSdData(host, port)

View File

@@ -98,31 +98,32 @@ class APIEventStream(HomeAssistantView):
def stream():
"""Stream events to response."""
self.hass.bus.listen(MATCH_ALL, forward_events)
unsub_stream = self.hass.bus.listen(MATCH_ALL, forward_events)
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
# Fire off one message right away to have browsers fire open event
to_write.put(STREAM_PING_PAYLOAD)
# Fire off one message so browsers fire open event right away
to_write.put(STREAM_PING_PAYLOAD)
while True:
try:
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
while True:
try:
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
if payload is stop_obj:
if payload is stop_obj:
break
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield msg.encode("UTF-8")
except queue.Empty:
to_write.put(STREAM_PING_PAYLOAD)
except GeneratorExit:
break
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield msg.encode("UTF-8")
except queue.Empty:
to_write.put(STREAM_PING_PAYLOAD)
except GeneratorExit:
break
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
self.hass.bus.remove_listener(MATCH_ALL, forward_events)
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
unsub_stream()
return self.Response(stream(), mimetype='text/event-stream')

View File

@@ -6,27 +6,34 @@ https://home-assistant.io/components/arduino/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import validate_config
from homeassistant.const import CONF_PORT
import homeassistant.helpers.config_validation as cv
DOMAIN = "arduino"
REQUIREMENTS = ['PyMata==2.12']
BOARD = None
_LOGGER = logging.getLogger(__name__)
BOARD = None
DOMAIN = 'arduino'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PORT): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup the Arduino component."""
if not validate_config(config,
{DOMAIN: ['port']},
_LOGGER):
return False
import serial
global BOARD
try:
BOARD = ArduinoBoard(config[DOMAIN]['port'])
BOARD = ArduinoBoard(config[DOMAIN][CONF_PORT])
except (serial.serialutil.SerialException, FileNotFoundError):
_LOGGER.exception("Your port is not accessible.")
return False

View File

@@ -4,19 +4,28 @@ Allow to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
from functools import partial
import logging
import os
import voluptuous as vol
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
@@ -36,6 +45,11 @@ DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
METHOD_TRIGGER = 'trigger'
METHOD_IF_ACTION = 'if_action'
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
@@ -88,41 +102,206 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
CONF_CONDITION: _CONDITION_SCHEMA,
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})
SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
TRIGGER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_VARIABLES, default={}): dict,
})
RELOAD_SERVICE_SCHEMA = vol.Schema({})
def is_on(hass, entity_id=None):
"""
Return true if specified automation entity_id is on.
Check all automation if no entity_id specified.
"""
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(hass.states.is_state(entity_id, STATE_ON)
for entity_id in entity_ids)
def turn_on(hass, entity_id=None):
"""Turn on specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None):
"""Turn off specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def toggle(hass, entity_id=None):
"""Toggle specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
def trigger(hass, entity_id=None):
"""Trigger specified automation or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
def reload(hass):
"""Reload the automation from config."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
def setup(hass, config):
"""Setup the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
success = _process_config(hass, config, component)
if not success:
return False
descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
entity.trigger(service_call.data.get(ATTR_VARIABLES))
def service_handler(service_call):
"""Handle automation service calls."""
for entity in component.extract_from_service(service_call):
getattr(entity, service_call.service)()
def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
conf = component.prepare_reload()
if conf is None:
return
_process_config(hass, conf, component)
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
schema=TRIGGER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE):
hass.services.register(DOMAIN, service, service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)
return True
class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
def __init__(self, name, attach_triggers, cond_func, action):
"""Initialize an automation entity."""
self._name = name
self._attach_triggers = attach_triggers
self._detach_triggers = attach_triggers(self.trigger)
self._cond_func = cond_func
self._action = action
self._enabled = True
self._last_triggered = None
@property
def name(self):
"""Name of the automation."""
return self._name
@property
def should_poll(self):
"""No polling needed for automation entities."""
return False
@property
def state_attributes(self):
"""Return the entity state attributes."""
return {
ATTR_LAST_TRIGGERED: self._last_triggered
}
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._enabled
def turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
if self._enabled:
return
self._detach_triggers = self._attach_triggers(self.trigger)
self._enabled = True
self.update_ha_state()
def turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
return
self._detach_triggers()
self._detach_triggers = None
self._enabled = False
self.update_ha_state()
def trigger(self, variables):
"""Trigger automation."""
if self._cond_func(variables):
self._action(variables)
self._last_triggered = utcnow()
self.update_ha_state()
def remove(self):
"""Remove automation from HASS."""
self.turn_off()
super().remove()
def _process_config(hass, config, component):
"""Process config and add automations."""
success = False
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
for list_no, config_block in enumerate(conf):
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
list_no))
success = (_setup_automation(hass, config_block, name, config) or
success)
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
list_no)
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)
if cond_func is None:
continue
else:
def cond_func(variables):
"""Condition will always pass."""
return True
attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action)
component.add_entities((entity,))
success = True
return success
def _setup_automation(hass, config_block, name, config):
"""Setup one instance of automation."""
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if CONF_CONDITION in config_block:
action = _process_if(hass, config, config_block, action)
if action is None:
return False
_process_trigger(hass, config, config_block.get(CONF_TRIGGER, []), name,
action)
return True
def _get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
@@ -136,7 +315,7 @@ def _get_action(hass, config, name):
return action
def _process_if(hass, config, p_config, action):
def _process_if(hass, config, p_config):
"""Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
@@ -182,29 +361,43 @@ def _process_if(hass, config, p_config, action):
if cond_type == CONDITION_TYPE_AND:
def if_action(variables=None):
"""AND all conditions."""
if all(check(hass, variables) for check in checks):
action(variables)
return all(check(hass, variables) for check in checks)
else:
def if_action(variables=None):
"""OR all conditions."""
if any(check(hass, variables) for check in checks):
action(variables)
return any(check(hass, variables) for check in checks)
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers."""
removes = []
for conf in trigger_configs:
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
conf.get(CONF_PLATFORM))
if platform is None:
continue
if platform.trigger(hass, conf, action):
_LOGGER.info("Initialized rule %s", name)
else:
remove = platform.trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up rule %s", name)
continue
_LOGGER.info("Initialized rule %s", name)
removes.append(remove)
if not removes:
return None
def remove_triggers():
"""Remove attached triggers."""
for remove in removes:
remove()
return remove_triggers
def _resolve_platform(method, hass, config, platform):

View File

@@ -39,5 +39,4 @@ def trigger(hass, config, action):
},
})
hass.bus.listen(event_type, handle_event)
return True
return hass.bus.listen(event_type, handle_event)

View File

@@ -7,13 +7,12 @@ at https://home-assistant.io/components/automation/#mqtt-trigger
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_PLATFORM
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'topic'
CONF_PAYLOAD = 'payload'
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
@@ -24,7 +23,7 @@ TRIGGER_SCHEMA = vol.Schema({
def trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config[CONF_TOPIC]
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
def mqtt_automation_listener(msg_topic, msg_payload, qos):
@@ -39,6 +38,4 @@ def trigger(hass, config, action):
}
})
mqtt.subscribe(hass, topic, mqtt_automation_listener)
return True
return mqtt.subscribe(hass, topic, mqtt_automation_listener)

View File

@@ -63,7 +63,4 @@ def trigger(hass, config, action):
action(variables)
track_state_change(
hass, entity_id, state_automation_listener)
return True
return track_state_change(hass, entity_id, state_automation_listener)

View File

@@ -0,0 +1,34 @@
turn_on:
description: Enable an automation.
fields:
entity_id:
description: Name of the automation to turn on.
example: 'automation.notify_home'
turn_off:
description: Disable an automation.
fields:
entity_id:
description: Name of the automation to turn off.
example: 'automation.notify_home'
toggle:
description: Toggle an automation.
fields:
entity_id:
description: Name of the automation to toggle on/off.
example: 'automation.notify_home'
trigger:
description: Trigger the action of an automation.
fields:
entity_id:
description: Name of the automation to trigger.
example: 'automation.notify_home'
reload:
description: Reload the automation configuration.

View File

@@ -7,8 +7,7 @@ at https://home-assistant.io/components/automation/#state-trigger
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.const import (
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, CONF_PLATFORM)
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
from homeassistant.helpers.event import track_state_change, track_point_in_time
import homeassistant.helpers.config_validation as cv
@@ -39,9 +38,13 @@ def trigger(hass, config, action):
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
time_delta = config.get(CONF_FOR)
remove_state_for_cancel = None
remove_state_for_listener = None
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal remove_state_for_cancel, remove_state_for_listener
def call_action():
"""Call action with right context."""
action({
@@ -60,26 +63,33 @@ def trigger(hass, config, action):
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
hass.bus.remove_listener(
EVENT_STATE_CHANGED, attached_state_for_cancel)
remove_state_for_cancel()
call_action()
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s.state == to_s.state:
return
hass.bus.remove_listener(EVENT_TIME_CHANGED,
attached_state_for_listener)
hass.bus.remove_listener(EVENT_STATE_CHANGED,
attached_state_for_cancel)
remove_state_for_listener()
remove_state_for_cancel()
attached_state_for_listener = track_point_in_time(
remove_state_for_listener = track_point_in_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
attached_state_for_cancel = track_state_change(
remove_state_for_cancel = track_state_change(
hass, entity, state_for_cancel_listener)
track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
unsub = track_state_change(hass, entity_id, state_automation_listener,
from_state, to_state)
return True
def remove():
"""Remove state listeners."""
unsub()
# pylint: disable=not-callable
if remove_state_for_cancel is not None:
remove_state_for_cancel()
if remove_state_for_listener is not None:
remove_state_for_listener()
return remove

View File

@@ -42,8 +42,6 @@ def trigger(hass, config, action):
# Do something to call action
if event == SUN_EVENT_SUNRISE:
track_sunrise(hass, call_action, offset)
return track_sunrise(hass, call_action, offset)
else:
track_sunset(hass, call_action, offset)
return True
return track_sunset(hass, call_action, offset)

View File

@@ -49,5 +49,4 @@ def trigger(hass, config, action):
elif not template_result:
already_triggered = False
track_state_change(hass, MATCH_ALL, state_changed_listener)
return True
return track_state_change(hass, MATCH_ALL, state_changed_listener)

View File

@@ -47,7 +47,5 @@ def trigger(hass, config, action):
},
})
track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
return True
return track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)

View File

@@ -58,7 +58,5 @@ def trigger(hass, config, action):
},
})
track_state_change(
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
return True
return track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)

View File

@@ -6,6 +6,8 @@ https://home-assistant.io/components/binary_sensor/
"""
import logging
import voluptuous as vol
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
@@ -25,6 +27,7 @@ SENSOR_CLASSES = [
'moisture', # Specifically a wetness sensor
'motion', # Motion sensor
'moving', # On means moving, Off means stopped
'occupancy', # On means occupied, Off means not occupied
'opening', # Door, window, etc.
'power', # Power, over-current, etc
'safety', # Generic on=unsafe, off=safe
@@ -33,6 +36,8 @@ SENSOR_CLASSES = [
'vibration', # On means vibration detected, Off means no vibration
]
SENSOR_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(SENSOR_CLASSES))
def setup(hass, config):
"""Track states and offer events for binary sensors."""

View File

@@ -4,23 +4,32 @@ Support for tracking the online status of a UPS.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.apcupsd/
"""
from homeassistant.components import apcupsd
from homeassistant.components.binary_sensor import BinarySensorDevice
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.components import apcupsd
DEFAULT_NAME = 'UPS Online Status'
DEPENDENCIES = [apcupsd.DOMAIN]
DEFAULT_NAME = "UPS Online Status"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Instantiate an OnlineStatus binary sensor entity."""
"""Setup an Online Status binary sensor."""
add_entities((OnlineStatus(config, apcupsd.DATA),))
class OnlineStatus(BinarySensorDevice):
"""Represent UPS online status."""
"""Representation of an UPS online status."""
def __init__(self, config, data):
"""Initialize the APCUPSd device."""
"""Initialize the APCUPSd binary device."""
self._config = config
self._data = data
self._state = None
@@ -29,7 +38,7 @@ class OnlineStatus(BinarySensorDevice):
@property
def name(self):
"""Return the name of the UPS online status sensor."""
return self._config.get("name", DEFAULT_NAME)
return self._config.get(CONF_NAME)
@property
def is_on(self):

View File

@@ -6,32 +6,39 @@ https://home-assistant.io/components/binary_sensor.bloomsky/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
import voluptuous as vol
DEPENDENCIES = ["bloomsky"]
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['bloomsky']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"Rain": "moisture",
"Night": None,
'Rain': 'moisture',
'Night': None,
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the available BloomSky weather binary sensors."""
logger = logging.getLogger(__name__)
bloomsky = get_component('bloomsky')
sensors = config.get('monitored_conditions', SENSOR_TYPES)
# Default needed in case of discovery
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
for device in bloomsky.BLOOMSKY.devices.values():
for variable in sensors:
if variable in SENSOR_TYPES:
add_devices([BloomSkySensor(bloomsky.BLOOMSKY,
device,
variable)])
else:
logger.error("Cannot find definition for device: %s", variable)
add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)])
class BloomSkySensor(BinarySensorDevice):
@@ -40,10 +47,10 @@ class BloomSkySensor(BinarySensorDevice):
def __init__(self, bs, device, sensor_name):
"""Initialize a BloomSky binary sensor."""
self._bloomsky = bs
self._device_id = device["DeviceID"]
self._device_id = device['DeviceID']
self._sensor_name = sensor_name
self._name = "{} {}".format(device["DeviceName"], sensor_name)
self._unique_id = "bloomsky_binary_sensor {}".format(self._name)
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
self.update()
@property
@@ -71,4 +78,4 @@ class BloomSkySensor(BinarySensorDevice):
self._bloomsky.refresh_devices()
self._state = \
self._bloomsky.devices[self._device_id]["Data"][self._sensor_name]
self._bloomsky.devices[self._device_id]['Data'][self._sensor_name]

View File

@@ -7,46 +7,50 @@ https://home-assistant.io/components/binary_sensor.command_line/
import logging
from datetime import timedelta
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Binary Command Sensor"
DEFAULT_SENSOR_CLASS = None
DEFAULT_NAME = 'Binary Command Sensor'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Command Sensor."""
if config.get('command') is None:
_LOGGER.error('Missing required variable: "command"')
return False
"""Setup the Command line Binary Sensor."""
name = config.get(CONF_NAME)
command = config.get(CONF_COMMAND)
payload_off = config.get(CONF_PAYLOAD_OFF)
payload_on = config.get(CONF_PAYLOAD_ON)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = DEFAULT_SENSOR_CLASS
data = CommandSensorData(config.get('command'))
data = CommandSensorData(command)
add_devices([CommandBinarySensor(
hass,
data,
config.get('name', DEFAULT_NAME),
sensor_class,
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE)
)])
hass, data, name, sensor_class, payload_on, payload_off,
value_template)])
# pylint: disable=too-many-arguments, too-many-instance-attributes

View File

@@ -0,0 +1,72 @@
"""
Support for Ecobee sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ecobee/
"""
from homeassistant.components import ecobee
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['ecobee']
ECOBEE_CONFIG_FILE = 'ecobee.conf'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Ecobee sensors."""
if discovery_info is None:
return
data = ecobee.NETWORK
dev = list()
for index in range(len(data.ecobee.thermostats)):
for sensor in data.ecobee.get_remote_sensors(index):
for item in sensor['capability']:
if item['type'] != 'occupancy':
continue
dev.append(EcobeeBinarySensor(sensor['name'], index))
add_devices(dev)
class EcobeeBinarySensor(BinarySensorDevice):
"""Representation of an Ecobee sensor."""
def __init__(self, sensor_name, sensor_index):
"""Initialize the sensor."""
self._name = sensor_name + ' Occupancy'
self.sensor_name = sensor_name
self.index = sensor_index
self._state = None
self._sensor_class = 'occupancy'
self.update()
@property
def name(self):
"""Return the name of the Ecobee sensor."""
return self._name.rstrip()
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state == 'true'
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "binary_sensor_ecobee_{}_{}".format(self._name, self.index)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._sensor_class
def update(self):
"""Get the latest state of the sensor."""
data = ecobee.NETWORK
data.update()
for sensor in data.ecobee.get_remote_sensors(self.index):
for item in sensor['capability']:
if (item['type'] == 'occupancy' and
self.sensor_name == sensor['name']):
self._state = item['value']

View File

@@ -4,27 +4,41 @@ Support for EnOcean binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.enocean/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
from homeassistant.components import enocean
from homeassistant.const import CONF_NAME
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_SENSOR_CLASS)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ["enocean"]
_LOGGER = logging.getLogger(__name__)
CONF_ID = "id"
DEPENDENCIES = ['enocean']
DEFAULT_NAME = 'EnOcean binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Binary Sensor platform fo EnOcean."""
dev_id = config.get(CONF_ID, None)
devname = config.get(CONF_NAME, "EnOcean binary sensor")
add_devices([EnOceanBinarySensor(dev_id, devname)])
dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME)
sensor_class = config.get(CONF_SENSOR_CLASS)
add_devices([EnOceanBinarySensor(dev_id, devname, sensor_class)])
class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
"""Representation of EnOcean binary sensors such as wall switches."""
def __init__(self, dev_id, devname):
def __init__(self, dev_id, devname, sensor_class):
"""Initialize the EnOcean binary sensor."""
enocean.EnOceanDevice.__init__(self)
self.stype = "listener"
@@ -32,12 +46,18 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
self.which = -1
self.onoff = -1
self.devname = devname
self._sensor_class = sensor_class
@property
def name(self):
"""The default name for the binary sensor."""
return self.devname
@property
def sensor_class(self):
"""Return the class of this sensor."""
return self._sensor_class
def value_changed(self, value, value2):
"""Fire an event with the data that have changed.

View File

@@ -0,0 +1,215 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg/
"""
import logging
from os import path
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (BinarySensorDevice,
PLATFORM_SCHEMA, DOMAIN)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
ATTR_ENTITY_ID)
REQUIREMENTS = ["ha-ffmpeg==0.10"]
SERVICE_RESTART = 'ffmpeg_restart'
FFMPEG_SENSOR_NOISE = 'noise'
FFMPEG_SENSOR_MOTION = 'motion'
MAP_FFMPEG_BIN = [
FFMPEG_SENSOR_NOISE,
FFMPEG_SENSOR_MOTION
]
CONF_TOOL = 'tool'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
CONF_OUTPUT = 'output'
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string,
vol.Optional(CONF_NAME, default="FFmpeg"): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
vol.Optional(CONF_DURATION, default=1):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_CHANGES, default=10):
vol.All(vol.Coerce(float), vol.Range(min=0, max=99)),
vol.Optional(CONF_REPEAT, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_REPEAT_TIME, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
})
SERVICE_RESTART_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
# list of all ffmpeg sensors
DEVICES = []
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(SensorNoise, config)
else:
entity = FFmpegMotion(SensorMotion, config)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, entity.shutdown_ffmpeg)
# add to system
add_entities([entity])
DEVICES.append(entity)
# exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
return True
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
# register service
def _service_handle_restart(service):
"""Handle service binary_sensor.ffmpeg_restart."""
entity_ids = service.data.get('entity_id')
if entity_ids:
_devices = [device for device in DEVICES
if device.entity_id in entity_ids]
else:
_devices = DEVICES
for device in _devices:
device.reset_ffmpeg()
hass.services.register(DOMAIN, SERVICE_RESTART,
_service_handle_restart,
descriptions.get(SERVICE_RESTART),
schema=SERVICE_RESTART_SCHEMA)
return True
class FFmpegBinarySensor(BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, ffobj, config):
"""Constructor for binary sensor noise detection."""
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(config.get(CONF_FFMPEG_BIN), self._callback)
self._start_ffmpeg(config)
def _callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.update_ha_state()
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
raise NotImplementedError
def shutdown_ffmpeg(self, event):
"""For STOP event to shutdown ffmpeg."""
self._ffmpeg.close()
def reset_ffmpeg(self):
"""Restart ffmpeg with new config."""
self._ffmpeg.close()
self._start_ffmpeg(self._config)
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def available(self):
"""Return True if entity is available."""
return self._ffmpeg.is_running
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
# init config
self._ffmpeg.set_options(
time_duration=config.get(CONF_DURATION),
time_reset=config.get(CONF_RESET),
peak=config.get(CONF_PEAK),
)
# run
self._ffmpeg.open_sensor(
input_source=config.get(CONF_INPUT),
output_dest=config.get(CONF_OUTPUT),
extra_cmd=config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "sound"
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
# init config
self._ffmpeg.set_options(
time_reset=config.get(CONF_RESET),
time_repeat=config.get(CONF_REPEAT_TIME),
repeat=config.get(CONF_REPEAT),
changes=config.get(CONF_CHANGES),
)
# run
self._ffmpeg.open_sensor(
input_source=config.get(CONF_INPUT),
extra_cmd=config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "motion"

View File

@@ -20,7 +20,9 @@ SENSOR_TYPES_CLASS = {
"SmokeV2": "smoke",
"Motion": "motion",
"MotionV2": "motion",
"RemoteMotion": None
"RemoteMotion": None,
"WeatherSensor": None,
"TiltSensor": None,
}
@@ -29,9 +31,11 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
discovery_info,
add_callback_devices)
return homematic.setup_hmdevice_discovery_helper(
HMBinarySensor,
discovery_info,
add_callback_devices
)
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
@@ -55,44 +59,8 @@ class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
return "motion"
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.sensors import HMBinarySensor\
as pyHMBinarySensor
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# check if the Homematic device correct for this HA device
if not isinstance(self._hmdevice, pyHMBinarySensor):
_LOGGER.critical("This %s can't be use as binary", self._name)
return False
# if exists user value?
if self._state and self._state not in self._hmdevice.BINARYNODE:
_LOGGER.critical("This %s have no binary with %s", self._name,
self._state)
return False
# only check and give a warning to the user
if self._state is None and len(self._hmdevice.BINARYNODE) > 1:
_LOGGER.critical("%s have multiple binary params. It use all "
"binary nodes as one. Possible param values: %s",
self._name, str(self._hmdevice.BINARYNODE))
return False
return True
def _init_data_struct(self):
"""Generate a data struct (self._data) from the Homematic metadata."""
super()._init_data_struct()
# object have 1 binary
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
for value in self._hmdevice.BINARYNODE:
self._state = value
# add state to data struct
if self._state:
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,

View File

@@ -9,45 +9,42 @@ import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_SENSOR_CLASS = 'sensor_class'
CONF_PAYLOAD_ON = 'payload_on'
CONF_PAYLOAD_OFF = 'payload_off'
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None):
vol.Any(vol.In(SENSOR_CLASSES), vol.SetTo(None)),
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add MQTT binary sensor."""
"""Setup the MQTT binary sensor."""
add_devices([MqttBinarySensor(
hass,
config[CONF_NAME],
config[CONF_STATE_TOPIC],
config[CONF_SENSOR_CLASS],
config[CONF_QOS],
config[CONF_PAYLOAD_ON],
config[CONF_PAYLOAD_OFF],
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_SENSOR_CLASS),
config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE)
)])

View File

@@ -32,7 +32,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pres.S_MOTION: [set_req.V_TRIPPED],
pres.S_SMOKE: [set_req.V_TRIPPED],
}
if float(gateway.version) >= 1.5:
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],
@@ -66,7 +66,7 @@ class MySensorsBinarySensor(
pres.S_MOTION: 'motion',
pres.S_SMOKE: 'smoke',
}
if float(self.gateway.version) >= 1.5:
if float(self.gateway.protocol_version) >= 1.5:
class_map.update({
pres.S_SPRINKLER: 'sprinkler',
pres.S_WATER_LEAK: 'leak',

View File

@@ -6,12 +6,12 @@ https://home-assistant.io/components/binary_sensor.nest/
"""
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.const import (
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
)
from homeassistant.const import (CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS)
import homeassistant.components.nest as nest
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['nest']
BINARY_TYPES = ['fan',
@@ -25,11 +25,11 @@ BINARY_TYPES = ['fan',
'hvac_emer_heat_state',
'online']
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): nest.DOMAIN,
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(BINARY_TYPES)],
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(BINARY_TYPES)]),
})

View File

@@ -5,45 +5,56 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.octoprint/
"""
import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_NAME, STATE_ON, STATE_OFF
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (
CONF_NAME, STATE_ON, STATE_OFF, CONF_MONITORED_CONDITIONS)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ["octoprint"]
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DEFAULT_NAME = 'OctoPrint'
SENSOR_TYPES = {
# API Endpoint, Group, Key, unit
"Printing": ["printer", "state", "printing", None],
"Printing Error": ["printer", "state", "error", None]
'Printing': ['printer', 'state', 'printing', None],
'Printing Error': ['printer', 'state', 'error', None]
}
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the available OctoPrint binary sensors."""
octoprint = get_component('octoprint')
name = config.get(CONF_NAME, "OctoPrint")
monitored_conditions = config.get("monitored_conditions",
name = config.get(CONF_NAME)
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS,
SENSOR_TYPES.keys())
devices = []
for octo_type in monitored_conditions:
if octo_type in SENSOR_TYPES:
new_sensor = OctoPrintBinarySensor(octoprint.OCTOPRINT,
octo_type,
SENSOR_TYPES[octo_type][2],
name,
SENSOR_TYPES[octo_type][3],
SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1],
"flags")
devices.append(new_sensor)
else:
_LOGGER.error("Unknown OctoPrint sensor type: %s", octo_type)
new_sensor = OctoPrintBinarySensor(octoprint.OCTOPRINT,
octo_type,
SENSOR_TYPES[octo_type][2],
name,
SENSOR_TYPES[octo_type][3],
SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1],
'flags')
devices.append(new_sensor)
add_devices(devices)
@@ -52,14 +63,14 @@ class OctoPrintBinarySensor(BinarySensorDevice):
"""Representation an OctoPrint binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, api, condition, sensor_type, sensor_name,
unit, endpoint, group, tool=None):
def __init__(self, api, condition, sensor_type, sensor_name, unit,
endpoint, group, tool=None):
"""Initialize a new OctoPrint sensor."""
self.sensor_name = sensor_name
if tool is None:
self._name = sensor_name + ' ' + condition
self._name = '{} {}'.format(sensor_name, condition)
else:
self._name = sensor_name + ' ' + condition
self._name = '{} {}'.format(sensor_name, condition)
self.sensor_type = sensor_type
self.api = api
self._state = False

View File

@@ -6,30 +6,44 @@ https://home-assistant.io/components/binary_sensor.rest/
"""
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'REST Binary Sensor'
DEFAULT_METHOD = 'GET'
DEFAULT_NAME = 'REST Binary Sensor'
DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the REST binary sensor."""
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
name = config.get(CONF_NAME)
resource = config.get(CONF_RESOURCE)
method = config.get(CONF_METHOD)
payload = config.get(CONF_PAYLOAD)
verify_ssl = config.get(CONF_VERIFY_SSL)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
@@ -39,11 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
add_devices([RestBinarySensor(
hass,
rest,
config.get('name', DEFAULT_NAME),
sensor_class,
config.get(CONF_VALUE_TEMPLATE))])
hass, rest, name, sensor_class, value_template)])
# pylint: disable=too-many-arguments

View File

@@ -0,0 +1,9 @@
# Describes the format for available binary_sensor services
ffmpeg_restart:
description: Send a restart command to a ffmpeg based sensor (party mode).
fields:
entity_id:
description: Name(s) of entites that will restart. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'

View File

@@ -6,54 +6,43 @@ https://home-assistant.io/components/binary_sensor.template/
"""
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice,
ENTITY_ID_FORMAT,
SENSOR_CLASSES)
from homeassistant.const import (ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE,
ATTR_ENTITY_ID, MATCH_ALL)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers import template
from homeassistant.helpers.event import track_state_change
from homeassistant.util import slugify
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, MATCH_ALL, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
CONF_SENSORS = 'sensors'
_LOGGER = logging.getLogger(__name__)
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup template binary sensors."""
sensors = []
if config.get(CONF_SENSORS) is None:
_LOGGER.error('Missing configuration data for binary_sensor platform')
return False
for device, device_config in config[CONF_SENSORS].items():
if device != slugify(device):
_LOGGER.error('Found invalid key for binary_sensor.template: %s. '
'Use %s instead', device, slugify(device))
continue
if not isinstance(device_config, dict):
_LOGGER.error('Missing configuration data for binary_sensor %s',
device)
continue
value_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID]
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config.get('sensor_class')
value_template = device_config.get(CONF_VALUE_TEMPLATE)
if sensor_class not in SENSOR_CLASSES:
_LOGGER.error('Sensor class is not valid')
continue
if value_template is None:
_LOGGER.error(
'Missing %s for sensor %s', CONF_VALUE_TEMPLATE, device)
continue
entity_ids = device_config.get(ATTR_ENTITY_ID, MATCH_ALL)
sensor_class = device_config.get(CONF_SENSOR_CLASS)
sensors.append(
BinarySensorTemplate(
@@ -93,8 +82,7 @@ class BinarySensorTemplate(BinarySensorDevice):
"""Called when the target device changes state."""
self.update_ha_state(True)
track_state_change(hass, entity_ids,
template_bsensor_state_listener)
track_state_change(hass, entity_ids, template_bsensor_state_listener)
@property
def name(self):
@@ -119,8 +107,8 @@ class BinarySensorTemplate(BinarySensorDevice):
def update(self):
"""Get the latest data and update the state."""
try:
self._state = template.render(self.hass,
self._template).lower() == 'true'
self._state = template.render(
self.hass, self._template).lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):

View File

@@ -0,0 +1,145 @@
"""
A sensor that monitors trands in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
ATTR_ENTITY_ID,
CONF_SENSOR_CLASS,
STATE_UNKNOWN,)
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
_LOGGER = logging.getLogger(__name__)
CONF_SENSORS = 'sensors'
CONF_ATTRIBUTE = 'attribute'
CONF_INVERT = 'invert'
SENSOR_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_ATTRIBUTE): cv.string,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_INVERT, default=False): cv.boolean,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the template sensors."""
sensors = []
for device, device_config in config[CONF_SENSORS].items():
entity_id = device_config[ATTR_ENTITY_ID]
attribute = device_config.get(CONF_ATTRIBUTE)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config[CONF_SENSOR_CLASS]
invert = device_config[CONF_INVERT]
sensors.append(
SensorTrend(
hass,
device,
friendly_name,
entity_id,
attribute,
sensor_class,
invert)
)
if not sensors:
_LOGGER.error("No sensors added")
return False
add_devices(sensors)
return True
class SensorTrend(BinarySensorDevice):
"""Representation of a Template Sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, device_id, friendly_name,
target_entity, attribute, sensor_class, invert):
"""Initialize the sensor."""
self._hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass)
self._name = friendly_name
self._target_entity = target_entity
self._attribute = attribute
self._sensor_class = sensor_class
self._invert = invert
self._state = None
self.from_state = None
self.to_state = None
self.update()
def template_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.from_state = old_state
self.to_state = new_state
self.update_ha_state(True)
track_state_change(hass, target_entity,
template_sensor_state_listener)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
@property
def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
@property
def should_poll(self):
"""No polling needed."""
return False
def update(self):
"""Get the latest data and update the states."""
if self.from_state is None or self.to_state is None:
return
if (self.from_state.state == STATE_UNKNOWN or
self.to_state.state == STATE_UNKNOWN):
return
try:
if self._attribute:
from_value = float(
self.from_state.attributes.get(self._attribute))
to_value = float(
self.to_state.attributes.get(self._attribute))
else:
from_value = float(self.from_state.state)
to_value = float(self.to_state.state)
self._state = to_value > from_value
if self._invert:
self._state = not self._state
except (ValueError, TypeError) as ex:
self._state = None
_LOGGER.error(ex)

View File

@@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.7.11', 'pubnub==3.8.2']
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {

View File

@@ -4,18 +4,27 @@ Contains functionality to use a ZigBee device as a binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.zigbee/
"""
import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.zigbee import (
ZigBeeDigitalIn, ZigBeeDigitalInConfig)
ZigBeeDigitalIn, ZigBeeDigitalInConfig, PLATFORM_SCHEMA)
DEPENDENCIES = ["zigbee"]
CONF_ON_STATE = 'on_state'
DEFAULT_ON_STATE = 'high'
DEPENDENCIES = ['zigbee']
STATES = ['high', 'low']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ON_STATE): vol.In(STATES),
})
def setup_platform(hass, config, add_entities, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ZigBee binary sensor platform."""
add_entities([
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
])
add_devices([ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))])
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):

View File

@@ -8,30 +8,34 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config, discovery
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
DOMAIN = "bloomsky"
BLOOMSKY = None
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
BLOOMSKY = None
BLOOMSKY_TYPE = ['camera', 'binary_sensor', 'sensor']
DOMAIN = 'bloomsky'
# The BloomSky only updates every 5-8 minutes as per the API spec so there's
# no point in polling the API more frequently
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
"""Setup BloomSky component."""
if not validate_config(
config,
{DOMAIN: [CONF_API_KEY]},
_LOGGER):
return False
api_key = config[DOMAIN][CONF_API_KEY]
global BLOOMSKY
@@ -40,7 +44,7 @@ def setup(hass, config):
except RuntimeError:
return False
for component in 'camera', 'binary_sensor', 'sensor':
for component in BLOOMSKY_TYPE:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
@@ -50,19 +54,19 @@ class BloomSky(object):
"""Handle all communication with the BloomSky API."""
# API documentation at http://weatherlution.com/bloomsky-api/
API_URL = "https://api.bloomsky.com/api/skydata"
API_URL = 'https://api.bloomsky.com/api/skydata'
def __init__(self, api_key):
"""Initialize the BookSky."""
self._api_key = api_key
self.devices = {}
_LOGGER.debug("Initial bloomsky device load...")
_LOGGER.debug("Initial BloomSky device load...")
self.refresh_devices()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self):
"""Use the API to retreive a list of devices."""
_LOGGER.debug("Fetching bloomsky update")
_LOGGER.debug("Fetching BloomSky update")
response = requests.get(self.API_URL,
headers={"Authorization": self._api_key},
timeout=10)
@@ -73,5 +77,5 @@ class BloomSky(object):
return
# Create dictionary keyed off of the device unique id
self.devices.update({
device["DeviceID"]: device for device in response.json()
device['DeviceID']: device for device in response.json()
})

View File

@@ -11,15 +11,15 @@ import requests
from homeassistant.components.camera import Camera
from homeassistant.loader import get_component
DEPENDENCIES = ["bloomsky"]
DEPENDENCIES = ['bloomsky']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to BloomSky cameras."""
bloomsky = get_component('bloomsky')
for device in bloomsky.BLOOMSKY.devices.values():
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
add_devices([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
class BloomSkyCamera(Camera):
@@ -28,8 +28,8 @@ class BloomSkyCamera(Camera):
def __init__(self, bs, device):
"""Setup for access to the BloomSky camera images."""
super(BloomSkyCamera, self).__init__()
self._name = device["DeviceName"]
self._id = device["DeviceID"]
self._name = device['DeviceName']
self._id = device['DeviceID']
self._bloomsky = bs
self._url = ""
self._last_url = ""
@@ -42,7 +42,7 @@ class BloomSkyCamera(Camera):
def camera_image(self):
"""Update the camera's image if it has changed."""
try:
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
self._url = self._bloomsky.devices[self._id]['Data']['ImageURL']
self._bloomsky.refresh_devices()
# If the URL hasn't changed then the image hasn't changed.
if self._url != self._last_url:

View File

@@ -0,0 +1,73 @@
"""
Support for Cameras with FFmpeg as decoder.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.ffmpeg/
"""
import logging
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
REQUIREMENTS = ['ha-ffmpeg==0.10']
_LOGGER = logging.getLogger(__name__)
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
DEFAULT_BINARY = 'ffmpeg'
DEFAULT_NAME = 'FFmpeg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
add_devices([FFmpegCamera(config)])
class FFmpegCamera(Camera):
"""An implementation of an FFmpeg camera."""
def __init__(self, config):
"""Initialize a FFmpeg camera."""
super().__init__()
self._name = config.get(CONF_NAME)
self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN)
def camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageSingle, IMAGE_JPEG
ffmpeg = ImageSingle(self._ffmpeg_bin)
return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg_bin)
stream.open_camera(self._input, extra_cmd=self._extra_arguments)
return response(
stream,
mimetype='multipart/x-mixed-replace;boundary=ffserver',
direct_passthrough=True
)
@property
def name(self):
"""Return the name of this camera."""
return self._name

View File

@@ -7,21 +7,33 @@ https://home-assistant.io/components/camera.foscam/
import logging
import requests
import voluptuous as vol
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.helpers import validate_config
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_IP = 'ip'
DEFAULT_NAME = 'Foscam Camera'
DEFAULT_PORT = 88
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IP): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a Foscam IP Camera."""
if not validate_config({DOMAIN: config},
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
return None
add_devices_callback([FoscamCamera(config)])
add_devices([FoscamCamera(config)])
# pylint: disable=too-many-instance-attributes
@@ -32,16 +44,16 @@ class FoscamCamera(Camera):
"""Initialize a Foscam camera."""
super(FoscamCamera, self).__init__()
ip_address = device_info.get('ip')
port = device_info.get('port', 88)
ip_address = device_info.get(CONF_IP)
port = device_info.get(CONF_PORT)
self._base_url = 'http://' + ip_address + ':' + str(port) + '/'
self._username = device_info.get('username')
self._password = device_info.get('password')
self._base_url = 'http://{}:{}/'.format(ip_address, port)
self._username = device_info.get(CONF_USERNAME)
self._password = device_info.get(CONF_PASSWORD)
self._snap_picture_url = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=' \
+ self._username + '&pwd=' + self._password
self._name = device_info.get('name', 'Foscam Camera')
self._name = device_info.get(CONF_NAME)
_LOGGER.info('Using the following URL for %s: %s',
self._name, self._snap_picture_url)

View File

@@ -7,22 +7,38 @@ https://home-assistant.io/components/camera.generic/
import logging
import requests
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
import voluptuous as vol
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.helpers import validate_config
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers import config_validation as cv, template
_LOGGER = logging.getLogger(__name__)
CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change'
CONF_STILL_IMAGE_URL = 'still_image_url'
DEFAULT_NAME = 'Generic Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STILL_IMAGE_URL): vol.Any(cv.url, cv.template),
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a generic IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']},
_LOGGER):
return None
add_devices_callback([GenericCamera(config)])
add_devices([GenericCamera(config)])
# pylint: disable=too-many-instance-attributes
@@ -32,30 +48,47 @@ class GenericCamera(Camera):
def __init__(self, device_info):
"""Initialize a generic camera."""
super().__init__()
self._name = device_info.get('name', 'Generic Camera')
self._username = device_info.get('username')
self._password = device_info.get('password')
self._still_image_url = device_info['still_image_url']
self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME)
password = device_info.get(CONF_PASSWORD)
if username and password:
if device_info[CONF_AUTHENTICATION] == HTTP_DIGEST_AUTHENTICATION:
self._auth = HTTPDigestAuth(username, password)
else:
self._auth = HTTPBasicAuth(username, password)
else:
self._auth = None
self._last_url = None
self._last_image = None
def camera_image(self):
"""Return a still image response from the camera."""
if self._username and self._password:
try:
response = requests.get(
self._still_image_url,
auth=HTTPBasicAuth(self._username, self._password),
timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
else:
try:
response = requests.get(self._still_image_url, timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
try:
url = template.render(self.hass, self._still_image_url)
except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err)
return self._last_image
return response.content
if url == self._last_url and self._limit_refetch:
return self._last_image
kwargs = {'timeout': 10, 'auth': self._auth}
try:
response = requests.get(url, **kwargs)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
self._last_url = url
self._last_image = response.content
return self._last_image
@property
def name(self):

View File

@@ -1,50 +1,55 @@
"""Camera that loads a picture from a local file."""
"""
Camera that loads a picture from a local file.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.local_file/
"""
import logging
import os
from homeassistant.components.camera import Camera
import voluptuous as vol
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_FILE_PATH = 'file_path'
DEFAULT_NAME = 'Local File'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FILE_PATH): cv.isfile,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Camera."""
# check for missing required configuration variable
if config.get("file_path") is None:
_LOGGER.error("Missing required variable: file_path")
return False
setup_config = (
{
"name": config.get("name", "Local File"),
"file_path": config.get("file_path")
}
)
file_path = config[CONF_FILE_PATH]
# check filepath given is readable
if not os.access(setup_config["file_path"], os.R_OK):
if not os.access(file_path, os.R_OK):
_LOGGER.error("file path is not readable")
return False
add_devices([
LocalFile(setup_config)
])
add_devices([LocalFile(config[CONF_NAME], file_path)])
class LocalFile(Camera):
"""Local camera."""
def __init__(self, device_info):
def __init__(self, name, file_path):
"""Initialize Local File Camera component."""
super().__init__()
self._name = device_info["name"]
self._config = device_info
self._name = name
self._file_path = file_path
def camera_image(self):
"""Return image response."""
with open(self._config["file_path"], 'rb') as file:
with open(self._file_path, 'rb') as file:
return file.read()
@property

View File

@@ -8,24 +8,48 @@ import logging
from contextlib import closing
import requests
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
import voluptuous as vol
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.helpers import validate_config
CONTENT_TYPE_HEADER = 'Content-Type'
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_MJPEG_URL = 'mjpeg_url'
CONTENT_TYPE_HEADER = 'Content-Type'
DEFAULT_NAME = 'Mjpeg Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MJPEG_URL): cv.url,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a MJPEG IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']},
_LOGGER):
return None
add_devices([MjpegCamera(config)])
add_devices_callback([MjpegCamera(config)])
def extract_image_from_mjpeg(stream):
"""Take in a MJPEG stream object, return the jpg from it."""
data = b''
for chunk in stream:
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
jpg = data[jpg_start:jpg_end + 2]
return jpg
# pylint: disable=too-many-instance-attributes
@@ -35,36 +59,29 @@ class MjpegCamera(Camera):
def __init__(self, device_info):
"""Initialize a MJPEG camera."""
super().__init__()
self._name = device_info.get('name', 'Mjpeg Camera')
self._username = device_info.get('username')
self._password = device_info.get('password')
self._mjpeg_url = device_info['mjpeg_url']
self._name = device_info.get(CONF_NAME)
self._authentication = device_info.get(CONF_AUTHENTICATION)
self._username = device_info.get(CONF_USERNAME)
self._password = device_info.get(CONF_PASSWORD)
self._mjpeg_url = device_info[CONF_MJPEG_URL]
def camera_stream(self):
"""Return a MJPEG stream image response directly from the camera."""
if self._username and self._password:
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
auth = HTTPDigestAuth(self._username, self._password)
else:
auth = HTTPBasicAuth(self._username, self._password)
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
auth=auth,
stream=True, timeout=10)
else:
return requests.get(self._mjpeg_url, stream=True, timeout=10)
def camera_image(self):
"""Return a still image response from the camera."""
def process_response(response):
"""Take in a response object, return the jpg from it."""
data = b''
for chunk in response.iter_content(1024):
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
jpg = data[jpg_start:jpg_end + 2]
return jpg
with closing(self.camera_stream()) as response:
return process_response(response)
return extract_image_from_mjpeg(response.iter_content(1024))
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from the camera."""

View File

@@ -6,34 +6,43 @@ https://home-assistant.io/components/camera.netatmo/
"""
import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.util import Throttle
from homeassistant.components.camera import Camera
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ["netatmo"]
DEPENDENCIES = ['netatmo']
_LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
ATTR_CAMERAS = 'cameras'
CONF_CAMERAS = 'cameras'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo Welcome cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
home = config.get(CONF_HOME)
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if ATTR_CAMERAS in config:
if camera_name not in config[ATTR_CAMERAS]:
if CONF_CAMERAS in config:
if camera_name not in config[CONF_CAMERAS]:
continue
add_devices_callback([WelcomeCamera(data, camera_name, home)])
add_devices([WelcomeCamera(data, camera_name, home)])
class WelcomeCamera(Camera):

View File

@@ -9,41 +9,77 @@ import subprocess
import logging
import shutil
from homeassistant.components.camera import Camera
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_FILE_PATH)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_HORIZONTAL_FLIP = 'horizontal_flip'
CONF_IMAGE_HEIGHT = 'image_height'
CONF_IMAGE_QUALITY = 'image_quality'
CONF_IMAGE_ROTATION = 'image_rotation'
CONF_IMAGE_WIDTH = 'image_width'
CONF_TIMELAPSE = 'timelapse'
CONF_VERTICAL_FLIP = 'vertical_flip'
DEFAULT_HORIZONTAL_FLIP = 0
DEFAULT_IMAGE_HEIGHT = 480
DEFAULT_IMAGE_QUALITIY = 7
DEFAULT_IMAGE_ROTATION = 0
DEFAULT_IMAGE_WIDTH = 640
DEFAULT_NAME = 'Raspberry Pi Camera'
DEFAULT_TIMELAPSE = 1000
DEFAULT_VERTICAL_FLIP = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FILE_PATH): cv.isfile,
vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP):
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_HORIZONTAL_FLIP):
vol.Coerce(int),
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITIY):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=359)),
vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH):
vol.Coerce(int),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TIMELAPSE, default=1000): vol.Coerce(int),
vol.Optional(CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP):
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Raspberry Camera."""
if shutil.which("raspistill") is None:
_LOGGER.error("Error: raspistill not found")
_LOGGER.error("'raspistill' was not found")
return False
setup_config = (
{
"name": config.get("name", "Raspberry Pi Camera"),
"image_width": int(config.get("image_width", "640")),
"image_height": int(config.get("image_height", "480")),
"image_quality": int(config.get("image_quality", "7")),
"image_rotation": int(config.get("image_rotation", "0")),
"timelapse": int(config.get("timelapse", "2000")),
"horizontal_flip": int(config.get("horizontal_flip", "0")),
"vertical_flip": int(config.get("vertical_flip", "0")),
"file_path": config.get("file_path",
os.path.join(os.path.dirname(__file__),
'image.jpg'))
CONF_NAME: config.get(CONF_NAME),
CONF_IMAGE_WIDTH: config.get(CONF_IMAGE_WIDTH),
CONF_IMAGE_HEIGHT: config.get(CONF_IMAGE_HEIGHT),
CONF_IMAGE_QUALITY: config.get(CONF_IMAGE_QUALITY),
CONF_IMAGE_ROTATION: config.get(CONF_IMAGE_ROTATION),
CONF_TIMELAPSE: config.get(CONF_TIMELAPSE),
CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP),
CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP),
CONF_FILE_PATH: config.get(CONF_FILE_PATH,
os.path.join(os.path.dirname(__file__),
'image.jpg'))
}
)
# check filepath given is writable
if not os.access(setup_config["file_path"], os.W_OK):
_LOGGER.error("Error: file path is not writable")
if not os.access(setup_config[CONF_FILE_PATH], os.W_OK):
_LOGGER.error("File path is not writable")
return False
add_devices([
RaspberryCamera(setup_config)
])
add_devices([RaspberryCamera(setup_config)])
class RaspberryCamera(Camera):
@@ -53,26 +89,26 @@ class RaspberryCamera(Camera):
"""Initialize Raspberry Pi camera component."""
super().__init__()
self._name = device_info["name"]
self._name = device_info[CONF_NAME]
self._config = device_info
# kill if there's raspistill instance
# Kill if there's raspistill instance
subprocess.Popen(['killall', 'raspistill'],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT)
cmd_args = [
'raspistill', '--nopreview', '-o', str(device_info["file_path"]),
'-t', '0', '-w', str(device_info["image_width"]),
'-h', str(device_info["image_height"]),
'-tl', str(device_info["timelapse"]),
'-q', str(device_info["image_quality"]),
'-rot', str(device_info["image_rotation"])
'raspistill', '--nopreview', '-o', device_info[CONF_FILE_PATH],
'-t', '0', '-w', str(device_info[CONF_IMAGE_WIDTH]),
'-h', str(device_info[CONF_IMAGE_HEIGHT]),
'-tl', str(device_info[CONF_TIMELAPSE]),
'-q', str(device_info[CONF_IMAGE_QUALITY]),
'-rot', str(device_info[CONF_IMAGE_ROTATION])
]
if device_info["horizontal_flip"]:
if device_info[CONF_HORIZONTAL_FLIP]:
cmd_args.append("-hf")
if device_info["vertical_flip"]:
if device_info[CONF_VERTICAL_FLIP]:
cmd_args.append("-vf")
subprocess.Popen(cmd_args,
@@ -81,7 +117,7 @@ class RaspberryCamera(Camera):
def camera_image(self):
"""Return raspstill image response."""
with open(self._config["file_path"], 'rb') as file:
with open(self._config[CONF_FILE_PATH], 'rb') as file:
return file.read()
@property

View File

@@ -0,0 +1,548 @@
"""
Provides functionality to interact with climate devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/climate/
"""
import logging
import os
from numbers import Number
import voluptuous as vol
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.config import load_yaml_config_file
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELSIUS)
DOMAIN = "climate"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SCAN_INTERVAL = 60
SERVICE_SET_AWAY_MODE = "set_away_mode"
SERVICE_SET_AUX_HEAT = "set_aux_heat"
SERVICE_SET_TEMPERATURE = "set_temperature"
SERVICE_SET_FAN_MODE = "set_fan_mode"
SERVICE_SET_OPERATION_MODE = "set_operation_mode"
SERVICE_SET_SWING_MODE = "set_swing_mode"
SERVICE_SET_HUMIDITY = "set_humidity"
STATE_HEAT = "heat"
STATE_COOL = "cool"
STATE_IDLE = "idle"
STATE_AUTO = "auto"
STATE_DRY = "dry"
STATE_FAN_ONLY = "fan_only"
ATTR_CURRENT_TEMPERATURE = "current_temperature"
ATTR_MAX_TEMP = "max_temp"
ATTR_MIN_TEMP = "min_temp"
ATTR_TARGET_TEMP_HIGH = "target_temp_high"
ATTR_TARGET_TEMP_LOW = "target_temp_low"
ATTR_AWAY_MODE = "away_mode"
ATTR_AUX_HEAT = "aux_heat"
ATTR_FAN_MODE = "fan_mode"
ATTR_FAN_LIST = "fan_list"
ATTR_CURRENT_HUMIDITY = "current_humidity"
ATTR_HUMIDITY = "humidity"
ATTR_MAX_HUMIDITY = "max_humidity"
ATTR_MIN_HUMIDITY = "min_humidity"
ATTR_OPERATION_MODE = "operation_mode"
ATTR_OPERATION_LIST = "operation_list"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_LIST = "swing_list"
_LOGGER = logging.getLogger(__name__)
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
})
SET_OPERATION_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_OPERATION_MODE): cv.string,
})
SET_HUMIDITY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_HUMIDITY): vol.Coerce(float),
})
SET_SWING_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_SWING_MODE): cv.string,
})
def set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified climate devices away mode on."""
data = {
ATTR_AWAY_MODE: away_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
def set_aux_heat(hass, aux_heat, entity_id=None):
"""Turn all or specified climate devices auxillary heater on."""
data = {
ATTR_AUX_HEAT: aux_heat
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
(ATTR_TEMPERATURE, temperature),
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)
def set_humidity(hass, humidity, entity_id=None):
"""Set new target humidity."""
data = {ATTR_HUMIDITY: humidity}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
def set_fan_mode(hass, fan, entity_id=None):
"""Set all or specified climate devices fan mode on."""
data = {ATTR_FAN_MODE: fan}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
def set_operation_mode(hass, operation_mode, entity_id=None):
"""Set new target operation mode."""
data = {ATTR_OPERATION_MODE: operation_mode}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
def set_swing_mode(hass, swing_mode, entity_id=None):
"""Set new target swing mode."""
data = {ATTR_SWING_MODE: swing_mode}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
# pylint: disable=too-many-branches
def setup(hass, config):
"""Setup climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def away_mode_set_service(service):
"""Set away mode on target climate devices."""
target_climate = component.extract_from_service(service)
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
return
for climate in target_climate:
if away_mode:
climate.turn_away_mode_on()
else:
climate.turn_away_mode_off()
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
descriptions.get(SERVICE_SET_AWAY_MODE),
schema=SET_AWAY_MODE_SCHEMA)
def aux_heat_set_service(service):
"""Set auxillary heater on target climate devices."""
target_climate = component.extract_from_service(service)
aux_heat = service.data.get(ATTR_AUX_HEAT)
if aux_heat is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT)
return
for climate in target_climate:
if aux_heat:
climate.turn_aux_heat_on()
else:
climate.turn_aux_heat_off()
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_AUX_HEAT, aux_heat_set_service,
descriptions.get(SERVICE_SET_AUX_HEAT),
schema=SET_AUX_HEAT_SCHEMA)
def temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
kwargs = service.data
for climate in target_climate:
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
descriptions.get(SERVICE_SET_TEMPERATURE),
schema=SET_TEMPERATURE_SCHEMA)
def humidity_set_service(service):
"""Set humidity on the target climate devices."""
target_climate = component.extract_from_service(service)
humidity = service.data.get(ATTR_HUMIDITY)
if humidity is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_HUMIDITY, ATTR_HUMIDITY)
return
for climate in target_climate:
climate.set_humidity(humidity)
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_HUMIDITY, humidity_set_service,
descriptions.get(SERVICE_SET_HUMIDITY),
schema=SET_HUMIDITY_SCHEMA)
def fan_mode_set_service(service):
"""Set fan mode on target climate devices."""
target_climate = component.extract_from_service(service)
fan = service.data.get(ATTR_FAN_MODE)
if fan is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_FAN_MODE, ATTR_FAN_MODE)
return
for climate in target_climate:
climate.set_fan_mode(fan)
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
descriptions.get(SERVICE_SET_FAN_MODE),
schema=SET_FAN_MODE_SCHEMA)
def operation_set_service(service):
"""Set operating mode on the target climate devices."""
target_climate = component.extract_from_service(service)
operation_mode = service.data.get(ATTR_OPERATION_MODE)
if operation_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE)
return
for climate in target_climate:
climate.set_operation_mode(operation_mode)
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_OPERATION_MODE, operation_set_service,
descriptions.get(SERVICE_SET_OPERATION_MODE),
schema=SET_OPERATION_MODE_SCHEMA)
def swing_set_service(service):
"""Set swing mode on the target climate devices."""
target_climate = component.extract_from_service(service)
swing_mode = service.data.get(ATTR_SWING_MODE)
if swing_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_SWING_MODE, ATTR_SWING_MODE)
return
for climate in target_climate:
climate.set_swing_mode(swing_mode)
if climate.should_poll:
climate.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_SWING_MODE, swing_set_service,
descriptions.get(SERVICE_SET_SWING_MODE),
schema=SET_SWING_MODE_SCHEMA)
return True
class ClimateDevice(Entity):
"""Representation of a climate device."""
# pylint: disable=too-many-public-methods,no-self-use
@property
def state(self):
"""Return the current state."""
return self.current_operation or STATE_UNKNOWN
@property
def state_attributes(self):
"""Return the optional state attributes."""
data = {
ATTR_CURRENT_TEMPERATURE:
self._convert_for_display(self.current_temperature),
ATTR_MIN_TEMP: self._convert_for_display(self.min_temp),
ATTR_MAX_TEMP: self._convert_for_display(self.max_temp),
ATTR_TEMPERATURE:
self._convert_for_display(self.target_temperature),
}
target_temp_high = self.target_temperature_high
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
humidity = self.target_humidity
if humidity is not None:
data[ATTR_HUMIDITY] = humidity
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
fan_mode = self.current_fan_mode
if fan_mode is not None:
data[ATTR_FAN_MODE] = fan_mode
data[ATTR_FAN_LIST] = self.fan_list
operation_mode = self.current_operation
if operation_mode is not None:
data[ATTR_OPERATION_MODE] = operation_mode
data[ATTR_OPERATION_LIST] = self.operation_list
swing_mode = self.current_swing_mode
if swing_mode is not None:
data[ATTR_SWING_MODE] = swing_mode
data[ATTR_SWING_LIST] = self.swing_list
is_away = self.is_away_mode_on
if is_away is not None:
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
is_aux_heat = self.is_aux_heat_on
if is_aux_heat is not None:
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
return data
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
raise NotImplementedError
@property
def current_humidity(self):
"""Return the current humidity."""
return None
@property
def target_humidity(self):
"""Return the humidity we try to reach."""
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return None
@property
def operation_list(self):
"""List of available operation modes."""
return None
@property
def current_temperature(self):
"""Return the current temperature."""
return None
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return None
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
return None
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
return None
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return None
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
return None
@property
def current_fan_mode(self):
"""Return the fan setting."""
return None
@property
def fan_list(self):
"""List of available fan modes."""
return None
@property
def current_swing_mode(self):
"""Return the fan setting."""
return None
@property
def swing_list(self):
"""List of available swing modes."""
return None
def set_temperature(self, **kwargs):
"""Set new target temperature."""
raise NotImplementedError()
def set_humidity(self, humidity):
"""Set new target humidity."""
raise NotImplementedError()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
raise NotImplementedError()
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
raise NotImplementedError()
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
raise NotImplementedError()
def turn_away_mode_on(self):
"""Turn away mode on."""
raise NotImplementedError()
def turn_away_mode_off(self):
"""Turn away mode off."""
raise NotImplementedError()
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
raise NotImplementedError()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
raise NotImplementedError()
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert_temperature(7, TEMP_CELSIUS, self.unit_of_measurement)
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert_temperature(35, TEMP_CELSIUS, self.unit_of_measurement)
@property
def min_humidity(self):
"""Return the minimum humidity."""
return 30
@property
def max_humidity(self):
"""Return the maximum humidity."""
return 99
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if temp is None or not isinstance(temp, Number):
return temp
value = convert_temperature(temp, self.unit_of_measurement,
self.hass.config.units.temperature_unit)
if self.hass.config.units.temperature_unit is TEMP_CELSIUS:
decimal_count = 1
else:
# Users of fahrenheit generally expect integer units.
decimal_count = 0
return round(value, decimal_count)

View File

@@ -0,0 +1,184 @@
"""
Demo platform that offers a fake climate device.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.climate import (
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo climate devices."""
add_devices([
DemoClimate("HeatPump", 68, TEMP_FAHRENHEIT, None, 77, "Auto Low",
None, None, "Auto", "heat", None, None, None),
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
67, 54, "Off", "cool", False, None, None),
DemoClimate("Ecobee", 23, TEMP_CELSIUS, None, 23, "Auto Low",
None, None, "Auto", "auto", None, 24, 21)
])
# pylint: disable=too-many-arguments, too-many-public-methods
class DemoClimate(ClimateDevice):
"""Representation of a demo climate device."""
# pylint: disable=too-many-instance-attributes
def __init__(self, name, target_temperature, unit_of_measurement,
away, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
current_operation, aux, target_temp_high, target_temp_low):
"""Initialize the climate device."""
self._name = name
self._target_temperature = target_temperature
self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement
self._away = away
self._current_temperature = current_temperature
self._current_humidity = current_humidity
self._current_fan_mode = current_fan_mode
self._current_operation = current_operation
self._aux = aux
self._current_swing_mode = current_swing_mode
self._fan_list = ["On Low", "On High", "Auto Low", "Auto High", "Off"]
self._operation_list = ["heat", "cool", "auto", "off"]
self._swing_list = ["Auto", "1", "2", "3", "Off"]
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
@property
def should_poll(self):
"""Polling not needed for a demo climate device."""
return False
@property
def name(self):
"""Return the name of the climate device."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
return self._target_temperature_high
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
return self._target_temperature_low
@property
def current_humidity(self):
"""Return the current humidity."""
return self._current_humidity
@property
def target_humidity(self):
"""Return the humidity we try to reach."""
return self._target_humidity
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
@property
def is_aux_heat_on(self):
"""Return true if away mode is on."""
return self._aux
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
"""List of available fan modes."""
return self._fan_list
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
if kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None and \
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.update_ha_state()
def set_humidity(self, humidity):
"""Set new target temperature."""
self._target_humidity = humidity
self.update_ha_state()
def set_swing_mode(self, swing_mode):
"""Set new target temperature."""
self._current_swing_mode = swing_mode
self.update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
self._current_fan_mode = fan
self.update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
self._current_operation = operation_mode
self.update_ha_state()
@property
def current_swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.update_ha_state()
def turn_aux_heat_on(self):
"""Turn away auxillary heater on."""
self._aux = True
self.update_ha_state()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self._aux = False
self.update_ha_state()

View File

@@ -0,0 +1,270 @@
"""
Platform for Ecobee Thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.ecobee/
"""
import logging
from os import path
import voluptuous as vol
from homeassistant.components import ecobee
from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
DEPENDENCIES = ['ecobee']
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Ecobee Thermostat Platform."""
if discovery_info is None:
return
data = ecobee.NETWORK
hold_temp = discovery_info['hold_temp']
_LOGGER.info(
"Loading ecobee thermostat component with hold_temp set to %s",
hold_temp)
devices = [Thermostat(data, index, hold_temp)
for index in range(len(data.ecobee.thermostats))]
add_devices(devices)
def fan_min_on_time_set_service(service):
"""Set the minimum fan on time on the target thermostats."""
entity_id = service.data.get('entity_id')
if entity_id:
target_thermostats = [device for device in devices
if device.entity_id == entity_id]
else:
target_thermostats = devices
fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME]
for thermostat in target_thermostats:
thermostat.set_fan_min_on_time(str(fan_min_on_time))
thermostat.update_ha_state(True)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
hass.services.register(
DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, fan_min_on_time_set_service,
descriptions.get(SERVICE_SET_FAN_MIN_ON_TIME),
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
# pylint: disable=too-many-public-methods, abstract-method
class Thermostat(ClimateDevice):
"""A thermostat class for Ecobee."""
def __init__(self, data, thermostat_index, hold_temp):
"""Initialize the thermostat."""
self.data = data
self.thermostat_index = thermostat_index
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
self._name = self.thermostat['name']
self.hold_temp = hold_temp
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
def update(self):
"""Get the latest state from the thermostat."""
self.data.update()
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
@property
def name(self):
"""Return the name of the Ecobee Thermostat."""
return self.thermostat['name']
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
"""Return the current temperature."""
return self.thermostat['runtime']['actualTemperature'] / 10
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if (self.operation_mode == 'heat' or
self.operation_mode == 'auxHeatOnly'):
return self.target_temperature_low
elif self.operation_mode == 'cool':
return self.target_temperature_high
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
return int(self.thermostat['runtime']['desiredHeat'] / 10)
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
return int(self.thermostat['runtime']['desiredCool'] / 10)
@property
def desired_fan_mode(self):
"""Return the desired fan mode of operation."""
return self.thermostat['runtime']['desiredFanMode']
@property
def fan(self):
"""Return the current fan state."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
else:
return STATE_OFF
@property
def current_operation(self):
"""Return current operation."""
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
else:
return self.operation_mode
@property
def operation_list(self):
"""Return the operation modes list."""
return self._operation_list
@property
def operation_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self.thermostat['settings']['hvacMode']
@property
def mode(self):
"""Return current mode ie. home, away, sleep."""
return self.thermostat['program']['currentClimateRef']
@property
def fan_min_on_time(self):
"""Return current fan minimum on time."""
return self.thermostat['settings']['fanMinOnTime']
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
# Move these to Thermostat Device and make them global
status = self.thermostat['equipmentStatus']
operation = None
if status == '':
operation = STATE_IDLE
elif 'Cool' in status:
operation = STATE_COOL
elif 'auxHeat' in status:
operation = STATE_HEAT
elif 'heatPump' in status:
operation = STATE_HEAT
else:
operation = status
return {
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
"fan": self.fan,
"mode": self.mode,
"operation": operation,
"fan_min_on_time": self.fan_min_on_time
}
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
mode = self.mode
events = self.thermostat['events']
for event in events:
if event['running']:
mode = event['holdClimateRef']
break
return 'away' in mode
def turn_away_mode_on(self):
"""Turn away on."""
if self.hold_temp:
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", "indefinite")
else:
self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
def turn_away_mode_off(self):
"""Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
low_temp = int(temperature)
high_temp = int(temperature)
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW))
low_temp = int(kwargs.get(ATTR_TARGET_TEMP_HIGH))
if self.hold_temp:
self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp,
high_temp, "indefinite")
_LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, "
"high=%s, is=%s", low_temp, isinstance(
low_temp, (int, float)), high_temp,
isinstance(high_temp, (int, float)))
else:
self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp,
high_temp)
_LOGGER.debug("Setting ecobee temp to: low=%s, is=%s, "
"high=%s, is=%s", low_temp, isinstance(
low_temp, (int, float)), high_temp,
isinstance(high_temp, (int, float)))
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
def set_fan_min_on_time(self, fan_min_on_time):
"""Set the minimum fan on time."""
self.data.ecobee.set_fan_min_on_time(self.thermostat_index,
fan_min_on_time)
# Home and Sleep mode aren't used in UI yet:
# def turn_home_mode_on(self):
# """ Turns home mode on. """
# self.data.ecobee.set_climate_hold(self.thermostat_index, "home")
# def turn_home_mode_off(self):
# """ Turns home mode off. """
# self.data.ecobee.resume_program(self.thermostat_index)
# def turn_sleep_mode_on(self):
# """ Turns sleep mode on. """
# self.data.ecobee.set_climate_hold(self.thermostat_index, "sleep")
# def turn_sleep_mode_off(self):
# """ Turns sleep mode off. """
# self.data.ecobee.resume_program(self.thermostat_index)

View File

@@ -0,0 +1,90 @@
"""
Support for eq3 Bluetooth Smart thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.eq3btsmart/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE
from homeassistant.util.temperature import convert
REQUIREMENTS = ['bluepy_devices==0.2.0']
CONF_MAC = 'mac'
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the eq3 BLE thermostats."""
devices = []
for name, device_cfg in config[CONF_DEVICES].items():
mac = device_cfg[CONF_MAC]
devices.append(EQ3BTSmartThermostat(mac, name))
add_devices(devices)
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of a EQ3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
from bluepy_devices.devices import eq3btsmart
self._name = _name
self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Can not report temperature, so return target_temperature."""
return self.target_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._thermostat.target_temperature
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._thermostat.target_temperature = temperature
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {"mode": self._thermostat.mode,
"mode_readable": self._thermostat.mode_readable}
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert(self._thermostat.min_temp, TEMP_CELSIUS,
self.unit_of_measurement)
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert(self._thermostat.max_temp, TEMP_CELSIUS,
self.unit_of_measurement)
def update(self):
"""Update the data from the thermostat."""
self._thermostat.update()

View File

@@ -0,0 +1,220 @@
"""
Adds support for generic thermostat units.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
DEPENDENCIES = ['switch', 'sensor']
TOL_TEMP = 0.3
CONF_NAME = 'name'
DEFAULT_NAME = 'Generic Thermostat'
CONF_HEATER = 'heater'
CONF_SENSOR = 'target_sensor'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({
vol.Required("platform"): "generic_thermostat",
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HEATER): cv.entity_id,
vol.Required(CONF_SENSOR): cv.entity_id,
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_AC_MODE): vol.Coerce(bool),
vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the generic thermostat."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
sensor_entity_id = config.get(CONF_SENSOR)
min_temp = config.get(CONF_MIN_TEMP)
max_temp = config.get(CONF_MAX_TEMP)
target_temp = config.get(CONF_TARGET_TEMP)
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
add_devices([GenericThermostat(hass, name, heater_entity_id,
sensor_entity_id, min_temp,
max_temp, target_temp, ac_mode,
min_cycle_duration)])
# pylint: disable=too-many-instance-attributes, abstract-method
class GenericThermostat(ClimateDevice):
"""Representation of a GenericThermostat device."""
# pylint: disable=too-many-arguments
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self.heater_entity_id = heater_entity_id
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._active = False
self._cur_temp = None
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
track_state_change(hass, sensor_entity_id, self._sensor_changed)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._update_temp(sensor_state)
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the thermostat."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit
@property
def current_temperature(self):
"""Return the sensor temperature."""
return self._cur_temp
@property
def operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.ac_mode:
cooling = self._active and self._is_device_active
return STATE_COOL if cooling else STATE_IDLE
else:
heating = self._active and self._is_device_active
return STATE_HEAT if heating else STATE_IDLE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temp = temperature
self._control_heating()
self.update_ha_state()
@property
def min_temp(self):
"""Return the minimum temperature."""
# pylint: disable=no-member
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
@property
def max_temp(self):
"""Return the maximum temperature."""
# pylint: disable=no-member
if self._min_temp:
return self._max_temp
else:
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
def _sensor_changed(self, entity_id, old_state, new_state):
"""Called when temperature changes."""
if new_state is None:
return
self._update_temp(new_state)
self._control_heating()
self.update_ha_state()
def _update_temp(self, state):
"""Update thermostat with latest state from sensor."""
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
try:
self._cur_temp = self.hass.config.units.temperature(
float(state.state), unit)
except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex)
def _control_heating(self):
"""Check if we need to turn heating on or off."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
self._active = True
_LOGGER.info('Obtained current and target temperature. '
'Generic thermostat active.')
if not self._active:
return
if self.min_cycle_duration:
if self._is_device_active:
current_state = STATE_ON
else:
current_state = STATE_OFF
long_enough = condition.state(self.hass, self.heater_entity_id,
current_state,
self.min_cycle_duration)
if not long_enough:
return
if self.ac_mode:
too_hot = self._cur_temp - self._target_temp > TOL_TEMP
is_cooling = self._is_device_active
if too_hot and not is_cooling:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
elif not too_hot and is_cooling:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > TOL_TEMP
is_heating = self._is_device_active
if too_cold and not is_heating:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
elif not too_cold and is_heating:
_LOGGER.info('Turning off heater %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
@property
def _is_device_active(self):
"""If the toggleable device is currently active."""
return switch.is_on(self.hass, self.heater_entity_id)

View File

@@ -0,0 +1,116 @@
"""
Support for the PRT Heatmiser themostats using the V3 protocol.
See https://github.com/andylockran/heatmiserV3 for more info on the
heatmiserV3 module dependency.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.heatmiser/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
CONF_IPADDRESS = 'ipaddress'
CONF_PORT = 'port'
CONF_TSTATS = 'tstats'
REQUIREMENTS = ["heatmiserV3==0.9.1"]
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the heatmiser thermostat."""
from heatmiserV3 import heatmiser, connection
ipaddress = str(config[CONF_IPADDRESS])
port = str(config[CONF_PORT])
if ipaddress is None or port is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_IPADDRESS, CONF_PORT)
return False
serport = connection.connection(ipaddress, port)
serport.open()
tstats = []
if CONF_TSTATS in config:
tstats = config[CONF_TSTATS]
if tstats is None:
_LOGGER.error("No thermostats configured.")
return False
for tstat in tstats:
add_devices([
HeatmiserV3Thermostat(
heatmiser,
tstat.get("id"),
tstat.get("name"),
serport)
])
return
class HeatmiserV3Thermostat(ClimateDevice):
"""Representation of a HeatmiserV3 thermostat."""
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, heatmiser, device, name, serport):
"""Initialize the thermostat."""
self.heatmiser = heatmiser
self.device = device
self.serport = serport
self._current_temperature = None
self._name = name
self._id = device
self.dcb = None
self.update()
self._target_temperature = int(self.dcb.get("roomset"))
@property
def name(self):
"""Return the name of the thermostat, if any."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self.dcb is not None:
low = self.dcb.get("floortemplow ")
high = self.dcb.get("floortemphigh")
temp = (high*256 + low)/10.0
self._current_temperature = temp
else:
self._current_temperature = None
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.heatmiser.hmSendAddress(
self._id,
18,
temperature,
1,
self.serport)
self._target_temperature = temperature
def update(self):
"""Get the latest data."""
self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport)

View File

@@ -0,0 +1,130 @@
"""
Support for Homematic thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homematic/
"""
import logging
import homeassistant.components.homematic as homematic
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.util.temperature import convert
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
DEPENDENCIES = ['homematic']
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
HM_STATE_MAP = {
"AUTO_MODE": STATE_AUTO,
"MANU_MODE": STATE_MANUAL,
"BOOST_MODE": STATE_BOOST,
}
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the Homematic thermostat platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(
HMThermostat,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
class HMThermostat(homematic.HMDevice, ClimateDevice):
"""Representation of a Homematic thermostat."""
@property
def unit_of_measurement(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.available:
return None
# read state and search
for mode, state in HM_STATE_MAP.items():
code = getattr(self._hmdevice, mode, 0)
if self._data.get('CONTROL_MODE') == code:
return state
@property
def operation_list(self):
"""List of available operation modes."""
if not self.available:
return None
op_list = []
# generate list
for mode in self._hmdevice.ACTIONNODE:
if mode in HM_STATE_MAP:
op_list.append(HM_STATE_MAP.get(mode))
return op_list
@property
def current_humidity(self):
"""Return the current humidity."""
if not self.available:
return None
return self._data.get('ACTUAL_HUMIDITY', None)
@property
def current_temperature(self):
"""Return the current temperature."""
if not self.available:
return None
return self._data.get('ACTUAL_TEMPERATURE', None)
@property
def target_temperature(self):
"""Return the target temperature."""
if not self.available:
return None
return self._data.get('SET_TEMPERATURE', None)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if not self.available:
return None
if temperature is None:
return
self._hmdevice.set_temperature(temperature)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for mode, state in HM_STATE_MAP.items():
if state == operation_mode:
code = getattr(self._hmdevice, mode, 0)
self._hmdevice.MODE = code
@property
def min_temp(self):
"""Return the minimum temperature - 4.5 means off."""
return convert(4.5, TEMP_CELSIUS, self.unit_of_measurement)
@property
def max_temp(self):
"""Return the maximum temperature - 30.5 means on."""
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
# Add state to data dict
self._data.update({"CONTROL_MODE": STATE_UNKNOWN,
"SET_TEMPERATURE": STATE_UNKNOWN,
"ACTUAL_TEMPERATURE": STATE_UNKNOWN})
# support humidity
if 'ACTUAL_HUMIDITY' in self._hmdevice.SENSORNODE:
self._data.update({'ACTUAL_HUMIDITY': STATE_UNKNOWN})

View File

@@ -0,0 +1,273 @@
"""
Support for Honeywell Round Connected and Honeywell Evohome thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.honeywell/
"""
import logging
import socket
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.2.1']
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature"
DEFAULT_AWAY_TEMP = 16
def _setup_round(username, password, config, add_devices):
"""Setup rounding function."""
from evohomeclient import EvohomeClient
try:
away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False
evo_api = EvohomeClient(username, password)
try:
zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones):
add_devices([RoundThermostat(evo_api,
zone['id'],
i == 0,
away_temp)])
except socket.error:
_LOGGER.error(
"Connection error logging into the honeywell evohome web service"
)
return False
return True
# config will be used later
def _setup_us(username, password, config, add_devices):
"""Setup user."""
import somecomfort
try:
client = somecomfort.SomeComfort(username, password)
except somecomfort.AuthError:
_LOGGER.error('Failed to login to honeywell account %s', username)
return False
except somecomfort.SomeComfortError as ex:
_LOGGER.error('Failed to initialize honeywell client: %s', str(ex))
return False
dev_id = config.get('thermostat')
loc_id = config.get('location')
add_devices([HoneywellUSThermostat(client, device)
for location in client.locations_by_id.values()
for device in location.devices_by_id.values()
if ((not loc_id or location.locationid == loc_id) and
(not dev_id or device.deviceid == dev_id))])
return True
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the honeywel thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get('region', 'eu').lower()
if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
if region not in ('us', 'eu'):
_LOGGER.error('Region `%s` is invalid (use either us or eu)', region)
return False
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, device, zone_id, master, away_temp):
"""Initialize the thermostat."""
self.device = device
self._current_temperature = None
self._target_temperature = None
self._name = "round connected"
self._id = zone_id
self._master = master
self._is_dhw = False
self._away_temp = away_temp
self._away = False
self.update()
@property
def name(self):
"""Return the name of the honeywell, if any."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._is_dhw:
return None
return self._target_temperature
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.device.set_temperature(self._name, temperature)
@property
def current_operation(self: ClimateDevice) -> str:
"""Get the current operation of the system."""
return getattr(self.device, 'system_mode', None)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.device, 'system_mode'):
self.device.system_mode = operation_mode
def turn_away_mode_on(self):
"""Turn away on.
Evohome does have a proprietary away mode, but it doesn't really work
the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
self.device.set_temperature(self._name, self._away_temp)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.device.cancel_temp_override(self._name)
def update(self):
"""Get the latest date."""
try:
# Only refresh if this is the "master" device,
# others will pick up the cache
for val in self.device.temperatures(force_refresh=self._master):
if val['id'] == self._id:
data = val
except StopIteration:
_LOGGER.error("Did not receive any temperature data from the "
"evohomeclient API.")
return
self._current_temperature = data['temp']
self._target_temperature = data['setpoint']
if data['thermostat'] == "DOMESTIC_HOT_WATER":
self._name = "Hot Water"
self._is_dhw = True
else:
self._name = data['name']
self._is_dhw = False
# pylint: disable=abstract-method
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
def __init__(self, client, device):
"""Initialize the thermostat."""
self._client = client
self._device = device
@property
def is_fan_on(self):
"""Return true if fan is on."""
return self._device.fan_running
@property
def name(self):
"""Return the name of the honeywell, if any."""
return self._device.name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
else TEMP_FAHRENHEIT)
@property
def current_temperature(self):
"""Return the current temperature."""
self._device.refresh()
return self._device.current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._device.system_mode == 'cool':
return self._device.setpoint_cool
else:
return self._device.setpoint_heat
@property
def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, 'system_mode', None)
def set_temperature(self, **kwargs):
"""Set target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
import somecomfort
try:
if self._device.system_mode == 'cool':
self._device.setpoint_cool = temperature
else:
self._device.setpoint_heat = temperature
except somecomfort.SomeComfortError:
_LOGGER.error('Temperature %.1f out of range', temperature)
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {'fan': (self.is_fan_on and 'running' or 'idle'),
'fanmode': self._device.fan_mode,
'system_mode': self._device.system_mode}
def turn_away_mode_on(self):
"""Turn away on."""
pass
def turn_away_mode_off(self):
"""Turn away off."""
pass
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, 'system_mode'):
self._device.system_mode = operation_mode

View File

@@ -0,0 +1,86 @@
"""
Support for KNX thermostats.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/knx/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.knx import (
KNXConfig, KNXMultiAddressDevice)
DEPENDENCIES = ["knx"]
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create and add an entity based on the configuration."""
add_entities([
KNXThermostat(hass, KNXConfig(config))
])
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
"""Representation of a KNX thermostat.
A KNX thermostat will has the following parameters:
- temperature (current temperature)
- setpoint (target temperature in HASS terms)
- operation mode selection (comfort/night/frost protection)
This version supports only polling. Messages from the KNX bus do not
automatically update the state of the thermostat (to be implemented
in future releases)
"""
def __init__(self, hass, config):
"""Initialize the thermostat based on the given configuration."""
KNXMultiAddressDevice.__init__(self, hass, config,
["temperature", "setpoint"],
["mode"])
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
self._away = False # not yet supported
self._is_fan_on = False # not yet supported
@property
def should_poll(self):
"""Polling is needed for the KNX thermostat."""
return True
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self):
"""Return the current temperature."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("temperature"))
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("setpoint"))
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
from knxip.conversion import float_to_knx2
self.set_value("setpoint", float_to_knx2(temperature))
_LOGGER.debug("Set target temperature to %s", temperature)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
raise NotImplementedError()

View File

@@ -0,0 +1,192 @@
"""
Support for Nest thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.nest/
"""
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE)
DEPENDENCIES = ['nest']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Nest thermostat."""
add_devices([NestThermostat(structure, device)
for structure, device in nest.devices()])
# pylint: disable=abstract-method
class NestThermostat(ClimateDevice):
"""Representation of a Nest thermostat."""
def __init__(self, structure, device):
"""Initialize the thermostat."""
self.structure = structure
self.device = device
@property
def name(self):
"""Return the name of the nest, if any."""
location = self.device.where
name = self.device.name
if location is None:
return name
else:
if name == '':
return location.capitalize()
else:
return location.capitalize() + '(' + name + ')'
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
# Move these to Thermostat Device and make them global
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
"mode": self.device.mode
}
@property
def current_temperature(self):
"""Return the current temperature."""
return self.device.temperature
@property
def operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.hvac_ac_state is True:
return STATE_COOL
elif self.device.hvac_heater_state is True:
return STATE_HEAT
else:
return STATE_IDLE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.mode == 'range':
low, high = self.target_temperature_low, \
self.target_temperature_high
if self.operation == STATE_COOL:
temp = high
elif self.operation == STATE_HEAT:
temp = low
else:
# If the outside temp is lower than the current temp, consider
# the 'low' temp to the target, otherwise use the high temp
if (self.device.structure.weather.current.temperature <
self.current_temperature):
temp = low
else:
temp = high
else:
if self.is_away_mode_on:
# away_temperature is a low, high tuple. Only one should be set
# if not in range mode, the other will be None
temp = self.device.away_temperature[0] or \
self.device.away_temperature[1]
else:
temp = self.device.target
return temp
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.is_away_mode_on and self.device.away_temperature[0]:
# away_temperature is always a low, high tuple
return self.device.away_temperature[0]
if self.device.mode == 'range':
return self.device.target[0]
return self.target_temperature
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.is_away_mode_on and self.device.away_temperature[1]:
# away_temperature is always a low, high tuple
return self.device.away_temperature[1]
if self.device.mode == 'range':
return self.device.target[1]
return self.target_temperature
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self.structure.away
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self.device.mode == 'range':
if self.target_temperature == self.target_temperature_low:
temperature = (temperature, self.target_temperature_high)
elif self.target_temperature == self.target_temperature_high:
temperature = (self.target_temperature_low, temperature)
self.device.target = temperature
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
self.device.mode = operation_mode
def turn_away_mode_on(self):
"""Turn away on."""
self.structure.away = True
def turn_away_mode_off(self):
"""Turn away off."""
self.structure.away = False
@property
def is_fan_on(self):
"""Return whether the fan is on."""
return self.device.fan
def turn_fan_on(self):
"""Turn fan on."""
self.device.fan = True
def turn_fan_off(self):
"""Turn fan off."""
self.device.fan = False
@property
def min_temp(self):
"""Identify min_temp in Nest API or defaults if not available."""
temp = self.device.away_temperature.low
if temp is None:
return super().min_temp
else:
return temp
@property
def max_temp(self):
"""Identify max_temp in Nest API or defaults if not available."""
temp = self.device.away_temperature.high
if temp is None:
return super().max_temp
else:
return temp
def update(self):
"""Python-nest has its own mechanism for staying up to date."""
pass

View File

@@ -0,0 +1,93 @@
"""
Support for Proliphix NT10e Thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.proliphix/
"""
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
REQUIREMENTS = ['proliphix==0.3.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Proliphix thermostats."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
import proliphix
pdp = proliphix.PDP(host, username, password)
add_devices([
ProliphixThermostat(pdp)
])
# pylint: disable=abstract-method
class ProliphixThermostat(ClimateDevice):
"""Representation a Proliphix thermostat."""
def __init__(self, pdp):
"""Initialize the thermostat."""
self._pdp = pdp
# initial data
self._pdp.update()
self._name = self._pdp.name
@property
def should_poll(self):
"""Polling needed for thermostat."""
return True
def update(self):
"""Update the data from the thermostat."""
self._pdp.update()
@property
def name(self):
"""Return the name of the thermostat."""
return self._name
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
"fan": self._pdp.fan_state
}
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
"""Return the current temperature."""
return self._pdp.cur_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._pdp.setback
@property
def current_operation(self):
"""Return the current state of the thermostat."""
state = self._pdp.hvac_state
if state in (1, 2):
return STATE_IDLE
elif state == 3:
return STATE_HEAT
elif state == 6:
return STATE_COOL
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._pdp.setback = temperature

View File

@@ -0,0 +1,139 @@
"""
Support for Radio Thermostat wifi-enabled home thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.radiotherm/
"""
import datetime
import logging
from urllib.error import URLError
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
ClimateDevice)
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
REQUIREMENTS = ['radiotherm==1.2']
HOLD_TEMP = 'hold_temp'
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Radio Thermostat."""
import radiotherm
hosts = []
if CONF_HOST in config:
hosts = config[CONF_HOST]
else:
hosts.append(radiotherm.discover.discover_address())
if hosts is None:
_LOGGER.error("No radiotherm thermostats detected.")
return False
hold_temp = config.get(HOLD_TEMP, False)
tstats = []
for host in hosts:
try:
tstat = radiotherm.get_thermostat(host)
tstats.append(RadioThermostat(tstat, hold_temp))
except (URLError, OSError):
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
host)
add_devices(tstats)
# pylint: disable=abstract-method
class RadioThermostat(ClimateDevice):
"""Representation of a Radio Thermostat."""
def __init__(self, device, hold_temp):
"""Initialize the thermostat."""
self.device = device
self.set_time()
self._target_temperature = None
self._current_temperature = None
self._current_operation = STATE_IDLE
self._name = None
self.hold_temp = hold_temp
self.update()
@property
def name(self):
"""Return the name of the Radio Thermostat."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
"fan": self.device.fmode['human'],
"mode": self.device.tmode['human']
}
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def current_operation(self):
"""Return the current operation. head, cool idle."""
return self._current_operation
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
def update(self):
"""Update the data from the thermostat."""
self._current_temperature = self.device.temp['raw']
self._name = self.device.name['raw']
if self.device.tmode['human'] == 'Cool':
self._target_temperature = self.device.t_cool['raw']
self._current_operation = STATE_COOL
elif self.device.tmode['human'] == 'Heat':
self._target_temperature = self.device.t_heat['raw']
self._current_operation = STATE_HEAT
else:
self._current_operation = STATE_IDLE
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self._current_operation == STATE_COOL:
self.device.t_cool = temperature
elif self._current_operation == STATE_HEAT:
self.device.t_heat = temperature
if self.hold_temp:
self.device.hold = 1
else:
self.device.hold = 0
def set_time(self):
"""Set device time."""
now = datetime.datetime.now()
self.device.time = {'day': now.weekday(),
'hour': now.hour, 'minute': now.minute}
def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off)."""
if operation_mode == STATE_OFF:
self.device.tmode = 0
elif operation_mode == STATE_AUTO:
self.device.tmode = 3
elif operation_mode == STATE_COOL:
self.device.t_cool = self._target_temperature
elif operation_mode == STATE_HEAT:
self.device.t_heat = self._target_temperature

View File

@@ -0,0 +1,84 @@
set_aux_heat:
description: Turn auxillary heater on/off for climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
aux_heat:
description: New value of axillary heater
example: true
set_away_mode:
description: Turn away mode on/off for climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
away_mode:
description: New value of away mode
example: true
set_temperature:
description: Set target temperature of climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
temperature:
description: New target temperature for hvac
example: 25
set_humidity:
description: Set target humidity of climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
humidity:
description: New target humidity for climate device
example: 60
set_fan_mode:
description: Set fan operation for climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.nest'
fan:
description: New value of fan mode
example: On Low
set_operation_mode:
description: Set operation mode for climate device
fields:
entity_id:
description: Name(s) of entities to change
example: 'climet.nest'
operation_mode:
description: New value of operation mode
example: Heat
set_swing_mode:
description: Set swing operation for climate device
fields:
entity_id:
description: Name(s) of entities to change
example: '.nest'
swing_mode:
description: New value of swing mode
example: 1

View File

@@ -0,0 +1,302 @@
"""
Support for ZWave climate devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components import zwave
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
DEFAULT_NAME = 'ZWave Climate'
REMOTEC = 0x5254
REMOTEC_ZXT_120 = 0x8377
REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120)
HORSTMANN = 0x0059
HORSTMANN_HRT4_ZW = 0x3
HORSTMANN_HRT4_ZW_THERMOSTAT = (HORSTMANN, HORSTMANN_HRT4_ZW)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
WORKAROUND_HRT4_ZW = 'hrt4_zw'
DEVICE_MAPPINGS = {
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120,
HORSTMANN_HRT4_ZW_THERMOSTAT: WORKAROUND_HRT4_ZW
}
SET_TEMP_TO_INDEX = {
'Heat': 1,
'Cool': 2,
'Auto': 3,
'Aux Heat': 4,
'Resume': 5,
'Fan Only': 6,
'Furnace': 7,
'Dry Air': 8,
'Moist Air': 9,
'Auto Changeover': 10,
'Heat Econ': 11,
'Cool Econ': 12,
'Away': 13,
'Unknown': 14
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ZWave Climate devices."""
if discovery_info is None or zwave.NETWORK is None:
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
discovery_info, zwave.NETWORK)
return
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
# pylint: disable=too-many-arguments, abstract-method
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
# pylint: disable=too-many-public-methods, too-many-instance-attributes
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
self._target_temperature = None
self._current_temperature = None
self._current_operation = None
self._operation_list = None
self._current_fan_mode = None
self._fan_list = None
self._current_swing_mode = None
self._swing_list = None
self._unit = temp_unit
self._index_operation = None
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
self._hrt4_zw = None
self.update_properties()
# register listener
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120:
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
" workaround")
self._zxt_120 = 1
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_HRT4_ZW:
_LOGGER.debug("Horstmann HRT4-ZW Zwave Thermostat"
" workaround")
self._hrt4_zw = 1
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id or \
self._value.node == value.node:
self.update_properties()
self.update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._index_operation = SET_TEMP_TO_INDEX.get(
self._current_operation)
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None and \
self.current_operation != 'Off':
if self._index_operation != value.index:
continue
if self._zxt_120:
break
self._target_temperature = int(value.data)
break
_LOGGER.debug("Device can't set setpoint based on operation mode."
" Defaulting to index=1")
self._target_temperature = int(value.data)
@property
def should_poll(self):
"""No polling on ZWave."""
return False
@property
def current_fan_mode(self):
"""Return the fan speed set."""
return self._current_fan_mode
@property
def fan_list(self):
"""List of available fan modes."""
return self._fan_list
@property
def current_swing_mode(self):
"""Return the swing mode set."""
return self._current_swing_mode
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
if self._unit == 'C':
return TEMP_CELSIUS
elif self._unit == 'F':
return TEMP_FAHRENHEIT
else:
return self._unit
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def current_operation(self):
"""Return the current operation mode."""
return self._current_operation
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
# pylint: disable=too-many-branches, too-many-statements
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
return
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None:
if self._hrt4_zw and self.current_operation == 'Off':
# HRT4-ZW can change setpoint when off.
value.data = int(temperature)
if self._index_operation != value.index:
continue
_LOGGER.debug("self._index_operation=%s and"
" self._current_operation=%s",
self._index_operation,
self._current_operation)
if self._zxt_120:
_LOGGER.debug("zxt_120: Setting new setpoint for %s, "
" operation=%s, temp=%s",
self._index_operation,
self._current_operation, temperature)
# ZXT-120 does not support get setpoint
self._target_temperature = temperature
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
break
else:
_LOGGER.debug("Setting new setpoint for %s, "
"operation=%s, temp=%s",
self._index_operation,
self._current_operation, temperature)
value.data = temperature
break
else:
_LOGGER.debug("Setting new setpoint for no known "
"operation mode. Index=1 and "
"temperature=%s", temperature)
value.data = temperature
break
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break

View File

@@ -11,26 +11,26 @@ import logging
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME
from homeassistant.helpers.entity import generate_entity_id
DOMAIN = "configurator"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SERVICE_CONFIGURE = "configure"
STATE_CONFIGURE = "configure"
STATE_CONFIGURED = "configured"
ATTR_LINK_NAME = "link_name"
ATTR_LINK_URL = "link_url"
ATTR_CONFIGURE_ID = "configure_id"
ATTR_DESCRIPTION = "description"
ATTR_DESCRIPTION_IMAGE = "description_image"
ATTR_SUBMIT_CAPTION = "submit_caption"
ATTR_FIELDS = "fields"
ATTR_ERRORS = "errors"
_REQUESTS = {}
_INSTANCES = {}
_LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description'
ATTR_DESCRIPTION_IMAGE = 'description_image'
ATTR_ERRORS = 'errors'
ATTR_FIELDS = 'fields'
ATTR_LINK_NAME = 'link_name'
ATTR_LINK_URL = 'link_url'
ATTR_SUBMIT_CAPTION = 'submit_caption'
DOMAIN = 'configurator'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SERVICE_CONFIGURE = 'configure'
STATE_CONFIGURE = 'configure'
STATE_CONFIGURED = 'configured'
# pylint: disable=too-many-arguments

View File

@@ -15,20 +15,20 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
DOMAIN = "conversation"
REQUIREMENTS = ['fuzzywuzzy==0.11.1']
SERVICE_PROCESS = "process"
ATTR_TEXT = 'text'
ATTR_TEXT = "text"
DOMAIN = 'conversation'
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
SERVICE_PROCESS = 'process'
SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REQUIREMENTS = ['fuzzywuzzy==0.11.0']
def setup(hass, config):
"""Register the process service."""

View File

@@ -0,0 +1,237 @@
"""
Support for Cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover/
"""
import os
import logging
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.components import group
from homeassistant.const import (
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID)
DOMAIN = 'cover'
SCAN_INTERVAL = 15
GROUP_NAME_ALL_COVERS = 'all_covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format(
GROUP_NAME_ALL_COVERS)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
ATTR_CURRENT_TILT_POSITION = 'current_tilt_position'
ATTR_POSITION = 'position'
ATTR_TILT_POSITION = 'tilt_position'
COVER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
vol.Required(ATTR_POSITION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
})
COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
vol.Required(ATTR_TILT_POSITION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
})
SERVICE_TO_METHOD = {
SERVICE_OPEN_COVER: {'method': 'open_cover'},
SERVICE_CLOSE_COVER: {'method': 'close_cover'},
SERVICE_SET_COVER_POSITION: {
'method': 'set_cover_position',
'schema': COVER_SET_COVER_POSITION_SCHEMA},
SERVICE_STOP_COVER: {'method': 'stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'},
SERVICE_SET_COVER_TILT_POSITION: {
'method': 'set_cover_tilt_position',
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},
}
def is_closed(hass, entity_id=None):
"""Return if the cover is closed based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_COVERS
return hass.states.is_state(entity_id, STATE_CLOSED)
def open_cover(hass, entity_id=None):
"""Open all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN_COVER, data)
def close_cover(hass, entity_id=None):
"""Close all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE_COVER, data)
def set_cover_position(hass, position, entity_id=None):
"""Move to specific position all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
data[ATTR_POSITION] = position
hass.services.call(DOMAIN, SERVICE_SET_COVER_POSITION, data)
def stop_cover(hass, entity_id=None):
"""Stop all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_STOP_COVER, data)
def open_cover_tilt(hass, entity_id=None):
"""Open all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN_COVER_TILT, data)
def close_cover_tilt(hass, entity_id=None):
"""Close all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE_COVER_TILT, data)
def set_cover_tilt_position(hass, tilt_position, entity_id=None):
"""Move to specific tilt position all or specified cover."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
data[ATTR_TILT_POSITION] = tilt_position
hass.services.call(DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data)
def stop_cover_tilt(hass, entity_id=None):
"""Stop all or specified cover tilt."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_STOP_COVER_TILT, data)
def setup(hass, config):
"""Track states and offer events for covers."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS)
component.setup(config)
def handle_cover_service(service):
"""Handle calls to the cover services."""
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
params.pop(ATTR_ENTITY_ID, None)
if method:
for cover in component.extract_from_service(service):
getattr(cover, method['method'])(**params)
if cover.should_poll:
cover.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get(
'schema', COVER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, service_name, handle_cover_service,
descriptions.get(service_name), schema=schema)
return True
class CoverDevice(Entity):
"""Representation a cover."""
# pylint: disable=no-self-use
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
pass
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt.
None is unknown, 0 is closed, 100 is fully open.
"""
pass
@property
def state(self):
"""Return the state of the cover."""
closed = self.is_closed
if closed is None:
return STATE_UNKNOWN
return STATE_CLOSED if closed else STATE_OPEN
@property
def state_attributes(self):
"""Return the state attributes."""
data = {}
current = self.current_cover_position
if current is not None:
data[ATTR_CURRENT_POSITION] = self.current_cover_position
current_tilt = self.current_cover_tilt_position
if current_tilt is not None:
data[ATTR_CURRENT_TILT_POSITION] = self.current_cover_tilt_position
return data
@property
def is_closed(self):
"""Return if the cover is closed or not."""
raise NotImplementedError()
def open_cover(self, **kwargs):
"""Open the cover."""
raise NotImplementedError()
def close_cover(self, **kwargs):
"""Close cover."""
raise NotImplementedError()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
pass
def stop_cover(self, **kwargs):
"""Stop the cover."""
pass
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
pass
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
pass
def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
pass
def stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
pass

View File

@@ -0,0 +1,153 @@
"""
Support for command line covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.command_line/
"""
import logging
import subprocess
import voluptuous as vol
from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_COMMAND_CLOSE, default='true'): cv.string,
vol.Optional(CONF_COMMAND_OPEN, default='true'): cv.string,
vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE, default='{{ value }}'): cv.template,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup cover controlled by shell commands."""
devices = config.get(CONF_COVERS, {})
covers = []
for device_name, device_config in devices.items():
covers.append(
CommandCover(
hass,
device_config.get(CONF_FRIENDLY_NAME, device_name),
device_config.get(CONF_COMMAND_OPEN),
device_config.get(CONF_COMMAND_CLOSE),
device_config.get(CONF_COMMAND_STOP),
device_config.get(CONF_COMMAND_STATE),
device_config.get(CONF_VALUE_TEMPLATE),
)
)
if not covers:
_LOGGER.error("No covers added")
return False
add_devices(covers)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class CommandCover(CoverDevice):
"""Representation a command line cover."""
# pylint: disable=too-many-arguments
def __init__(self, hass, name, command_open, command_close, command_stop,
command_state, value_template):
"""Initialize the cover."""
self._hass = hass
self._name = name
self._state = None
self._command_open = command_open
self._command_close = command_close
self._command_stop = command_stop
self._command_state = command_state
self._value_template = value_template
@staticmethod
def _move_cover(command):
"""Execute the actual commands."""
_LOGGER.info('Running command: %s', command)
success = (subprocess.call(command, shell=True) == 0)
if not success:
_LOGGER.error('Command failed: %s', command)
return success
@staticmethod
def _query_state_value(command):
"""Execute state command for return value."""
_LOGGER.info('Running state command: %s', command)
try:
return_value = subprocess.check_output(command, shell=True)
return return_value.strip().decode('utf-8')
except subprocess.CalledProcessError:
_LOGGER.error('Command failed: %s', command)
@property
def should_poll(self):
"""Only poll if we have state command."""
return self._command_state is not None
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._state
def _query_state(self):
"""Query for the state."""
if not self._command_state:
_LOGGER.error('No state command specified')
return
return self._query_state_value(self._command_state)
def update(self):
"""Update device state."""
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
self._state = int(payload)
def open_cover(self, **kwargs):
"""Open the cover."""
self._move_cover(self._command_open)
def close_cover(self, **kwargs):
"""Close the cover."""
self._move_cover(self._command_close)
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._move_cover(self._command_stop)

View File

@@ -0,0 +1,170 @@
"""
Demo platform for the cover component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.cover import CoverDevice
from homeassistant.helpers.event import track_utc_time_change
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo covers."""
add_devices([
DemoCover(hass, 'Kitchen Window'),
DemoCover(hass, 'Hall Window', 10),
DemoCover(hass, 'Living Room Window', 70, 50),
])
class DemoCover(CoverDevice):
"""Representation of a demo cover."""
# pylint: disable=no-self-use, too-many-instance-attributes
def __init__(self, hass, name, position=None, tilt_position=None):
"""Initialize the cover."""
self.hass = hass
self._name = name
self._position = position
self._set_position = None
self._set_tilt_position = None
self._tilt_position = tilt_position
self._closing = True
self._closing_tilt = True
self._unsub_listener_cover = None
self._unsub_listener_cover_tilt = None
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def should_poll(self):
"""No polling needed for a demo cover."""
return False
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return self._position
@property
def current_cover_tilt_position(self):
"""Return the current tilt position of the cover."""
return self._tilt_position
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._position is not None:
if self.current_cover_position > 0:
return False
else:
return True
else:
return None
def close_cover(self, **kwargs):
"""Close the cover."""
if self._position in (0, None):
return
self._listen_cover()
self._closing = True
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
if self._tilt_position in (0, None):
return
self._listen_cover_tilt()
self._closing_tilt = True
def open_cover(self, **kwargs):
"""Open the cover."""
if self._position in (100, None):
return
self._listen_cover()
self._closing = False
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
if self._tilt_position in (100, None):
return
self._listen_cover_tilt()
self._closing_tilt = False
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self._set_position = round(position, -1)
if self._position == position:
return
self._listen_cover()
self._closing = position < self._position
def set_cover_tilt_position(self, tilt_position, **kwargs):
"""Move the cover til to a specific position."""
self._set_tilt_position = round(tilt_position, -1)
if self._tilt_position == tilt_position:
return
self._listen_cover_tilt()
self._closing_tilt = tilt_position < self._tilt_position
def stop_cover(self, **kwargs):
"""Stop the cover."""
if self._position is None:
return
if self._unsub_listener_cover is not None:
self._unsub_listener_cover()
self._unsub_listener_cover = None
self._set_position = None
def stop_cover_tilt(self, **kwargs):
"""Stop the cover tilt."""
if self._tilt_position is None:
return
if self._unsub_listener_cover_tilt is not None:
self._unsub_listener_cover_tilt()
self._unsub_listener_cover_tilt = None
self._set_tilt_position = None
def _listen_cover(self):
"""Listen for changes in cover."""
if self._unsub_listener_cover is None:
self._unsub_listener_cover = track_utc_time_change(
self.hass, self._time_changed_cover)
def _time_changed_cover(self, now):
"""Track time changes."""
if self._closing:
self._position -= 10
else:
self._position += 10
if self._position in (100, 0, self._set_position):
self.stop_cover()
self.update_ha_state()
def _listen_cover_tilt(self):
"""Listen for changes in cover tilt."""
if self._unsub_listener_cover_tilt is None:
self._unsub_listener_cover_tilt = track_utc_time_change(
self.hass, self._time_changed_cover_tilt)
def _time_changed_cover_tilt(self, now):
"""Track time changes."""
if self._closing_tilt:
self._tilt_position -= 10
else:
self._tilt_position += 10
if self._tilt_position in (100, 0, self._set_tilt_position):
self.stop_cover_tilt()
self.update_ha_state()

View File

@@ -0,0 +1,86 @@
"""
The homematic cover platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.homematic/
Important: For this platform to work the homematic component has to be
properly configured.
"""
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice,\
ATTR_POSITION
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(
HMCover,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
class HMCover(homematic.HMDevice, CoverDevice):
"""Represents a Homematic Cover in Home Assistant."""
@property
def current_cover_position(self):
"""
Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
if self.available:
return int(self._hm_get_state() * 100)
return None
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if self.available:
if ATTR_POSITION in kwargs:
position = float(kwargs[ATTR_POSITION])
position = min(100, max(0, position))
level = position / 100.0
self._hmdevice.set_level(level, self._channel)
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
def open_cover(self, **kwargs):
"""Open the cover."""
if self.available:
self._hmdevice.move_up(self._channel)
def close_cover(self, **kwargs):
"""Close the cover."""
if self.available:
self._hmdevice.move_down(self._channel)
def stop_cover(self, **kwargs):
"""Stop the device if in motion."""
if self.available:
self._hmdevice.stop(self._channel)
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
# Add state to data dict
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})

View File

@@ -0,0 +1,167 @@
"""
Support for MQTT cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mqtt/
"""
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.cover import CoverDevice
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_OPEN = 'payload_open'
CONF_PAYLOAD_CLOSE = 'payload_close'
CONF_PAYLOAD_STOP = 'payload_stop'
CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed'
DEFAULT_NAME = "MQTT Cover"
DEFAULT_PAYLOAD_OPEN = "OPEN"
DEFAULT_PAYLOAD_CLOSE = "CLOSE"
DEFAULT_PAYLOAD_STOP = "STOP"
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Cover."""
add_devices_callback([MqttCover(
hass,
config[CONF_NAME],
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_RETAIN],
config[CONF_STATE_OPEN],
config[CONF_STATE_CLOSED],
config[CONF_PAYLOAD_OPEN],
config[CONF_PAYLOAD_CLOSE],
config[CONF_PAYLOAD_STOP],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE)
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttCover(CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, hass, name, state_topic, command_topic, qos,
retain, state_open, state_closed, payload_open, payload_close,
payload_stop, optimistic, value_template):
"""Initialize the cover."""
self._position = None
self._state = None
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_open = payload_open
self._payload_close = payload_close
self._payload_stop = payload_stop
self._state_open = state_open
self._state_closed = state_closed
self._retain = retain
self._optimistic = optimistic or state_topic is None
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
if payload == self._state_open:
self._state = False
_LOGGER.warning("state=%s", int(self._state))
self.update_ha_state()
elif payload == self._state_closed:
self._state = True
self.update_ha_state()
elif payload.isnumeric() and 0 <= int(payload) <= 100:
if int(payload) > 0:
self._state = False
else:
self._state = True
self._position = int(payload)
self.update_ha_state()
else:
_LOGGER.warning(
"Payload is not True or False or"
" integer(0-100) %s", payload)
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
else:
mqtt.subscribe(hass, self._state_topic, message_received,
self._qos)
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._state
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._position
def open_cover(self, **kwargs):
"""Move the cover up."""
mqtt.publish(self.hass, self._command_topic, self._payload_open,
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = 100
self.update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
mqtt.publish(self.hass, self._command_topic, self._payload_close,
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = 0
self.update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""
mqtt.publish(self.hass, self._command_topic, self._payload_stop,
self._qos, self._retain)

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