Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4748e7f7e9 | |||
| e8f8ea080b | |||
| b8251b084a | |||
| 54a17f5d98 | |||
| 8438001942 | |||
| de150ecbc9 | |||
| d466bae244 | |||
| e87da765c5 | |||
| ba28208106 | |||
| 545329174d | |||
| 5881f6000e | |||
| 3411c4c7c3 | |||
| 5bf66cae1f | |||
| 53c8115f82 | |||
| 911231afc1 | |||
| 44f5a66b66 | |||
| ee6c83f569 | |||
| fb0232429e | |||
| 1cace5782c | |||
| 02848b3949 | |||
| e8ad76c816 | |||
| 267cda447e | |||
| 94e3986d54 | |||
| 24aa3b3c97 | |||
| b3d2db45de | |||
| 1af5d4c8b8 | |||
| 4d41c5cd0f | |||
| e632a47772 | |||
| 32c234ffcc | |||
| 5995f2438e | |||
| 35b388edce | |||
| 91028cbc13 | |||
| 3668afe306 | |||
| 47864fc7d7 | |||
| e88e6d1030 | |||
| d7b757fb97 | |||
| 6a837f3aad | |||
| 165871d48a | |||
| fb719f530a | |||
| d53d8f5ea9 | |||
| 7aafa309c9 | |||
| abff2f2b36 | |||
| f55095df83 | |||
| 9d4ccb1f49 | |||
| 9eacde0005 | |||
| 22870d424a | |||
| e00f9339d1 | |||
| d8db881e9a | |||
| fa8ed4de41 | |||
| 79fa9963da | |||
| d06a3c9145 | |||
| c1139a9fda | |||
| 9ade87013e | |||
| 478c82c34c | |||
| 85baebb23b | |||
| 88d62bd935 | |||
| 9530c7366b | |||
| 26eba4cb1a | |||
| c06fe51122 | |||
| f595c8715c | |||
| 6e6b2ae3f4 | |||
| d903661577 | |||
| a5faa851e8 | |||
| 5ec6eaf7d0 | |||
| 73036f4725 | |||
| 17a2cac7e1 | |||
| e0a6d7941c | |||
| 4638696f8c | |||
| 428db4a644 | |||
| ea1e4ea215 | |||
| 6b787ee01e | |||
| 6be20883f0 | |||
| 95ea0c02b9 | |||
| 5059d8dde9 | |||
| 3bbd909b20 | |||
| 909b5ffa5b | |||
| e324885ff6 | |||
| 8afed2cafa | |||
| 6bbe3483d9 | |||
| 9c600012a1 | |||
| aed59aea7d | |||
| 09d52820dd | |||
| 48c1631178 | |||
| 1170b2897a | |||
| 5144547b70 | |||
| e460d8f637 | |||
| 7bab4055a5 | |||
| 892f6a706a | |||
| 59cd92cb4d | |||
| 98bdcd3405 | |||
| a569ee787d | |||
| ad52816595 | |||
| 29870b301e | |||
| b4c8d10dbc | |||
| cd67368bb7 | |||
| e9813b219e | |||
| 641d531be3 | |||
| 74980d9563 | |||
| 0f37d8d8eb | |||
| 22362727e4 | |||
| 48e6befc13 | |||
| 4de9717256 | |||
| b02b008fe5 | |||
| 3c615e2319 | |||
| 8467d07a3d | |||
| 6f45906eda | |||
| 34ba4d3e09 | |||
| 3b1c0a7502 | |||
| 6a2f0fc456 | |||
| 2aab77a486 | |||
| 02960ec482 | |||
| db7f6a328f | |||
| 290ec9b4ac | |||
| 0198ba4eac | |||
| 09b53a0d55 | |||
| 269e97c6de | |||
| 68ef55a982 | |||
| 91a3522100 | |||
| fe7f797ad9 | |||
| 70888532f8 | |||
| 32e1e046ae | |||
| 601395bc12 | |||
| a08ac85971 | |||
| 5dc63c17c8 | |||
| 795121d5a8 | |||
| 6ae4e5cb6c | |||
| b5ae005acc | |||
| fb9627deda | |||
| 6fdd7f5350 | |||
| a7a662d224 | |||
| 3bbcf4d8b1 | |||
| a0a509ceea | |||
| 40c71b5d96 | |||
| 81628b01c2 | |||
| 28e939afcf | |||
| e5ef548f10 | |||
| dedc4a129c | |||
| 95cc672161 | |||
| 6a84b82663 | |||
| 000832a82c | |||
| 0907eea442 | |||
| b8b1fadc6d | |||
| 24d3cbdfe9 | |||
| 451f0cb3f1 | |||
| b4df9b30d8 | |||
| 27ee4c555a | |||
| 0c310c166a | |||
| 06df31bb5b | |||
| 177d8ef4ef | |||
| a50205aedb | |||
| 9226cef61e | |||
| 29f2dd2ce9 | |||
| ed7a227035 | |||
| d8ad4e1584 | |||
| db7abc1cfe | |||
| a571271c39 | |||
| 9e38255c26 | |||
| 586e47d08d | |||
| 78f0e681ed | |||
| afdd734b44 | |||
| 6b6d34ba51 | |||
| dadcf92290 | |||
| dcfc1ef361 | |||
| 83f1272662 | |||
| d2dfe04ec9 | |||
| 24d412938e | |||
| 748d7f4ecb | |||
| 1094de7ad9 | |||
| 831d96995d | |||
| 60f540315a | |||
| 0bcfb65a30 | |||
| 5036bb0bc6 | |||
| 87e332c777 | |||
| 88e600827e | |||
| e045a6f0c3 | |||
| c792dd4126 | |||
| 571cbdf40c | |||
| 4b12ea04d6 | |||
| 5f664acb4f | |||
| e5b6592870 | |||
| 705b3571f4 | |||
| dfee443312 | |||
| 0943cc78cd | |||
| 8816b62d9c | |||
| eadd07dc7d | |||
| 12e2c38436 | |||
| 4864a67dcd | |||
| 70fe7f747a | |||
| 7f27cc5468 | |||
| 586208b3ed | |||
| 2430acf3ad | |||
| 5a25c74276 | |||
| 3fa1963345 | |||
| d9ecc4af64 | |||
| 62ba0fa7a2 | |||
| ed872f6054 | |||
| 47a9313fdb | |||
| ca73295dd1 | |||
| 2ca3541eac | |||
| 95b7a8c4b9 | |||
| 185ae50e24 | |||
| e6b7511e7d | |||
| 1ada7d6211 | |||
| 2bea5a484f | |||
| 75e6ed87d6 |
@@ -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
|
||||
|
||||
+4
-1
@@ -96,4 +96,7 @@ virtualization/vagrant/.vagrant
|
||||
virtualization/vagrant/config
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
# Built docs
|
||||
docs/build
|
||||
+230
@@ -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."
|
||||
Vendored
+281
@@ -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
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,7 @@
|
||||
.. _bootstrap_module:
|
||||
|
||||
:mod:`homeassistant.bootstrap`
|
||||
-------------------------
|
||||
|
||||
.. automodule:: homeassistant.bootstrap
|
||||
:members:
|
||||
@@ -0,0 +1,18 @@
|
||||
.. _core_module:
|
||||
|
||||
:mod:`homeassistant.core`
|
||||
-------------------------
|
||||
|
||||
.. automodule:: homeassistant.core
|
||||
|
||||
.. autoclass:: Config
|
||||
:members:
|
||||
|
||||
.. autoclass:: EventBus
|
||||
:members:
|
||||
|
||||
.. autoclass:: StateMachine
|
||||
:members:
|
||||
|
||||
.. autoclass:: ServiceRegistry
|
||||
:members:
|
||||
@@ -0,0 +1,10 @@
|
||||
.. _components_device_tracker_module:
|
||||
|
||||
:mod:`homeassistant.components.device_tracker`
|
||||
----------------------------------------------
|
||||
|
||||
.. automodule:: homeassistant.components.device_tracker
|
||||
:members:
|
||||
|
||||
.. autoclass:: Device
|
||||
:members:
|
||||
@@ -0,0 +1,12 @@
|
||||
.. _helpers_entity_module:
|
||||
|
||||
:mod:`homeassistant.helpers.entity`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: homeassistant.helpers.entity
|
||||
|
||||
.. autoclass:: Entity
|
||||
:members:
|
||||
|
||||
.. autoclass:: ToggleEntity
|
||||
:members:
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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/
|
||||
+75
-62
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)])
|
||||
|
||||
|
||||
@@ -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)]),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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('#')
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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),
|
||||
])
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user