* 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
This commit is contained in:
Fabian Affolter 2016-09-11 03:22:58 +02:00 committed by Robbie Trencheny
parent 64cc4a47ec
commit 1b46ed5045
303 changed files with 8104 additions and 3318 deletions

View File

@ -138,6 +138,7 @@ 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/foursquare.py
homeassistant/components/garage_door/rpi_gpio.py
@ -205,6 +206,7 @@ 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
@ -223,6 +225,7 @@ omit =
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
@ -232,6 +235,7 @@ omit =
homeassistant/components/sensor/onewire.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
@ -250,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

5
.gitignore vendored
View File

@ -96,4 +96,7 @@ virtualization/vagrant/.vagrant
virtualization/vagrant/config
# Visual Studio Code
.vscode
.vscode
# Built docs
docs/build

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

@ -14,7 +14,7 @@ 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.loader as loader
@ -90,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:
log_exception(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:
log_exception(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:
log_exception(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:
@ -173,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(
@ -182,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."""

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

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

@ -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.positive_int,
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,7 @@ 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

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

@ -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

@ -27,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

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

@ -2,7 +2,7 @@
Support for Ecobee sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ecobee/
https://home-assistant.io/components/binary_sensor.ecobee/
"""
from homeassistant.components import ecobee
from homeassistant.components.binary_sensor import BinarySensorDevice
@ -38,7 +38,7 @@ class EcobeeBinarySensor(BinarySensorDevice):
self.sensor_name = sensor_name
self.index = sensor_index
self._state = None
self._sensor_class = 'motion'
self._sensor_class = 'occupancy'
self.update()
@property

View File

@ -16,7 +16,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
ATTR_ENTITY_ID)
REQUIREMENTS = ["ha-ffmpeg==0.9"]
REQUIREMENTS = ["ha-ffmpeg==0.10"]
SERVICE_RESTART = 'ffmpeg_restart'

View File

@ -31,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):
@ -57,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

@ -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

@ -13,12 +13,15 @@ from homeassistant.components.binary_sensor import (
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS)
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_METHOD = 'GET'
DEFAULT_NAME = 'REST Binary Sensor'
DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
@ -27,10 +30,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
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,
})
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -39,7 +41,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
resource = config.get(CONF_RESOURCE)
method = config.get(CONF_METHOD)
payload = config.get(CONF_PAYLOAD)
verify_ssl = config.get('verify_ssl', True)
verify_ssl = config.get(CONF_VERIFY_SSL)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)

View File

@ -5,22 +5,22 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
import logging
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
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)
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
CONF_SENSORS = 'sensors'
_LOGGER = logging.getLogger(__name__)
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
@ -33,15 +33,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup template binary sensors."""
sensors = []
for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID]
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
@ -85,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):
@ -111,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

@ -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

@ -5,16 +5,14 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.ffmpeg/
"""
import logging
from contextlib import closing
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.camera.mjpeg import extract_image_from_mjpeg
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
REQUIREMENTS = ['ha-ffmpeg==0.9']
REQUIREMENTS = ['ha-ffmpeg==0.10']
_LOGGER = logging.getLogger(__name__)
@ -49,22 +47,20 @@ class FFmpegCamera(Camera):
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN)
def _ffmpeg_stream(self):
"""Return a FFmpeg process object."""
from haffmpeg import CameraMjpeg
ffmpeg = CameraMjpeg(self._ffmpeg_bin)
ffmpeg.open_camera(self._input, extra_cmd=self._extra_arguments)
return ffmpeg
def camera_image(self):
"""Return a still image response from the camera."""
with closing(self._ffmpeg_stream()) as stream:
return extract_image_from_mjpeg(stream)
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."""
stream = self._ffmpeg_stream()
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',

View File

@ -12,7 +12,6 @@ import voluptuous as vol
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.config import load_yaml_config_file
import homeassistant.util as util
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@ -44,6 +43,8 @@ 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"
@ -68,8 +69,10 @@ SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Required(ATTR_AUX_HEAT): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
@ -113,14 +116,19 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
def set_temperature(hass, temperature, entity_id=None):
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None):
"""Set new target temperature."""
data = {ATTR_TEMPERATURE: temperature}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, data)
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):
@ -227,20 +235,9 @@ def setup(hass, config):
def temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
temperature = util.convert(
service.data.get(ATTR_TEMPERATURE), float)
if temperature is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
return
kwargs = service.data
for climate in target_climate:
climate.set_temperature(convert_temperature(
temperature, hass.config.units.temperature_unit,
climate.unit_of_measurement))
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)
@ -351,7 +348,7 @@ class ClimateDevice(Entity):
@property
def state(self):
"""Return the current state."""
return self.target_temperature or STATE_UNKNOWN
return self.current_operation or STATE_UNKNOWN
@property
def state_attributes(self):
@ -364,6 +361,12 @@ class ClimateDevice(Entity):
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:
@ -432,6 +435,16 @@ class ClimateDevice(Entity):
"""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."""
@ -462,7 +475,7 @@ class ClimateDevice(Entity):
"""List of available swing modes."""
return None
def set_temperature(self, temperature):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
raise NotImplementedError()

View File

@ -4,17 +4,20 @@ 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
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
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, "Auto", "heat", None, None, None),
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
67, 54, "Off", "Cool", False),
67, 54, "Off", "cool", False, None, None),
DemoClimate("Ecobee", 23, TEMP_CELSIUS, None, 23, "Auto Low",
None, None, "Auto", "auto", None, 24, 21)
])
@ -26,7 +29,7 @@ class DemoClimate(ClimateDevice):
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):
current_operation, aux, target_temp_high, target_temp_low):
"""Initialize the climate device."""
self._name = name
self._target_temperature = target_temperature
@ -40,8 +43,10 @@ class DemoClimate(ClimateDevice):
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 Changeover", "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):
@ -68,6 +73,16 @@ class DemoClimate(ClimateDevice):
"""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."""
@ -108,9 +123,14 @@ class DemoClimate(ClimateDevice):
"""List of available fan modes."""
return self._fan_list
def set_temperature(self, temperature):
"""Set new target temperature."""
self._target_temperature = temperature
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):

View File

@ -6,23 +6,27 @@ 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)
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_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
DEPENDENCIES = ['ecobee']
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_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'
ATTR_FAN_MIN_ON_TIME = "fan_min_on_time"
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),
@ -142,7 +146,11 @@ class Thermostat(ClimateDevice):
@property
def current_operation(self):
"""Return current operation."""
return self.operation_mode
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
else:
return self.operation_mode
@property
def operation_list(self):
@ -211,11 +219,17 @@ class Thermostat(ClimateDevice):
"""Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index)
def set_temperature(self, temperature):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = int(temperature)
low_temp = temperature - 1
high_temp = temperature + 1
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
low_temp = temperature - 1
high_temp = temperature + 1
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if self.hold_temp:
self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp,
high_temp, "indefinite")

View File

@ -7,14 +7,12 @@ https://home-assistant.io/components/climate.eq3btsmart/
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS
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'
CONF_DEVICES = 'devices'
CONF_ID = 'id'
_LOGGER = logging.getLogger(__name__)
@ -28,7 +26,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices.append(EQ3BTSmartThermostat(mac, name))
add_devices(devices)
return True
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
@ -63,8 +60,11 @@ class EQ3BTSmartThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._thermostat.target_temperature
def set_temperature(self, 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

View File

@ -11,7 +11,8 @@ 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
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
@ -123,8 +124,11 @@ class GenericThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, temperature):
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()

View File

@ -10,7 +10,7 @@ https://home-assistant.io/components/climate.heatmiser/
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
CONF_IPADDRESS = 'ipaddress'
CONF_PORT = 'port'
@ -98,16 +98,18 @@ class HeatmiserV3Thermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
def set_temperature(self, temperature):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = int(temperature)
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.heatmiser.hmSendAddress(
self._id,
18,
temperature,
1,
self.serport)
self._target_temperature = int(temperature)
self._target_temperature = temperature
def update(self):
"""Get the latest data."""

View File

@ -8,7 +8,7 @@ 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
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
DEPENDENCIES = ['homematic']
@ -29,9 +29,11 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
discovery_info,
add_callback_devices)
return homematic.setup_hmdevice_discovery_helper(
HMThermostat,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
@ -90,10 +92,13 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
return None
return self._data.get('SET_TEMPERATURE', None)
def set_temperature(self, temperature):
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):
@ -113,26 +118,8 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
"""Return the maximum temperature - 30.5 means on."""
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
def _check_hm_to_ha_object(self):
"""Check if possible to use the Homematic object as this HA type."""
from pyhomematic.devicetypes.thermostats import HMThermostat\
as pyHMThermostat
# 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 isinstance(self._hmdevice, pyHMThermostat):
return True
_LOGGER.critical("This %s can't be use as thermostat", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
super()._init_data_struct()
# Add state to data dict
self._data.update({"CONTROL_MODE": STATE_UNKNOWN,
"SET_TEMPERATURE": STATE_UNKNOWN,

View File

@ -9,7 +9,8 @@ import socket
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.2.1']
@ -132,8 +133,11 @@ class RoundThermostat(ClimateDevice):
return None
return self._target_temperature
def set_temperature(self, 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
@ -234,8 +238,11 @@ class HoneywellUSThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, 'system_mode', None)
def set_temperature(self, temperature):
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':

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/knx/
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.knx import (
KNXConfig, KNXMultiAddressDevice)
@ -71,8 +71,11 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
return knx2_to_float(self.value("setpoint"))
def set_temperature(self, temperature):
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))

View File

@ -8,13 +8,13 @@ import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
from homeassistant.const import TEMP_CELSIUS, CONF_PLATFORM, CONF_SCAN_INTERVAL
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE)
DEPENDENCIES = ['nest']
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)),
})
@ -132,8 +132,11 @@ class NestThermostat(ClimateDevice):
"""Return if away mode is on."""
return self.structure.away
def set_temperature(self, temperature):
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)

View File

@ -7,7 +7,7 @@ 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)
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
REQUIREMENTS = ['proliphix==0.3.1']
@ -85,6 +85,9 @@ class ProliphixThermostat(ClimateDevice):
elif state == 6:
return STATE_COOL
def set_temperature(self, temperature):
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

@ -11,7 +11,7 @@ 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
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
REQUIREMENTS = ['radiotherm==1.2']
HOLD_TEMP = 'hold_temp'
@ -107,8 +107,11 @@ class RadioThermostat(ClimateDevice):
else:
self._current_operation = STATE_IDLE
def set_temperature(self, temperature):
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:

View File

@ -12,7 +12,8 @@ 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
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
@ -23,6 +24,10 @@ 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
@ -30,9 +35,11 @@ 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
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120,
HORSTMANN_HRT4_ZW_THERMOSTAT: WORKAROUND_HRT4_ZW
}
SET_TEMP_TO_INDEX = {
@ -63,6 +70,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
value.set_change_verified(False)
if value.index != 1: # Only add 1 device
return
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
@ -88,8 +97,10 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
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(
@ -99,12 +110,15 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
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."""
@ -120,6 +134,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
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",
@ -153,11 +169,14 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None and \
self.current_operation != 'Off':
if SET_TEMP_TO_INDEX.get(self._current_operation) \
!= value.index:
if self._index_operation != value.index:
continue
if self._zxt_120:
continue
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
@ -215,28 +234,48 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
def set_temperature(self, 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 SET_TEMP_TO_INDEX.get(self._current_operation) \
!= value.index:
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("SET_TEMP_TO_INDEX=%s and"
_LOGGER.debug("self._index_operation=%s and"
" self._current_operation=%s",
SET_TEMP_TO_INDEX.get(self._current_operation),
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 = int(round(temperature, 0))
value.data = round(temperature, 0)
break
else:
value.data = int(temperature)
break
_LOGGER.debug("Setting new setpoint for %s, "
"operation=%s, temp=%s",
self._index_operation,
self._current_operation, temperature)
value.data = temperature
break
else:
value.data = int(temperature)
_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):

View File

@ -7,29 +7,54 @@ https://home-assistant.io/components/cover.command_line/
import logging
import subprocess
from homeassistant.components.cover import CoverDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
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,
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
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."""
covers = config.get('covers', {})
devices = []
devices = config.get(CONF_COVERS, {})
covers = []
for dev_name, properties in covers.items():
devices.append(
for device_name, device_config in devices.items():
covers.append(
CommandCover(
hass,
properties.get('name', dev_name),
properties.get('opencmd', 'true'),
properties.get('closecmd', 'true'),
properties.get('stopcmd', 'true'),
properties.get('statecmd', False),
properties.get(CONF_VALUE_TEMPLATE, '{{ value }}')))
add_devices_callback(devices)
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

View File

@ -5,7 +5,6 @@ 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.const import EVENT_TIME_CHANGED
from homeassistant.helpers.event import track_utc_time_change
@ -32,8 +31,8 @@ class DemoCover(CoverDevice):
self._tilt_position = tilt_position
self._closing = True
self._closing_tilt = True
self._listener_cover = None
self._listener_cover_tilt = None
self._unsub_listener_cover = None
self._unsub_listener_cover_tilt = None
@property
def name(self):
@ -120,10 +119,9 @@ class DemoCover(CoverDevice):
"""Stop the cover."""
if self._position is None:
return
if self._listener_cover is not None:
self.hass.bus.remove_listener(EVENT_TIME_CHANGED,
self._listener_cover)
self._listener_cover = None
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):
@ -131,16 +129,15 @@ class DemoCover(CoverDevice):
if self._tilt_position is None:
return
if self._listener_cover_tilt is not None:
self.hass.bus.remove_listener(EVENT_TIME_CHANGED,
self._listener_cover_tilt)
self._listener_cover_tilt = None
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._listener_cover is None:
self._listener_cover = track_utc_time_change(
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):
@ -156,8 +153,8 @@ class DemoCover(CoverDevice):
def _listen_cover_tilt(self):
"""Listen for changes in cover tilt."""
if self._listener_cover_tilt is None:
self._listener_cover_tilt = track_utc_time_change(
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):

View File

@ -24,9 +24,11 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMCover,
discovery_info,
add_callback_devices)
return homematic.setup_hmdevice_discovery_helper(
HMCover,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
@ -77,25 +79,8 @@ class HMCover(homematic.HMDevice, CoverDevice):
if self.available:
self._hmdevice.stop(self._channel)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.actors import Blind
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# Check if the homematic device is correct for this HA device
if isinstance(self._hmdevice, Blind):
return True
_LOGGER.critical("This %s can't be use as cover!", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
super()._init_data_struct()
# Add state to data dict
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})

View File

@ -19,13 +19,13 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'binary_sensor',
'camera',
'climate',
'cover',
'device_tracker',
'garage_door',
'fan',
'light',
'lock',
'media_player',
'notify',
'rollershutter',
'sensor',
'switch',
]

View File

@ -7,23 +7,38 @@ https://home-assistant.io/components/device_sun_light_trigger/
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.event_decorators import track_state_change
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
DOMAIN = "device_sun_light_trigger"
DOMAIN = 'device_sun_light_trigger'
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun']
CONF_DEVICE_GROUP = 'device_group'
CONF_DISABLE_TURN_OFF = 'disable_turn_off'
CONF_LIGHT_GROUP = 'light_group'
CONF_LIGHT_PROFILE = 'light_profile'
DEFAULT_DISABLE_TURN_OFF = False
DEFAULT_LIGHT_PROFILE = 'relax'
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
# Light profile to be used if none given
LIGHT_PROFILE = 'relax'
CONF_LIGHT_PROFILE = 'light_profile'
CONF_LIGHT_GROUP = 'light_group'
CONF_DEVICE_GROUP = 'device_group'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_DEVICE_GROUP): cv.entity_id,
vol.Optional(CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF):
cv.boolean,
vol.Optional(CONF_LIGHT_GROUP): cv.string,
vol.Optional(CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE):
cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-locals
@ -35,10 +50,10 @@ def setup(hass, config):
light = get_component('light')
sun = get_component('sun')
disable_turn_off = 'disable_turn_off' in config[DOMAIN]
disable_turn_off = config[DOMAIN].get(CONF_DISABLE_TURN_OFF)
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
light.ENTITY_ID_ALL_LIGHTS)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE)
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
device_entity_ids = group.get_entity_ids(hass, device_group,
@ -52,7 +67,7 @@ def setup(hass, config):
light_ids = group.get_entity_ids(hass, light_group, light.DOMAIN)
if not light_ids:
logger.error("No lights found to turn on ")
logger.error("No lights found to turn on")
return False
def calc_time_for_light_when_sunset():

View File

@ -62,6 +62,7 @@ ATTR_HOST_NAME = 'host_name'
ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery'
ATTR_ATTRIBUTES = 'attributes'
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds
@ -86,10 +87,11 @@ def is_on(hass: HomeAssistantType, entity_id: str=None):
return hass.states.is_state(entity, STATE_HOME)
# pylint: disable=too-many-arguments
def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None,
host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=None,
battery=None): # pylint: disable=too-many-arguments
battery=None, attributes: dict=None):
"""Call service to notify you see device."""
data = {key: value for key, value in
((ATTR_MAC, mac),
@ -99,6 +101,9 @@ def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None,
(ATTR_GPS, gps),
(ATTR_GPS_ACCURACY, gps_accuracy),
(ATTR_BATTERY, battery)) if value is not None}
if attributes:
for key, value in attributes:
data[key] = value
hass.services.call(DOMAIN, SERVICE_SEE, data)
@ -164,7 +169,7 @@ def setup(hass: HomeAssistantType, config: ConfigType):
"""Service to see a device."""
args = {key: value for key, value in call.data.items() if key in
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY, ATTR_ATTRIBUTES)}
tracker.see(**args)
descriptions = load_yaml_config_file(
@ -202,7 +207,7 @@ class DeviceTracker(object):
def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None,
battery: str=None):
battery: str=None, attributes: dict=None):
"""Notify the device tracker that you see a device."""
with self.lock:
if mac is None and dev_id is None:
@ -218,7 +223,7 @@ class DeviceTracker(object):
if device:
device.seen(host_name, location_name, gps, gps_accuracy,
battery)
battery, attributes)
if device.track:
device.update_ha_state()
return
@ -232,7 +237,8 @@ class DeviceTracker(object):
if mac is not None:
self.mac_to_dev[mac] = device
device.seen(host_name, location_name, gps, gps_accuracy, battery)
device.seen(host_name, location_name, gps, gps_accuracy, battery,
attributes)
if device.track:
device.update_ha_state()
@ -267,6 +273,7 @@ class Device(Entity):
gps_accuracy = 0
last_seen = None # type: dt_util.dt.datetime
battery = None # type: str
attributes = None # type: dict
# Track if the last update of this device was HOME.
last_update_home = False
@ -330,6 +337,10 @@ class Device(Entity):
if self.battery:
attr[ATTR_BATTERY] = self.battery
if self.attributes:
for key, value in self.attributes:
attr[key] = value
return attr
@property
@ -338,13 +349,15 @@ class Device(Entity):
return self.away_hide and self.state != STATE_HOME
def seen(self, host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=0, battery: str=None):
gps: GPSType=None, gps_accuracy=0, battery: str=None,
attributes: dict=None):
"""Mark the device as seen."""
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.gps_accuracy = gps_accuracy or 0
self.battery = battery
self.attributes = attributes
self.gps = None
if gps is not None:
try:

View File

@ -9,9 +9,11 @@ import re
import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
@ -25,15 +27,16 @@ _DEVICES_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return a Aruba scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ArubaDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -90,7 +93,7 @@ class ArubaDeviceScanner(object):
def get_aruba_data(self):
"""Retrieve data from Aruba Access Point and return parsed result."""
import pexpect
connect = "ssh {}@{}"
connect = 'ssh {}@{}'
ssh = pexpect.spawn(connect.format(self.username, self.host))
query = ssh.expect(['password:', pexpect.TIMEOUT, pexpect.EOF,
'continue connecting (yes/no)?',
@ -98,22 +101,22 @@ class ArubaDeviceScanner(object):
'Connection refused',
'Connection timed out'], timeout=120)
if query == 1:
_LOGGER.error("Timeout")
_LOGGER.error('Timeout')
return
elif query == 2:
_LOGGER.error("Unexpected response from router")
_LOGGER.error('Unexpected response from router')
return
elif query == 3:
ssh.sendline('yes')
ssh.expect('password:')
elif query == 4:
_LOGGER.error("Host key Changed")
_LOGGER.error('Host key Changed')
return
elif query == 5:
_LOGGER.error("Connection refused by server")
_LOGGER.error('Connection refused by server')
return
elif query == 6:
_LOGGER.error("Connection timed out")
_LOGGER.error('Connection timed out')
return
ssh.sendline(self.password)
ssh.expect('#')

View File

@ -26,19 +26,20 @@ CONF_PROTOCOL = 'protocol'
CONF_MODE = 'mode'
CONF_SSH_KEY = 'ssh_key'
CONF_PUB_KEY = 'pub_key'
SECRET_GROUP = 'Password or SSH Key'
PLATFORM_SCHEMA = vol.All(
cv.has_at_least_one_key(CONF_PASSWORD, CONF_PUB_KEY, CONF_SSH_KEY),
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PROTOCOL, default='ssh'):
vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'):
vol.In(['router', 'ap']),
vol.Optional(CONF_SSH_KEY): cv.isfile,
vol.Optional(CONF_PUB_KEY): cv.isfile
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
}))
@ -101,6 +102,21 @@ class AsusWrtDeviceScanner(object):
self.protocol = config[CONF_PROTOCOL]
self.mode = config[CONF_MODE]
if self.protocol == 'ssh':
if self.ssh_key:
self.ssh_secret = {'ssh_key': self.ssh_key}
elif self.password:
self.ssh_secret = {'password': self.password}
else:
_LOGGER.error('No password or private key specified')
self.success_init = False
return
else:
if not self.password:
_LOGGER.error('No password specified')
self.success_init = False
return
self.lock = threading.Lock()
self.last_results = {}
@ -149,15 +165,17 @@ class AsusWrtDeviceScanner(object):
"""Retrieve data from ASUSWRT via the ssh protocol."""
from pexpect import pxssh, exceptions
ssh = pxssh.pxssh()
try:
ssh.login(self.host, self.username, **self.ssh_secret)
except exceptions.EOF as err:
_LOGGER.error('Connection refused. Is SSH enabled?')
return None
except pxssh.ExceptionPxssh as err:
_LOGGER.error('Unable to connect via SSH: %s', str(err))
return None
try:
ssh = pxssh.pxssh()
if self.ssh_key:
ssh.login(self.host, self.username, ssh_key=self.ssh_key)
elif self.password:
ssh.login(self.host, self.username, self.password)
else:
_LOGGER.error('No password or private key specified')
return None
ssh.sendline(_IP_NEIGH_CMD)
ssh.prompt()
neighbors = ssh.before.split(b'\n')[1:-1]
@ -178,9 +196,6 @@ class AsusWrtDeviceScanner(object):
except pxssh.ExceptionPxssh as exc:
_LOGGER.error('Unexpected response from router: %s', exc)
return None
except exceptions.EOF:
_LOGGER.error('Connection refused or no route to host')
return None
def telnet_connection(self):
"""Retrieve data from ASUSWRT via the telnet protocol."""

View File

@ -0,0 +1,161 @@
"""
Support for the Automatic platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.automatic/
"""
from datetime import timedelta
import logging
import re
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import (PLATFORM_SCHEMA,
ATTR_ATTRIBUTES)
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle, datetime as dt_util
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
CONF_CLIENT_ID = 'client_id'
CONF_SECRET = 'secret'
CONF_DEVICES = 'devices'
SCOPE = 'scope:location scope:vehicle:profile scope:user:profile scope:trip'
ATTR_ACCESS_TOKEN = 'access_token'
ATTR_EXPIRES_IN = 'expires_in'
ATTR_RESULTS = 'results'
ATTR_VEHICLE = 'vehicle'
ATTR_ENDED_AT = 'ended_at'
ATTR_END_LOCATION = 'end_location'
URL_AUTHORIZE = 'https://accounts.automatic.com/oauth/access_token/'
URL_VEHICLES = 'https://api.automatic.com/vehicle/'
URL_TRIPS = 'https://api.automatic.com/trip/'
_VEHICLE_ID_REGEX = re.compile(
(URL_VEHICLES + '(.*)?[/]$').replace('/', r'\/'))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string])
})
def setup_scanner(hass, config: dict, see):
"""Validate the configuration and return an Automatic scanner."""
try:
AutomaticDeviceScanner(config, see)
except requests.HTTPError as err:
_LOGGER.error(str(err))
return False
return True
class AutomaticDeviceScanner(object):
"""A class representing an Automatic device."""
def __init__(self, config: dict, see) -> None:
"""Initialize the automatic device scanner."""
self._devices = config.get(CONF_DEVICES, None)
self._access_token_payload = {
'username': config.get(CONF_USERNAME),
'password': config.get(CONF_PASSWORD),
'client_id': config.get(CONF_CLIENT_ID),
'client_secret': config.get(CONF_SECRET),
'grant_type': 'password',
'scope': SCOPE
}
self._headers = None
self._token_expires = dt_util.now()
self.last_results = {}
self.last_trips = {}
self.see = see
self.scan_devices()
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [item['id'] for item in self.last_results]
def get_device_name(self, device):
"""Get the device name from id."""
vehicle = [item['display_name'] for item in self.last_results
if item['id'] == device]
return vehicle[0]
def _update_headers(self):
"""Get the access token from automatic."""
if self._headers is None or self._token_expires <= dt_util.now():
resp = requests.post(
URL_AUTHORIZE,
data=self._access_token_payload)
resp.raise_for_status()
json = resp.json()
access_token = json[ATTR_ACCESS_TOKEN]
self._token_expires = dt_util.now() + timedelta(
seconds=json[ATTR_EXPIRES_IN])
self._headers = {
'Authorization': 'Bearer {}'.format(access_token)
}
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self) -> None:
"""Update the device info."""
_LOGGER.info('Updating devices')
self._update_headers()
response = requests.get(URL_VEHICLES, headers=self._headers)
response.raise_for_status()
self.last_results = [item for item in response.json()[ATTR_RESULTS]
if self._devices is None or item[
'display_name'] in self._devices]
response = requests.get(URL_TRIPS, headers=self._headers)
if response.status_code == 200:
for trip in response.json()[ATTR_RESULTS]:
vehicle_id = _VEHICLE_ID_REGEX.match(
trip[ATTR_VEHICLE]).group(1)
if vehicle_id not in self.last_trips:
self.last_trips[vehicle_id] = trip
elif self.last_trips[vehicle_id][ATTR_ENDED_AT] < trip[
ATTR_ENDED_AT]:
self.last_trips[vehicle_id] = trip
for vehicle in self.last_results:
dev_id = vehicle.get('id')
attrs = {
'fuel_level': vehicle.get('fuel_level_percent')
}
kwargs = {
'dev_id': dev_id,
'mac': dev_id,
ATTR_ATTRIBUTES: attrs
}
if dev_id in self.last_trips:
end_location = self.last_trips[dev_id][ATTR_END_LOCATION]
kwargs['gps'] = (end_location['lat'], end_location['lon'])
kwargs['gps_accuracy'] = end_location['accuracy_m']
self.see(**kwargs)

View File

@ -11,6 +11,7 @@ from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA,
load_config,
DEFAULT_TRACK_NEW
)
import homeassistant.util as util
import homeassistant.util.dt as dt_util
@ -58,10 +59,13 @@ def setup_scanner(hass, config, see):
def discover_ble_devices():
"""Discover Bluetooth LE devices."""
_LOGGER.debug("Discovering Bluetooth LE devices")
service = DiscoveryService()
devices = service.discover(duration)
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
try:
service = DiscoveryService()
devices = service.discover(duration)
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
except RuntimeError as error:
_LOGGER.error("Error during Bluetooth LE scan: %s", error)
devices = []
return devices
yaml_path = hass.config.path(YAML_DEVICES)
@ -85,7 +89,7 @@ def setup_scanner(hass, config, see):
# if track new devices is true discover new devices
# on every scan.
track_new = util.convert(config.get(CONF_TRACK_NEW), bool,
len(devs_to_track) == 0)
DEFAULT_TRACK_NEW)
if not devs_to_track and not track_new:
_LOGGER.warning("No Bluetooth LE devices to track!")
return False

View File

@ -2,15 +2,13 @@
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES,
CONF_TRACK_NEW,
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
load_config,
)
import homeassistant.util as util
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW)
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
@ -19,6 +17,10 @@ REQUIREMENTS = ['pybluez==0.22']
BT_PREFIX = 'BT_'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TRACK_NEW): cv.boolean
})
def setup_scanner(hass, config, see):
"""Setup the Bluetooth Scanner."""
@ -53,10 +55,8 @@ def setup_scanner(hass, config, see):
else:
devs_donot_track.append(device.mac[3:])
# if track new devices is true discover new devices
# on startup.
track_new = util.convert(config.get(CONF_TRACK_NEW), bool,
len(devs_to_track) == 0)
# if track new devices is true discover new devices on startup.
track_new = config.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
if track_new:
for dev in discover_devices():
if dev[0] not in devs_to_track and \
@ -64,16 +64,16 @@ def setup_scanner(hass, config, see):
devs_to_track.append(dev[0])
see_device(dev)
if not devs_to_track:
_LOGGER.warning("No bluetooth devices to track!")
return False
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
def update_bluetooth(now):
"""Lookup bluetooth device and update status."""
try:
if track_new:
for dev in discover_devices():
if dev[0] not in devs_to_track and \
dev[0] not in devs_donot_track:
devs_to_track.append(dev[0])
for mac in devs_to_track:
_LOGGER.debug("Scanning " + mac)
result = bluetooth.lookup_name(mac, timeout=5)

View File

@ -13,9 +13,10 @@ import json
from urllib.parse import unquote
import requests
import voluptuous as vol
from homeassistant.helpers import validate_config
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
@ -26,14 +27,14 @@ _LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Return a BT Home Hub 5 scanner if successful."""
if not validate_config(config,
{DOMAIN: [CONF_HOST]},
_LOGGER):
return None
scanner = BTHomeHub5DeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -44,7 +45,7 @@ class BTHomeHub5DeviceScanner(object):
def __init__(self, config):
"""Initialise the scanner."""
_LOGGER.info("Initialising BT Home Hub 5")
_LOGGER.info('Initialising BT Home Hub 5')
self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
@ -85,7 +86,7 @@ class BTHomeHub5DeviceScanner(object):
return False
with self.lock:
_LOGGER.info("Scanning")
_LOGGER.info('Scanning')
data = _get_homehub_data(self.url)

View File

@ -10,10 +10,11 @@ import threading
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -24,15 +25,16 @@ _LOGGER = logging.getLogger(__name__)
_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return a DD-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = DdWrtDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -107,7 +109,7 @@ class DdWrtDeviceScanner(object):
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info('Checking ARP')
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
@ -143,18 +145,18 @@ class DdWrtDeviceScanner(object):
auth=(self.username, self.password),
timeout=4)
except requests.exceptions.Timeout:
_LOGGER.exception("Connection to the router timed out")
_LOGGER.exception('Connection to the router timed out')
return
if response.status_code == 200:
return _parse_ddwrt_response(response.text)
elif response.status_code == 401:
# Authentication error
_LOGGER.exception(
"Failed to authenticate, "
"please check your username and password")
'Failed to authenticate, '
'please check your username and password')
return
else:
_LOGGER.error("Invalid response from ddwrt: %s", response)
_LOGGER.error('Invalid response from ddwrt: %s', response)
def _parse_ddwrt_response(data_str):

View File

@ -7,9 +7,11 @@ https://home-assistant.io/components/device_tracker.fritz/
import logging
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
REQUIREMENTS = ['https://github.com/deisi/fritzconnection/archive/'
@ -21,14 +23,17 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers.
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string,
vol.Optional(CONF_PASSWORD, default='admin'): cv.string,
vol.Optional(CONF_USERNAME, default=''): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return FritzBoxScanner."""
if not validate_config(config,
{DOMAIN: []},
_LOGGER):
return None
scanner = FritzBoxScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -40,22 +45,14 @@ class FritzBoxScanner(object):
def __init__(self, config):
"""Initialize the scanner."""
self.last_results = []
self.host = '169.254.1.1' # This IP is valid for all FRITZ!Box router.
self.username = 'admin'
self.password = ''
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.success_init = True
# pylint: disable=import-error
import fritzconnection as fc
# Check for user specific configuration
if CONF_HOST in config.keys():
self.host = config[CONF_HOST]
if CONF_USERNAME in config.keys():
self.username = config[CONF_USERNAME]
if CONF_PASSWORD in config.keys():
self.password = config[CONF_PASSWORD]
# Establish a connection to the FRITZ!Box.
try:
self.fritz_box = fc.FritzHosts(address=self.host,
@ -70,25 +67,25 @@ class FritzBoxScanner(object):
self.success_init = False
if self.success_init:
_LOGGER.info("Successfully connected to %s",
_LOGGER.info('Successfully connected to %s',
self.fritz_box.modelname)
self._update_info()
else:
_LOGGER.error("Failed to establish connection to FRITZ!Box "
"with IP: %s", self.host)
_LOGGER.error('Failed to establish connection to FRITZ!Box '
'with IP: %s', self.host)
def scan_devices(self):
"""Scan for new devices and return a list of found device ids."""
self._update_info()
active_hosts = []
for known_host in self.last_results:
if known_host["status"] == "1":
active_hosts.append(known_host["mac"])
if known_host['status'] == '1':
active_hosts.append(known_host['mac'])
return active_hosts
def get_device_name(self, mac):
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)["NewHostName"]
ret = self.fritz_box.get_specific_host_entry(mac)['NewHostName']
if ret == {}:
return None
return ret
@ -99,6 +96,6 @@ class FritzBoxScanner(object):
if not self.success_init:
return False
_LOGGER.info("Scanning")
_LOGGER.info('Scanning')
self.last_results = self.fritz_box.get_hosts_info()
return True

View File

@ -11,10 +11,11 @@ import threading
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -22,14 +23,15 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return a Luci scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = LuciDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -93,7 +95,7 @@ class LuciDeviceScanner(object):
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info('Checking ARP')
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
result = _req_json_rpc(url, 'net.arptable',
@ -117,19 +119,19 @@ def _req_json_rpc(url, method, *args, **kwargs):
try:
res = requests.post(url, data=data, timeout=5, **kwargs)
except requests.exceptions.Timeout:
_LOGGER.exception("Connection to the router timed out")
_LOGGER.exception('Connection to the router timed out')
return
if res.status_code == 200:
try:
result = res.json()
except ValueError:
# If json decoder could not parse the response
_LOGGER.exception("Failed to parse response from luci")
_LOGGER.exception('Failed to parse response from luci')
return
try:
return result['result']
except KeyError:
_LOGGER.exception("No result in response from luci")
_LOGGER.exception('No result in response from luci')
return
elif res.status_code == 401:
# Authentication error
@ -138,7 +140,7 @@ def _req_json_rpc(url, method, *args, **kwargs):
"please check your username and password")
return
else:
_LOGGER.error("Invalid response from luci: %s", res)
_LOGGER.error('Invalid response from luci: %s', res)
def _get_token(host, username, password):

View File

@ -9,13 +9,12 @@ import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_DEVICES
from homeassistant.components.mqtt import CONF_QOS
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
CONF_DEVICES = 'devices'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({

View File

@ -8,9 +8,12 @@ import logging
import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -19,6 +22,17 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3.3']
DEFAULT_HOST = 'routerlogin.net'
DEFAULT_USER = 'admin'
DEFAULT_PORT = 5000
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USER): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port
})
def get_scanner(hass, config):
"""Validate the configuration and returns a Netgear scanner."""
@ -28,10 +42,6 @@ def get_scanner(hass, config):
password = info.get(CONF_PASSWORD)
port = info.get(CONF_PORT)
if password is not None and host is None:
_LOGGER.warning('Found username or password but no host')
return None
scanner = NetgearDeviceScanner(host, username, password, port)
return scanner if scanner.success_init else None
@ -47,16 +57,9 @@ class NetgearDeviceScanner(object):
self.last_results = []
self.lock = threading.Lock()
if host is None:
self._api = pynetgear.Netgear()
elif username is None:
self._api = pynetgear.Netgear(password, host)
elif port is None:
self._api = pynetgear.Netgear(password, host, username)
else:
self._api = pynetgear.Netgear(password, host, username, port)
self._api = pynetgear.Netgear(password, host, username, port)
_LOGGER.info("Logging in")
_LOGGER.info('Logging in')
results = self._api.get_attached_devices()
@ -65,7 +68,7 @@ class NetgearDeviceScanner(object):
if self.success_init:
self.last_results = results
else:
_LOGGER.error("Failed to Login")
_LOGGER.error('Failed to Login')
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
@ -91,7 +94,7 @@ class NetgearDeviceScanner(object):
return
with self.lock:
_LOGGER.info("Scanning")
_LOGGER.info('Scanning')
results = self._api.get_attached_devices()

View File

@ -22,7 +22,8 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
# Interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
CONF_HOME_INTERVAL = 'home_interval'
CONF_EXCLUDE = 'exclude'
REQUIREMENTS = ['python-nmap==0.6.1']
@ -60,6 +61,7 @@ class NmapDeviceScanner(object):
self.last_results = []
self.hosts = config[CONF_HOSTS]
self.exclude = config.get(CONF_EXCLUDE, [])
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
@ -93,7 +95,8 @@ class NmapDeviceScanner(object):
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-F --host-timeout 5s"
options = "-F --host-timeout 5s "
exclude = "--exclude "
if self.home_interval:
boundary = dt_util.now() - self.home_interval
@ -102,10 +105,16 @@ class NmapDeviceScanner(object):
if last_results:
# Pylint is confused here.
# pylint: disable=no-member
options += " --exclude {}".format(",".join(device.ip for device
in last_results))
exclude_hosts = self.exclude + [device.ip for device
in last_results]
else:
exclude_hosts = self.exclude
else:
last_results = []
exclude_hosts = self.exclude
if exclude_hosts:
exclude = " --exclude {}".format(",".join(exclude_hosts))
options += exclude
try:
result = scanner.scan(hosts=self.hosts, arguments=options)

View File

@ -9,9 +9,14 @@ import logging
import threading
from collections import defaultdict
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
@ -22,20 +27,35 @@ BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
_LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
VALIDATE_WAYPOINTS = 'waypoints'
WAYPOINT_LAT_KEY = 'lat'
WAYPOINT_LON_KEY = 'lon'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
vol.Optional(CONF_WAYPOINT_IMPORT, default=True): cv.boolean,
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(cv.ensure_list, [cv.string])
})
def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
def validate_payload(payload, data_type):
"""Validate OwnTracks payload."""
@ -50,7 +70,7 @@ def setup_scanner(hass, config, see):
'because of missing or malformatted data: %s',
data_type, data)
return None
if data_type == VALIDATE_TRANSITION:
if data_type == VALIDATE_TRANSITION or data_type == VALIDATE_WAYPOINTS:
return data
if max_gps_accuracy is not None and \
convert(data.get('acc'), float, 0.0) > max_gps_accuracy:
@ -182,6 +202,26 @@ def setup_scanner(hass, config, see):
data['event'])
return
def owntracks_waypoint_update(topic, payload, qos):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(payload, VALIDATE_WAYPOINTS)
if not data:
return
wayps = data['waypoints']
_LOGGER.info("Got %d waypoints from %s", len(wayps), topic)
for wayp in wayps:
name = wayp['desc']
pretty_name = parse_topic(topic, True)[1] + ' - ' + name
lat = wayp[WAYPOINT_LAT_KEY]
lon = wayp[WAYPOINT_LON_KEY]
rad = wayp['rad']
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False, True)
zone_comp.add_zone(hass, pretty_name, zone)
def see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
@ -195,18 +235,39 @@ def setup_scanner(hass, config, see):
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
if waypoint_import:
if waypoint_whitelist is None:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format('+', '+'),
owntracks_waypoint_update, 1)
else:
for whitelist_user in waypoint_whitelist:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format(whitelist_user,
'+'),
owntracks_waypoint_update, 1)
return True
def parse_topic(topic, pretty=False):
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple."""
parts = topic.split('/')
dev_id_format = ''
if pretty:
dev_id_format = '{} {}'
else:
dev_id_format = '{}_{}'
dev_id = slugify(dev_id_format.format(parts[1], parts[2]))
host_name = parts[1]
return (host_name, dev_id)
def _parse_see_args(topic, data):
"""Parse the OwnTracks location parameters, into the format see expects."""
parts = topic.split('/')
dev_id = slugify('{}_{}'.format(parts[1], parts[2]))
host_name = parts[1]
(host_name, dev_id) = parse_topic(topic, False)
kwargs = {
'dev_id': dev_id,
'host_name': host_name,
'gps': (data['lat'], data['lon'])
'gps': (data[WAYPOINT_LAT_KEY], data[WAYPOINT_LON_KEY])
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']

View File

@ -9,9 +9,11 @@ import logging
import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -23,15 +25,16 @@ REQUIREMENTS = ['pysnmp==4.3.2']
CONF_COMMUNITY = "community"
CONF_BASEOID = "baseoid"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_COMMUNITY): cv.string,
vol.Required(CONF_BASEOID): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return an snmp scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER):
return None
scanner = SnmpScanner(config[DOMAIN])
return scanner if scanner.success_init else None

View File

@ -11,10 +11,11 @@ import threading
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -24,15 +25,16 @@ CONF_HTTP_ID = "http_id"
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_HTTP_ID): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and returns a Tomato scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME,
CONF_PASSWORD, CONF_HTTP_ID]},
_LOGGER):
return None
return TomatoDeviceScanner(config[DOMAIN])

View File

@ -277,8 +277,10 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/userRpm/LoginRpm.htm?Save=Save'.format(self.host)
# Generate md5 hash of password
password = hashlib.md5(self.password.encode('utf')).hexdigest()
# Generate md5 hash of password. The C7 appears to use the first 15
# characters of the password only, so we truncate to remove additional
# characters from being hashed.
password = hashlib.md5(self.password.encode('utf')[:15]).hexdigest()
credentials = '{}:{}'.format(self.username, password).encode('utf')
# Encode the credentials to be sent as a cookie.

View File

@ -11,10 +11,11 @@ import threading
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@ -22,14 +23,15 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return an ubus scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = UbusDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None

View File

@ -6,10 +6,11 @@ https://home-assistant.io/components/device_tracker.unifi/
"""
import logging
import urllib
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
# Unifi package doesn't list urllib3 as a requirement
REQUIREMENTS = ['urllib3', 'unifi==1.2.5']
@ -18,28 +19,24 @@ _LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default='localhost'): cv.string,
vol.Optional(CONF_SITE_ID, default='default'): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PORT, default=8443): cv.port
})
def get_scanner(hass, config):
"""Setup Unifi device_tracker."""
from unifi.controller import Controller
if not validate_config(config, {DOMAIN: [CONF_USERNAME,
CONF_PASSWORD]},
_LOGGER):
_LOGGER.error('Invalid configuration')
return False
this_config = config[DOMAIN]
host = this_config.get(CONF_HOST, 'localhost')
username = this_config.get(CONF_USERNAME)
password = this_config.get(CONF_PASSWORD)
site_id = this_config.get(CONF_SITE_ID, 'default')
try:
port = int(this_config.get(CONF_PORT, 8443))
except ValueError:
_LOGGER.error('Invalid port (must be numeric like 8443)')
return False
host = config[DOMAIN].get(CONF_HOST)
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)
site_id = config[DOMAIN].get(CONF_SITE_ID)
port = config[DOMAIN].get(CONF_PORT)
try:
ctrl = Controller(host, username, password, port, 'v4', site_id)

View File

@ -12,10 +12,11 @@ import threading
import requests
import voluptuous as vol
from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
from homeassistant.util import sanitize_filename
_LOGGER = logging.getLogger(__name__)
ATTR_SUBDIR = 'subdir'
ATTR_URL = 'url'
@ -30,15 +31,16 @@ SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({
vol.Optional(ATTR_SUBDIR): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DOWNLOAD_DIR): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-branches
def setup(hass, config):
"""Listen for download events to download files."""
logger = logging.getLogger(__name__)
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
return False
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
# If path is relative, we assume relative to HASS config dir
@ -46,8 +48,7 @@ def setup(hass, config):
download_path = hass.config.path(download_path)
if not os.path.isdir(download_path):
logger.error(
_LOGGER.error(
"Download path %s does not exist. File Downloader not active.",
download_path)
@ -113,16 +114,16 @@ def setup(hass, config):
final_path = "{}_{}.{}".format(path, tries, ext)
logger.info("%s -> %s", url, final_path)
_LOGGER.info("%s -> %s", url, final_path)
with open(final_path, 'wb') as fil:
for chunk in req.iter_content(1024):
fil.write(chunk)
logger.info("Downloading of %s done", url)
_LOGGER.info("Downloading of %s done", url)
except requests.exceptions.ConnectionError:
logger.exception("ConnectionError occured for %s", url)
_LOGGER.exception("ConnectionError occured for %s", url)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/ecobee/
import logging
import os
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@ -15,14 +16,23 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle
DOMAIN = "ecobee"
NETWORK = None
CONF_HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
CONF_HOLD_TEMP = 'hold_temp'
DOMAIN = 'ecobee'
ECOBEE_CONFIG_FILE = 'ecobee.conf'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
NETWORK = None
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_KEY): cv.string,
@ -30,14 +40,6 @@ CONFIG_SCHEMA = vol.Schema({
})
}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {}
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
def request_configuration(network, hass, config):
"""Request configuration steps from the user."""
@ -97,7 +99,7 @@ class EcobeeData(object):
def update(self):
"""Get the latest data from pyecobee."""
self.ecobee.update()
_LOGGER.info("ecobee data updated successfully.")
_LOGGER.info("Ecobee data updated successfully")
def setup(hass, config):
@ -116,9 +118,6 @@ def setup(hass, config):
# Create ecobee.conf if it doesn't exist
if not os.path.isfile(hass.config.path(ECOBEE_CONFIG_FILE)):
if config[DOMAIN].get(CONF_API_KEY) is None:
_LOGGER.error("No ecobee api_key found in config.")
return
jsonconfig = {"API_KEY": config[DOMAIN].get(CONF_API_KEY)}
config_from_file(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)

View File

@ -44,7 +44,7 @@ DEFAULT_LISTEN_PORT = 8300
DEFAULT_OFF_MAPS_TO_ON_DOMAINS = ['script', 'scene']
DEFAULT_EXPOSE_BY_DEFAULT = True
DEFAULT_EXPOSED_DOMAINS = [
'switch', 'light', 'group', 'input_boolean', 'media_player'
'switch', 'light', 'group', 'input_boolean', 'media_player', 'fan'
]
HUE_API_STATE_ON = 'on'

View File

@ -39,6 +39,7 @@ SERVICE_OSCILLATE = 'oscillate'
SPEED_OFF = 'off'
SPEED_LOW = 'low'
SPEED_MED = 'med'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
ATTR_SPEED = 'speed'

View File

@ -1,5 +1,5 @@
"""
Demo garage door platform that has a fake fan.
Demo fan platform that has a fake fan.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
@ -19,7 +19,7 @@ DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup demo garage door platform."""
"""Setup demo fan platform."""
add_devices_callback([
DemoFan(hass, FAN_NAME, STATE_OFF),
])

View File

@ -0,0 +1,276 @@
"""
Support for MQTT fans.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/fan.mqtt/
"""
import logging
from functools import partial
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (CONF_NAME, CONF_OPTIMISTIC, CONF_STATE,
STATE_ON, STATE_OFF)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
SPEED_HIGH, FanEntity,
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
SPEED_OFF, ATTR_SPEED)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["mqtt"]
CONF_STATE_VALUE_TEMPLATE = "state_value_template"
CONF_SPEED_STATE_TOPIC = "speed_state_topic"
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
CONF_SPEED_VALUE_TEMPLATE = "speed_value_template"
CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"
CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"
CONF_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"
CONF_PAYLOAD_ON = "payload_on"
CONF_PAYLOAD_OFF = "payload_off"
CONF_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"
CONF_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"
CONF_PAYLOAD_LOW_SPEED = "payload_low_speed"
CONF_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"
CONF_PAYLOAD_HIGH_SPEED = "payload_high_speed"
CONF_SPEED_LIST = "speeds"
DEFAULT_NAME = "MQTT Fan"
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_OPTIMISTIC = False
OSCILLATE_ON_PAYLOAD = "oscillate_on"
OSCILLATE_OFF_PAYLOAD = "oscillate_off"
OSCILLATION = "oscillation"
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_OSCILLATION_ON,
default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OSCILLATION_OFF,
default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string,
vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MED): cv.string,
vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string,
vol.Optional(CONF_SPEED_LIST,
default=[SPEED_OFF, SPEED_LOW,
SPEED_MED, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup MQTT fan platform."""
add_devices_callback([MqttFan(
hass,
config[CONF_NAME],
{
key: config.get(key) for key in (
CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_SPEED_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
CONF_OSCILLATION_COMMAND_TOPIC,
)
},
{
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE),
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE)
},
config[CONF_QOS],
config[CONF_RETAIN],
{
STATE_ON: config[CONF_PAYLOAD_ON],
STATE_OFF: config[CONF_PAYLOAD_OFF],
OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON],
OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF],
SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED],
SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED],
SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED],
},
config[CONF_SPEED_LIST],
config[CONF_OPTIMISTIC],
)])
# pylint: disable=too-many-instance-attributes
class MqttFan(FanEntity):
"""A MQTT fan component."""
# pylint: disable=too-many-arguments
def __init__(self, hass, name, topic, templates, qos, retain, payload,
speed_list, optimistic):
"""Initialize MQTT fan."""
self._hass = hass
self._name = name
self._topic = topic
self._qos = qos
self._retain = retain
self._payload = payload
self._speed_list = speed_list
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
self._optimistic_oscillation = (optimistic or
topic[CONF_OSCILLATION_STATE_TOPIC]
is None)
self._optimistic_speed = (optimistic or
topic[CONF_SPEED_STATE_TOPIC] is None)
self._state = False
self._supported_features = 0
self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC]
is not None and SUPPORT_OSCILLATE)
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED)
templates = {key: ((lambda value: value) if tpl is None else
partial(render_with_possible_json_value, hass, tpl))
for key, tpl in templates.items()}
def state_received(topic, payload, qos):
"""A new MQTT message has been received."""
payload = templates[CONF_STATE](payload)
if payload == self._payload[STATE_ON]:
self._state = True
elif payload == self._payload[STATE_OFF]:
self._state = False
self.update_ha_state()
if self._topic[CONF_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
state_received, self._qos)
def speed_received(topic, payload, qos):
"""A new MQTT message for the speed has been received."""
payload = templates[ATTR_SPEED](payload)
if payload == self._payload[SPEED_LOW]:
self._speed = SPEED_LOW
elif payload == self._payload[SPEED_MEDIUM]:
self._speed = SPEED_MED
elif payload == self._payload[SPEED_HIGH]:
self._speed = SPEED_HIGH
self.update_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
speed_received, self._qos)
self._speed = SPEED_OFF
elif self._topic[CONF_SPEED_COMMAND_TOPIC] is not None:
self._speed = SPEED_OFF
else:
self._speed = SPEED_OFF
def oscillation_received(topic, payload, qos):
"""A new MQTT message has been received."""
payload = templates[OSCILLATION](payload)
if payload == self._payload[OSCILLATE_ON_PAYLOAD]:
self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
self._oscillation = False
self.update_ha_state()
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass,
self._topic[CONF_OSCILLATION_STATE_TOPIC],
oscillation_received, self._qos)
self._oscillation = False
if self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None:
self._oscillation = False
else:
self._oscillation = False
@property
def should_poll(self):
"""No polling needed for a MQTT fan."""
return False
@property
def assumed_state(self):
"""Return true if we do optimistic updates."""
return self._optimistic
@property
def is_on(self):
"""Return true if device is on."""
return self._state
@property
def name(self) -> str:
"""Get entity name."""
return self._name
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return self._speed_list
@property
def supported_features(self) -> int:
"""Flag supported features."""
return self._supported_features
@property
def speed(self):
"""Return the current speed."""
return self._speed
@property
def oscillating(self):
"""Return the oscillation state."""
return self._oscillation
def turn_on(self, speed: str=SPEED_MED) -> None:
"""Turn on the entity."""
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_ON], self._qos, self._retain)
self.set_speed(speed)
def turn_off(self) -> None:
"""Turn off the entity."""
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_OFF], self._qos, self._retain)
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
if self._topic[CONF_SPEED_COMMAND_TOPIC] is not None:
mqtt_payload = SPEED_OFF
if speed == SPEED_LOW:
mqtt_payload = self._payload[SPEED_LOW]
elif speed == SPEED_MED:
mqtt_payload = self._payload[SPEED_MEDIUM]
elif speed == SPEED_HIGH:
mqtt_payload = self._payload[SPEED_HIGH]
else:
mqtt_payload = speed
self._speed = speed
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain)
self.update_ha_state()
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
if self._topic[CONF_SPEED_COMMAND_TOPIC] is not None:
self._oscillation = oscillating
mqtt.publish(self._hass,
self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
self._oscillation, self._qos, self._retain)
self.update_ha_state()

View File

@ -1,5 +1,5 @@
"""
Support for RSS/Atom feed.
Support for RSS/Atom feeds.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/feedreader/
@ -9,22 +9,39 @@ from logging import getLogger
from os.path import exists
from threading import Lock
import pickle
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['feedparser==5.2.1']
_LOGGER = getLogger(__name__)
DOMAIN = "feedreader"
EVENT_FEEDREADER = "feedreader"
# pylint: disable=no-value-for-parameter
CONF_URLS = 'urls'
DOMAIN = 'feedreader'
EVENT_FEEDREADER = 'feedreader'
MAX_ENTRIES = 20
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
'urls': [vol.Url()],
vol.Required(CONF_URLS): vol.All(cv.ensure_list, [cv.url]),
}
}, extra=vol.ALLOW_EXTRA)
MAX_ENTRIES = 20
def setup(hass, config):
"""Setup the feedreader component."""
urls = config.get(DOMAIN)[CONF_URLS]
data_file = hass.config.path("{}.pickle".format(DOMAIN))
storage = StoredData(data_file)
feeds = [FeedManager(url, hass, storage) for url in urls]
return len(feeds) > 0
# pylint: disable=too-few-public-methods
@ -83,9 +100,8 @@ class FeedManager(object):
def _update_and_fire_entry(self, entry):
"""Update last_entry_timestamp and fire entry."""
# We are lucky, `published_parsed` data available,
# let's make use of it to publish only new available
# entries since the last run
# We are lucky, `published_parsed` data available, let's make use of
# it to publish only new available entries since the last run
if 'published_parsed' in entry.keys():
self._has_published_parsed = True
self._last_entry_timestamp = max(entry.published_parsed,
@ -163,12 +179,3 @@ class StoredData(object):
_LOGGER.error('Error saving pickled data to %s',
self._data_file)
self._cache_outdated = True
def setup(hass, config):
"""Setup the feedreader component."""
urls = config.get(DOMAIN)['urls']
data_file = hass.config.path("{}.pickle".format(DOMAIN))
storage = StoredData(data_file)
feeds = [FeedManager(url, hass, storage) for url in urls]
return len(feeds) > 0

View File

@ -7,42 +7,51 @@ https://home-assistant.io/components/foursquare/
import logging
import os
import json
import requests
import requests
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
from homeassistant.components.http import HomeAssistantView
DOMAIN = "foursquare"
SERVICE_CHECKIN = "checkin"
EVENT_PUSH = "foursquare.push"
EVENT_CHECKIN = "foursquare.checkin"
CHECKIN_SERVICE_SCHEMA = vol.Schema({
vol.Required("venueId"): cv.string,
vol.Optional("eventId"): cv.string,
vol.Optional("shout"): cv.string,
vol.Optional("mentions"): cv.string,
vol.Optional("broadcast"): cv.string,
vol.Optional("ll"): cv.string,
vol.Optional("llAcc"): cv.string,
vol.Optional("alt"): cv.string,
vol.Optional("altAcc"): cv.string,
})
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["http"]
CONF_PUSH_SECRET = 'push_secret'
DEPENDENCIES = ['http']
DOMAIN = 'foursquare'
EVENT_CHECKIN = 'foursquare.checkin'
EVENT_PUSH = 'foursquare.push'
SERVICE_CHECKIN = 'checkin'
CHECKIN_SERVICE_SCHEMA = vol.Schema({
vol.Optional('alt'): cv.string,
vol.Optional('altAcc'): cv.string,
vol.Optional('broadcast'): cv.string,
vol.Optional('eventId'): cv.string,
vol.Optional('ll'): cv.string,
vol.Optional('llAcc'): cv.string,
vol.Optional('mentions'): cv.string,
vol.Optional('shout'): cv.string,
vol.Required('venueId'): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Required(CONF_PUSH_SECRET): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup the Foursquare component."""
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), "services.yaml"))
os.path.join(os.path.dirname(__file__), 'services.yaml'))
config = config[DOMAIN]
@ -51,7 +60,7 @@ def setup(hass, config):
url = ("https://api.foursquare.com/v2/checkins/add"
"?oauth_token={}"
"&v=20160802"
"&m=swarm").format(config["access_token"])
"&m=swarm").format(config[CONF_ACCESS_TOKEN])
response = requests.post(url, data=call.data, timeout=10)
if response.status_code not in (200, 201):
@ -62,12 +71,12 @@ def setup(hass, config):
hass.bus.fire(EVENT_CHECKIN, response.text)
# Register our service with Home Assistant.
hass.services.register(DOMAIN, "checkin", checkin_user,
hass.services.register(DOMAIN, 'checkin', checkin_user,
descriptions[DOMAIN][SERVICE_CHECKIN],
schema=CHECKIN_SERVICE_SCHEMA)
hass.wsgi.register_view(FoursquarePushReceiver(hass,
config["push_secret"]))
hass.wsgi.register_view(FoursquarePushReceiver(
hass, config[CONF_PUSH_SECRET]))
return True

View File

@ -2,7 +2,7 @@
FINGERPRINTS = {
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c",
"frontend.html": "610cc799225ede933a9894b64bb35717",
"frontend.html": "20defe06c11b2fa2f076dc92b6c3b0dd",
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",

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