pax_global_header00006660000000000000000000000064147457564400014532gustar00rootroot0000000000000052 comment=23ee7f646e59a8e951d7de025e75d86ae9841b79 python-fritzhome-0.6.14/000077500000000000000000000000001474575644000151505ustar00rootroot00000000000000python-fritzhome-0.6.14/.codeclimate.yml000066400000000000000000000003331474575644000202210ustar00rootroot00000000000000version: "2" checks: argument-count: config: threshold: 5 method-complexity: config: threshold: 13 method-count: config: threshold: 30 method-lines: config: threshold: 45 python-fritzhome-0.6.14/.devcontainer/000077500000000000000000000000001474575644000177075ustar00rootroot00000000000000python-fritzhome-0.6.14/.devcontainer/Dockerfile.dev000066400000000000000000000012211474575644000224520ustar00rootroot00000000000000FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9 SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ git \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src RUN pip3 install \ black \ coverage \ docutils \ flake8 \ future \ pre-commit \ pytest \ pytest-cov \ python-coveralls \ rstcheck WORKDIR /workspaces # Set the default shell to bash instead of sh ENV SHELL /bin/bash python-fritzhome-0.6.14/.devcontainer/devcontainer.json000066400000000000000000000020331474575644000232610ustar00rootroot00000000000000{ "name": "Python Fritz!Smarthome Dev", "context": "..", "dockerFile": "Dockerfile.dev", "postCreateCommand": "pre-commit install", "containerEnv": { "DEVCONTAINER": "1" }, "runArgs": ["-e", "GIT_EDITOR=code --wait"], "extensions": [ "ms-python.vscode-pylance", "visualstudioexptteam.vscodeintellicode", "esbenp.prettier-vscode", "lextudio.restructuredtext", "trond-snekvik.simple-rst" ], // Please keep this file in sync with settings in .vscode/settings.default.json "settings": { "python.pythonPath": "/usr/local/bin/python", "python.linting.flake8Enabled": true, "python.linting.enabled": true, "python.formatting.provider": "black", "python.testing.pytestEnabled": false, "editor.formatOnPaste": false, "editor.formatOnSave": true, "editor.formatOnType": true, "files.trimTrailingWhitespace": true, "terminal.integrated.profiles.linux": { "zsh": { "path": "/usr/bin/zsh" } }, "terminal.integrated.defaultProfile.linux": "zsh" } } python-fritzhome-0.6.14/.github/000077500000000000000000000000001474575644000165105ustar00rootroot00000000000000python-fritzhome-0.6.14/.github/workflows/000077500000000000000000000000001474575644000205455ustar00rootroot00000000000000python-fritzhome-0.6.14/.github/workflows/publish.yml000066400000000000000000000014261474575644000227410ustar00rootroot00000000000000name: Publish Python Package on PYPI on: push jobs: deploy: name: Publish to Pypi runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v5 with: python-version: 3.9 - name: Install pypa/build run: >- python -m pip install build --user - name: Build a binary wheel and a source tarball run: >- python -m build --sdist --wheel --outdir dist/ . - name: Publish distribution to PyPI if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} python-fritzhome-0.6.14/.github/workflows/test.yml000066400000000000000000000041631474575644000222530ustar00rootroot00000000000000name: Test on: [push, pull_request] jobs: tests: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: include: - python-version: "3.7" os: ubuntu-20.04 - python-version: "3.8" os: ubuntu-20.04 - python-version: "3.9" os: ubuntu-latest - python-version: "3.10" os: ubuntu-latest - python-version: "3.11" os: ubuntu-latest - python-version: "3.12" os: ubuntu-latest - python-version: "3.13" os: ubuntu-latest - python-version: "pypy3.9" os: windows-latest steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies (Windows) if: runner.os == 'Windows' run: pip install pytest pytest-cov python-coveralls coverage flake8 pydocstyle setuptools cryptography<44 - name: Install dependencies (Ubuntu) if: runner.os == 'Linux' run: pip install pytest pytest-cov python-coveralls coverage flake8 pydocstyle setuptools cryptography - name: Lint with flake8 run: | flake8 pyfritzhome --count --show-source --statistics - name: Run pydocstyle for analysising with Python docstring conventions. run: pydocstyle - name: Setup run: python setup.py install - name: Test run: pytest --cov=pyfritzhome --cov-report=lcov - name: Coveralls Parallel uses: coverallsapp/github-action@main with: github-token: ${{ secrets.github_token }} flag-name: run-${{ matrix.test_number }} parallel: true path-to-lcov: coverage.lcov finish: needs: tests runs-on: ubuntu-22.04 steps: - name: Coveralls Finished uses: coverallsapp/github-action@main with: github-token: ${{ secrets.github_token }} parallel-finished: true python-fritzhome-0.6.14/.gitignore000066400000000000000000000001661474575644000171430ustar00rootroot00000000000000*~ *.pyc *.swp build/ dist/ *.egg-info/ .vscode/settings.json # Coverage Information .coverage .noseids coverage.xml python-fritzhome-0.6.14/.hound.yml000066400000000000000000000000611474575644000170630ustar00rootroot00000000000000flake8: enabled: true config_file: setup.cfg python-fritzhome-0.6.14/.pre-commit-config.yaml000066400000000000000000000006401474575644000214310ustar00rootroot00000000000000repos: - repo: local hooks: - id: black name: black entry: black language: system types: [python] require_serial: true - id: flake8 name: flake8 entry: flake8 language: system types: [python] require_serial: true - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.2.1 hooks: - id: prettier python-fritzhome-0.6.14/.vscode/000077500000000000000000000000001474575644000165115ustar00rootroot00000000000000python-fritzhome-0.6.14/.vscode/settings.default.json000066400000000000000000000001751474575644000226720ustar00rootroot00000000000000{ "python.linting.flake8Enabled": true, "python.formatting.provider": "black", "python.testing.pytestEnabled": false } python-fritzhome-0.6.14/.vscode/tasks.json000066400000000000000000000012331474575644000205300ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "Run setup", "type": "shell", "command": "python setup.py install --force", "group": { "kind": "test", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, { "label": "Run tests", "type": "shell", "command": "pytest --cov=pyfritzhome --cov-report term-missing", "group": { "kind": "test", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] } ] } python-fritzhome-0.6.14/LICENSE000066400000000000000000000020551474575644000161570ustar00rootroot00000000000000MIT License Copyright (c) 2018 Heiko Thiery Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-fritzhome-0.6.14/README.rst000066400000000000000000000131341474575644000166410ustar00rootroot00000000000000Python Library to access AVM Fritz!Box homeautomation ===================================================== |BuildStatus| |PypiVersion| |PyPiPythonVersions| |Coveralls| |CodeClimate| Tested Devices -------------- * `FRITZ!Box 6490 Cable`_ * `FRITZ!Box 7590`_ * `FRITZ!DECT 200`_ * `FRITZ!DECT 302`_ * `FRITZ!DECT 440`_ * `FRITZ!DECT 500`_ * `Comet DECT`_ * `Panasonic KX-HNS101` * `Magenta Smarthome Tür-/Fensterkontakt optisch`_ * `RADEMACHER RolloTron DECT 1213`_ * `Magenta Smarthome Zwischenstecker außen`_ * `Magenta SmartHome LED-Lampe E27 Warmweiß` * `Magenta SmartHome Zwischenstecker innen` * `Magenta Smarthome Tür-/Fensterkontakt magnetisch` fritzhome CLI tool ------------------ You have to add a user with the rights to access the smarthome actors. In the fritzbox webinterface under "System -> FRITZ!Box-Benutzer" you can add a new user. .. code:: shell $ fritzhome -f fritz.box -u smarthome -p smarthome list ############################## name=Fenster Badezimmer ain=11934 0154799-1 id=2000 productname=HAN-FUN manufacturer=0x0feb present=True lock=None devicelock=None is_group=False Alert: alert=True ############################## name=Thermostat Badezimmer ain=11959 0171328 id=16 productname=Comet DECT manufacturer=AVM present=True lock=False devicelock=False is_group=False Temperature: temperature=19 offset=-3 Thermostat: battery_low=False battery_level=80 actual=19.0 target=19.0 comfort=22.0 eco=19.0 window=False window_until=0 boost=None boost_until=None adaptive_heating_running=None summer=False holiday=False ############################## name=Wohnzimmer Couch ain=09995 0523646 id=17 productname=FRITZ!DECT 301 manufacturer=AVM present=True lock=False devicelock=False is_group=False Temperature: temperature=20.5 Thermostat: battery_low=False battery_level=80 actual=20.5 target=21.5 comfort=21.5 eco=17.5 window=False summer=False holiday=False ############################## name=Wohnzimmer Tisch ain=09995 0517495 id=18 productname=FRITZ!DECT 301 manufacturer=AVM present=True lock=False devicelock=False is_group=False Temperature: temperature=21.0 Thermostat: battery_low=False battery_level=80 actual=21.0 target=21.5 comfort=21.5 eco=17.5 window=False summer=False holiday=False ############################## name=Schalter WC Heizung ain=08761 0402392 id=21 productname=FRITZ!DECT 200 manufacturer=AVM present=True lock=True devicelock=False is_group=False Switch: switch_state=False Powermeter: power=0 energy=436529 voltage=231.0 Temperature: temperature=22 offset=3 ############################## name=Wohnzimmer ain=grp303E4F-3F7D9BE07 id=900 productname= manufacturer=AVM present=True lock=False devicelock=False is_group=True group_members=['17', '18'] Thermostat: battery_low=None battery_level=None actual=None target=21.5 comfort=21.5 eco=17.5 window=None summer=None holiday=None Fritzbox User ------------- Add a new user: System -> FRITZ!Box-Benutzer .. image:: doc/fritzbox_user_overview.png .. image:: doc/fritzbox_user_smarthome.png References ---------- - https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AHA-HTTP-Interface.pdf - https://github.com/DerMitch/fritzbox-smarthome .. |BuildStatus| image:: https://github.com/hthiery/python-fritzhome/actions/workflows/test.yml/badge.svg :target: https://github.com/hthiery/python-fritzhome/actions/workflows/test.yml .. |PyPiVersion| image:: https://badge.fury.io/py/pyfritzhome.svg :target: http://badge.fury.io/py/pyfritzhome .. |PyPiPythonVersions| image:: https://img.shields.io/pypi/pyversions/pyfritzhome.svg :alt: Python versions :target: http://badge.fury.io/py/pyfritzhome .. |Coveralls| image:: https://coveralls.io/repos/github/hthiery/python-fritzhome/badge.svg?branch=master :target: https://coveralls.io/github/hthiery/python-fritzhome?branch=master .. |CodeClimate| image:: https://api.codeclimate.com/v1/badges/fc83491ef0ae81080882/maintainability :target: https://codeclimate.com/github/hthiery/python-fritzhome/maintainability :alt: Maintainability .. _Comet DECT: https://www.eurotronic.org/produkte/comet-dect.html .. _FRITZ!DECT 200: https://avm.de/produkte/fritzdect/fritzdect-200/ .. _FRITZ!DECT 302: https://avm.de/produkte/fritzdect/fritzdect-302/ .. _FRITZ!DECT 440: https://avm.de/produkte/fritzdect/fritzdect-440/ .. _FRITZ!DECT 500: https://avm.de/produkte/fritzdect/fritzdect-500/ .. _FRITZ!Box 6490 Cable: https://avm.de/produkte/fritzbox/fritzbox-6490-cable/ .. _FRITZ!Box 7590: https://avm.de/produkte/fritzbox/fritzbox-7590/ .. _Magenta Smarthome Tür-/Fensterkontakt optisch: https://www.smarthome.de/geraete/smarthome-tuer-fensterkontakt-optisch-weiss .. _RADEMACHER RolloTron DECT 1213: https://www.rademacher.de/shop/rollladen-sonnenschutz/elektrischer-gurtwickler/rollotron-dect-1213 .. _Magenta Smarthome Zwischenstecker außen: https://www.smarthome.de/geraete/smarthome-zwischenstecker-aussen-schwarzpython-fritzhome-0.6.14/doc/000077500000000000000000000000001474575644000157155ustar00rootroot00000000000000python-fritzhome-0.6.14/doc/CONTRIBUTING.rst000066400000000000000000000016731474575644000203650ustar00rootroot00000000000000================================ Contributing to python-fritzhome ================================ ---------------------------------------- Use the Visual Studio Code dev-container ---------------------------------------- The easiest way to get started with development is to use Visual Studio Code with devcontainers_. This approach will create a preconfigured development environment with all the tools you need. .. _devcontainers: https://code.visualstudio.com/docs/remote/containers **Prerequisites** - `Docker`_ - `Visual Studio code`_ .. _Docker: https://docs.docker.com/install/ .. _Visual Studio code: https://code.visualstudio.com/ **Tasks** The devcontainer comes with some useful tasks to help you with development, you can start these tasks by opening the command palette and select Tasks: Run Task then select the task you want to run. - Run setup: This will install the library from source - Run tests: This will run the all tests python-fritzhome-0.6.14/doc/fritzbox_user_overview.png000066400000000000000000001243221474575644000232620ustar00rootroot00000000000000PNG  IHDRB.gAMA a cHRMz&u0`:pQ<bKGD oFFs? vpAg@$aIIDATxy\w?7GF $*`TTZGH+z^[UkZU"ţ~v=ZZ[Z[ARD rT5AL8&@~# ._χ0|>f;2 b*!4/xA Bh^BX Ցl󚣕ZQD*Ut. !W4zD@[fWEaR33[[&ʊמ}@!4 !іn=|瓨d: -]^nۣ@K'ДjyZ;⳷$v̽*HQ͈]}E]Z&rN*Bhx1fLܣ#^Oܤm:9p>ZEQP\|ݯ%Z"*)S!cn}#JjI7VƷ5ye]V?$֌uo7Mz+Kʴ>W+YE_|HS]hj-ҼoG&v ҉~_yR.g(JvѭG/Yh0r2#uii#e?.~\R늡G1LKD5ݻ2YE%om-J-g qhoYXo,fb"*).apP*))vBcif-0SqyVub3h_v5ImW7Klcjh~]ѹ JWO}93/_Qo tmE+y?yJ۬u_sKvcU6< F _Xײ)]v[yZ+A1ٱ'{_;W`tWRC_+hc2 _R>PQ.mLNuy ۼYӃ: f(,[WJ7;1c"~u ~ܳzA|Ƕm>%y[W?F !i1Ѧ{Kڌ߽fLlcg첮1,͈_UnVu7ܬphN4κkUEkO9\]hC:Xヵgٷ"k?EAD<|Qo sO}nȯIӾ2A㥧nkF7KJ?~o{uayEeSnlթ0{Ȼ{t`Hw wW'=mE]\#֕|hwٞLU?CyVK<`S#VG'vnƜ+ClXw‡_2 F2uI/gΏg(4wo_hv_=zݣ})%WF 2,@mj7GZ?s32#>GVDQp#kj_i5*tlvѿN=\9T| P͟OtMwƿ;Dd#0{j-̨W'fSB"/.{OJz3!s ZX]7uZYLJQ6yt:{D{5GԇuU~IHI]m4/*3+6yi5~\]zC\\Q ݵ!tzq&+t_2n,UID/*;>~qisxF[g2]/97{{*P;d{E/}{e|<Дj$ ݶlZ* KKޮ\7r:лTvwxϫݬodWR^]{Ό+vj闌R-K[T{2E,P6}YsQ7w%+jƼKgqR.~&"ݝIO=).(uCuIj*K析?*,(vj]CM.Bz͞Iq͒hj^֕Yvz.3!ڵ1KOUݢݭu!t^Qٶ5~DNS͕>:#:y\BDl6z̈htmg\Uڣ@Zz U? ms[KkK[x ehEBDk:,$^v}Z+LZ0 4Z"ZvD϶^ib?,Hw0#xDGtEYԊKdafVg j) NLdNl7USdztK:\]c+r_]YkK5l}4ZTDOi%`łU%~rxfijutyw\deƯOdeZjy^C=wK6ߣT6DtFY6eZ:z6t.iEaYLf=g d{=/቏'c:uX * K(,6jEߵmc~iBz' 2\P9۬!z$`mHOR׉QCNj8NZ( SpZn傂n/p^e^|2jHjɒ_7_T / -J];1c+.0e3xD䫄z(*9jHwo{+kYLE6).?U:X~-DdÔWTv^+!+*is:(єuOv\RaSjuA~?c32Wg;y"z\Tvn8Wּ"Dogٷչ/ۥ_mַv 5U3jH<*(Z*VU? h^}&G v~Ѫvm\;1Dd-0 Vo cz {udZbӮB_>X-bؗ%+9YPXRb|f]mɁk-`c1e6"Pj K3/+Rmu}9n>Q1ff^fD;$-l?T=ڭ0pIC14^QiʅeZM9GD#]l>gY*4<#eˬ,m-ijY)]KHx2fEK]}LDfѰ?'/~ףƑ'o6Q}[JVጾ6 xꖺW~] ChXEe+*6]צ#J?1{jYVzF`>Bh GnIœݯZZ.I5˻}ڽ5~m^QYtF'/y~R\燋}EKN<>|HdaF7dŕ[}<WMb/en]y<7QзK":2s+awqQع޷$D)#{EK~zPfԇ)__+N%_2 >]Jnv5g~^׿ko$4Q['&pǁ/Üm~\Okt?s; -: -tRpM=unѷI*x聺t~TnP%ڃc;96I:32#K?e~^ۓUbZ|7?-n!EX}=sώLtzڣڶ1>=&o-Q̛^ncD.~03@ĕjg'r~6QSsy8Wa.C['rNԸWYi=\ـf?.A+@a,uЛ5#HOOQ?l'0.ZP}HSpcоݬel3Y=TD(,=)%"i[ u%ʊמ}3)&yّl󚣕ZuLUr%\zACi"UBh^B !4/xA Bh^BbiݾZk/uCe7h}p !4/xA Bh^BgwH!]NQ>y?C41nW_jw8N׮HS͇?^>w*Yt~vZ#rh7qvdK<;M#<۾ P*tܝ'W_.vR:vu͈\%6K/{znѥu35Z.;_yHM҉4Ne=g۵}k|WV(R(dZ/Z_e)TKD\uY>ܱm y_Vѷ{_H+VtdX|qUu._lrnz}WO55)iwQsek(^5߁|#9O˳c w ""_ffs+=bWۛ+qlܞ0()YWXR#w3۽ֽXT ܁3Ku09*ec1bV֌nۏ"z]6r;“GFvPo.;uhKDy+)KU v}&2W=.47,!Gu:EQu 5K-\E\2#zӸ'T<*(!~ :Hk_ 5̱wg_Xbi wKd<'6κ(ӺDž%W_;6 c^yTeQ]۷R~V}v[~~UPwΈRUqiiɞp*H=vs֙cW6XjZ[2I[X(HXHD ]l~WcQAI=BܾMՏ~NQ* J.?677KQ%U=*(K6fD) JΦ*۶k[-B rnpWPD9v;_D8QlU)3Af;_rԻr).r2Vŕ2-+UqmڶG`2W>RQP[kf~_GffYbEAI=|LXsT"ͨ#+Ņ^o#TȘ~r5kKbDT5}TPʢv ‚z,.㎲<:FrmRᓢ\ rҎWU > 4eVORW-)ZZ ,X4rr][ci Lel<ZJlrZp~>WW""⳿&wBD67;IQ>_֩__:O8ƢYqG٥ g?l Wx!t,a!rf~Vuz.Qi{_jMsÂzq_ٯtnD$7ͽ0]j?).\:eDDeeG1f.hfFi xjO#RUJowUqײi_e, kGwoWyva:?,-kвd,-{"Mbcmwm6Oϧ5eUeGG<6_cKҮ-M2%r sp%N,sGi0Shʴm/U}KʬזXY{׹_O۟)ee16i.eZysigҽވ8_#5Wvv~棢}D/.m3fҶubA^f~ql}˹Ls+Qlv[t<_z(N)n礩r}A^{ݼ=$:PS&m㢒ؒI?+tM%v@wlCjϧpiE,g~'2147+.&S %Rqy%]]۷ǜgW6񞪴L;ߍ 3I;&Qaۤm\5-;X=).}R\߻ÕR-XQqIy|Ҁw_ګ+,rh=u~WKmKʌ?33}5"zܸO "r&KYPrd}q!e{ueOpα*?:/#g*.=p徴ft5>s+cK.IV R7M_x\)MM]ny ww͍{SI )v'?]ǿf>c(%hX/[> nmuw頋KNQWojyr=}Ļ*!.uZk_ةh7qqIz[Q2#rf~y%5<BsZZs/ñ @ Bh^B]f"j<Ǣ/ Bh^Lw  Wx1j:*4/xA Bh^B !4/xA Bh^B !4/xA Bh^B !4/ISW95O4$*YUȅ)̪Qㆎ #Z>mWq1kGnWhh*5}5i"yIE t9)2jaEVpҟd646Cp)f9mWW1u(Uu֘kdtg<"׿CI3j=?_EO G5bkIaCӰ;Rx.fH >lhg5inB^:_ɣ_(IS곜uUL·1n8M߽qWVTc9̈;?l6&|h gfT7NvM^DDV~^yaCطu2"F_8UȽWQ ƨK!{[qi&v00Dhil$`ij3I F/"оD)C~}vCɤkyFsTPҠZmFlrڴ9QF\kŧa]suS&Zn##$`۳)bOqS4]Z09&F F,nt=J0XLD 1lDʟUq#Dcv/_ `FH,,N[' r6Fe4]UpjJ#lFf*Fš9fB_b . T_PjWBi6V%%gЈMu6m-X+> k49=ӱ|c܎ ]o  I6 .hX 3:x Se$3ʄ[O 8iDnj$l]7wL "ާL8u&-/.zUi?2:{!r"NړQVQI#7_QG5NuvQ[f%יcJܞun Ieƪh|TxJ~aGK&4miDGvkV>?) K\#gO+/iq*M z3q6̞8M%quWVaU&tbVR6O[Yq_"(m;snv>-Li3g7@F_HvyiF 6,&3Z %#P~YT^ GV$Ũ~1еG>+NOnkkGw*Rv佧81AΆ{Z=': SjM>Koתզzm ṁ&[islH1h\3Mϼ3:gBݒ(pغN '-&V˻*6:״ְ:x3ߣ!yiUgiK"e5'.=O384AʉdM2#2'9sT@I8#/܆K;b\7437bՊ}g/VF= <-xlWjvi 0XŮa|3;2.}=>{vەz77=ݹSzՑcmakl5\W,:oࣆsNfc;+ձwmk:jdW_HѤ=mk zN u5V#qLk몥:~5h>?4B0#yCF):"y?kLI#GWR| v$Jf]6ZlZʲG=?ڔԼ2WK2sT'rg;oުnQcUDDotb.DW?م1kR>\^*u5W]7<[K%C칬:v,CĈ"1ːN*nH>1uZdT"wd[6cg^Ї:AZf`wy׳(INR:, ou#Xbהu Yi*8b3RTD2]~-IADʤ.=X82k⑮N>su^H&S.;v⑮RkLvJW W=h&㣀aY!tëگ*bǻ2P2VT}5v!xˬUGz8{Ϙҿ|7>s4]gM7~OwߡFw7 kG^&(lw1~Ԩck:)z{1е >5]2mVwfbd2jk5H@9|s4?97K:'C/\yyJ$^vIuNk ;3>?󛗘>|؛ 2\{ZubBVk,Wz9YK1oaS]֜Ch8̟Hyֹ}3!R5QdF>>Si.!ze^">|ُ.Q\;@lB:DwZ&xtܑ¸/$xTޓ$jBYOUq։֞>N[G}|cXwD?0e]ޭ.de\:5,.,q){/'sx$n8CwJy]ލa1)22+}Q,T,%_[wb}U.˖T;Bcc2;#9WLx&j4]R~[a3Kpz/Ryt R+SeJRquV*U4k4M.KuULDnܕ"cr\=XD< w[db,;%zOȑM*VdYiִ.ad)f7UEG'(9 1ol(,X=cEI78%'D8R0TiDFJ%RD*W^igxKDD~ N-2%]lkci2^M9{ɠy\fu*G9JA[YCDqwqhCDDԹ3}쮰ЕG$v;e)n e2ޞO9dޛK<SW7_{:9d}ҎQ셩TC{ZN =41Xתަ\RCNt\>5?ssi(B*US4<.&GyU(@^5ip+.a*K3o{۳Zr-*>WϱLpK{'yN*_5SQ0 RRI<} ^X -*}_&՜@(t2cťLZ߯9jңM`a-/>x UBh^B4Bˣ.L6Fڴ3gIS#"Œ^#ߚ;~$NFo8?2:2j#ΛxW &N]tl[U""_zqe蚁W2V7` MO=-: 9;gd{f6VZEgT_,D\ZPo*#s3#8e/F %;&et5$pp9.],iĜ2ƹg_-ȅ_SŞΧr=ZH l4Y{տ˵"Uݲ'nwܢs3WVTc9̈;?l6&|*Liۮ HYx؏XOyaCطu2"F_8UȽWQ  nf+ثFZ4QbH,u9'dl#;NY䋗TS&/$] ۫]I''rsHdy/Zw>=SXrs%3|np7d˷ bXa+LY<޾|+0FO$}/$Ύ]^ ʄ[gO ?i]ٺ3.zUk??GOBɕQ>7+,r"NړQVUI*%nϊITψTI?,6iY\U/fI?=ns٣ gt_'1Ae5kPդ*zysQF'V.6X#gO+_+v}I{*nL zߞ*l=)pKxUw፴~y҈$0;0pd?dpMF&jm}]}=@ZT e^-G^M64uTھUOkON%O4"AU9ݑ]޿|YÙ0TۅML hX 3:xȜa2?T(1pҼsj$l]7wL !;,Bsi֮>M ߴ53Cz}qGF!{—Sٽ2GDp7GstbO!VwYǷE ߳u}\\7Cުk tU12z'xw2["|1nUˈW :ΧYbyj?ZcCuZ|ʹS=X}"}{M cebl/}ك:Vwiwo!I =eH ꫢzV&bB֟+W3RGm]mϱ.nf*sY1dnkv9s"jk\c9 wGuvǦsU0;T:4h*'zoDד5qˌПTS5d09#/܆t\7437bՊ}g/VF= <-;2Bb#Nr+J|fMF'[mR?{.+YƑs?p%ݥqSAՅ9.V-`?cJ5>sΈX!bbe *KpIn)!xy/&Y}،d s_KR2F@{82k⑮N>s|`cգJkFt!xkb;cW-*dqƋdz{`*oerۯ=N:%Fzrggm 9TYL :1^uzV9 ZEBG^R}]SgM\J$#\X>ny;%3.m[d?쏐{}@<7rWf4r}Sf*'rg;oުnQcUDDotb.DW?EcN,fnȊzVdŠ30%r2Ǚ;!up1T>[bZW jbjYsKʩK)hq"wO eG٩i޺5U42, 8VX\8zɺZշ2IKCVm]i08boGFVN>L Y azPv\؄dBˑ1@#xf0v_ S6hrrXP^tq功V:8k>&^V ;25fb@)i-]rr4Sf;=wf v'"SOMۼ^zZ-mU@yBVl&F$u -w;igpUCqkbͰTub;yiO.xxWS%I hđLB;xйlFVoQ]޿#~p3 CZ]R-#KOq]0Hb~H<~S:s̎}}.z$]%B9?{s{-r^C_wLh%T5ȝמJt.)]EEUK6rkj4Y#B W3"{3}eO;rlƦ12O<' >>PbDڑ‘˂MPEEFFg`L|33e}:w%q 6ElIHVNqeL!/R] k f 5l\3Yhvv {7ŤʬijGN\S\#4Vc"uHRC_. |χGd (}Wآy}KV8|oaowp4%)kWX0pCغQwke= bI/-OjrbrxU0ŸvFwq9CADDLo-vsJG| j> Fk>P֯^Gw;{W5B@cQ%$ŒSfb@ 9 !4/xA Bh^B ҤB?Wu=-: 9;m(=ηd{f6VZEgT_,D\ZPo*#s3#8Gt)0fL}DA҅G>:ںv^5ip+=UF-uySk禮*'V8։K\-"1Sgʣ.L6FʯZ59U=<_xnX4}a( +SfjntSY;!">]WoeDp웑{W=AQMr5WC lZiQãx,=tKsh_m|XX<!Gыbm}rTrB)Os7ƈKߒCWmE>[wU]?ic҈m%[o2e܁]\OS s,-ͨ!krZ!>,;B֛DU:<`&GG}óxTl_ uDP]gN6ևM?,A_ѵEjzNf0T}ANL7"ɚeFdOsz$`pG^T v`Ų^o ߳ignĪ9"b^ୌ{VA$;y(Z0f:Z)]}> =ikfG*L|㎌ B/:{ex>{vەz77=ݹSzՑcmaklioܗ=xЭcE {7_=w԰SYuqcW>gRepa*ˊ9's XsϱQ^Vycw=B雵FQu9FT!&Ȉ(ZcCWi)*^ &X}"}{MUZzZKk$4 Zճ=cak5Po1 Q7ە9zxT'~g#9S'(Ck4eLnC|K H361j5X3.m;]d?쏐{,8yS󶽪܍D8CED8o1+x씏Ow}t_ CǕNvaZOrۖ8IgM=<]w^6}k`~_KΥFsFIJ Sy#+fH+"cQ%xpIMߠ+۸Q6qhڍJ"k/nlaC*qnMϒ##O^Ϣ~.ddۜtY2Ǚ!?5-&fyꂱbQTiSu9bJHQIٴa3גAbeҍ\,&SFugH 9:̑=h5QC|Xc{zF4 H'G2,2$b˳biGFϜlɇ36l6dhÐhrx؄c/Bq功V:8k>&gL yb@)i-]rr4xz۞;y3x⩧mx/=ܟ%"M9""w5c)- HӐ0BaEb8^W\̝lc9NeXp Y(cϺzݬƎ#a -n`p&$\Td|['O&,\JTL0 c M+#>%иrR 7f\SBّ|,;5 %zZJk$4 MӁXX޻ʎuƘ:A|Ln:t˟ /@DRrϭ>6p)$zQfXHK{p_N2om\mSc94BSt*sȹMTWnZsb*"F$BՔӲW0 CZ]J~FTXG}c#H;~셑ك8]؀.,عHw;6SIԨQ;xՒu YuVw?ג.9ugH^rH\SL$sI*r`mF+m4nz]sSc4J ]&uB~%lGC9X%:A|/5tѤVaP4&xtܑ¸/$pFD9ZPSoubdUV4ud)7 |GcS%ݺrJVvʥ[Òx ?ዧJ3t%"S(9q'E.BEwQrYuGG,c7er*vGhxt_|UQ}r`TP$!cDR *03Kpz/Ry R+SeJFS+j@5ZZ%Ɍ:qS*&"~k19.,R'̥"sd){Bh#ÔqKo:[YUZl\z]UTDwBӫX~%lGsJ6u28_[NnFg #[P眨$#j/bޛszAK^jԩ*g+U>6M{øC'""νVn1PDD6oew<$S)Mqc((͘D$?%; _Q8)ۼU9F3vzg=}+l<ƾ%g TY;g,!l(٣rri|xtH9d`ۘGW#`"?HBb_<ٟ ]-p̖Oum#/to5zH{`܇o߽ C դ4ݫj&8r퉈>n[?ǿND~K>RX5og#rUt +e$݈4uӋYZSn\z]SU&r7 ;kdLX^%lGG+ v k9c  oCӟ iZ\] YWV!NwŲpiQi>37wL;y+:sɤUM>/(KTh4|۞eԊkQb~--g_ڻ?s\τI|^` -*}_&՜@(t2cťLZ4&N&ex!UBh^B !4/xA Bh^Bbմ]dۅOM[F!"NxX_o{?{HMJENT]t\*yW[n)pe>3M|dȷhǃwoW_,vtëD\|tQf'> 6W|:4B55]49*ΌJ ־Q)Ҭ+q!'sxFĄڱ.g &ԔXezقT9JY;s-^ |wY0=Wqůu\~vg֌Ue_y󫱥Dvo'WcIDl{X/Ά߹A=u , T%3 [KcZ\͟Z)?Z>6[E5,#j_7Z֮ףCALHIro}^HNR~Tcz ,Afi-n+:t7G[\b0[ ,d![a-NԮ| ҂T*2rϠ؜wך *|ke#q;X+fdijV[MCl/J&"|L״ `Uh r)LRwkK.M~?W2(ty%""Uvѓz,XH;-՞H֯$)JIQc}x^M}uQl*p~sݞq^}Qa895Ur\ӽJX&0g,͙(L9n/2!grOk"RyigV9YJw]tߛa~u]חrzÜ>oH-V㣛~Lj#t$"{Y~^>|⚆秒uk$V%>v7ߴC.*YQޮ` l{sb忦udJG"?/,]WYջjC|HMm2}4;ϯ,H~_-sK}/MQJ$^n<&]R=_}[\-%'urnR0ub"/ ee*L"RGiR7|w#whА2VWc i/dv^t\ش.kKu9&}uvMtQ [dY}@ћqo0NRG3~^szVPP*tѽQuwߞJ=ta֌GѰ3K}|£D"1PsӾ=&bQ[;|~ǻ>=Ic2 67 N7~ǧ́_mj=בc/WWeֻ{ډ_ϼFvv.[J}jK)|AT>XxITv΋FJ̝ՂzҴo.V07lͨ~ekk^ȴj'3~E7wq\&kg{kURAӒB77ް]βpo}&A\E]\ 5*bioiRw-ӜY";f>>b*>ry{?_cQ}T֚צJ?˟X^)!6ćequ/U ̉,LSBdanR"*},ZθY"r|W'/pfwo7N_n_q|9I~jsݻL#l; "n=5)QWejkezuޡMLnB&-!;4acrlJKǮZc+>w#oH9:W0(LSi:XHT nקWdqI^DDD悞^zr? fZi:AwK 1)&Ǟ"2KḺp$&|CsPf+KsĜ|E~F RQo ܻFOYV`[s2""<Y~}-3{HuQhrq aoNi%!'.c߬x[Z0bW''YFôzTޞ~yEDԾǢfeDx>=gք}, ,O+̏v늤5%z~4!rǷk}ͅz.JzH*u{K歫q}^[ٞU\%<)MV s^wPGddp,c+ [VO3x1B3 e|8{"bEGi*^Uq[^z)Ԕ[ҵ[~%W}r>w|{#Io.E&$tp }dޱD }0xiyٷV:$ E])&q*`YT k }9MiU^UҞևr' :^_oyX^D "]~-C.ӐGA]%kuli.=ЄL CGg?bY.n6DD忴SI%F2%Z[Z~F ;ua5c[/2RS>5D,Zf$fNZW,_vYw#[^ ׵,F-t}}ˌn{T2&'=&^g2U&%RCB⻬&C]ūrv] eWXN]CDTZ6cUb\-᜕Osl`ݻ8~T&. {G_%JξW[yﻑקy:oh?!>X K|\&KY[T럆3эH}^fn"wI{lg.n{$QŻĈ, Y W~[sRT8,NJǏա:>C{qW_e2/_]:zѡlo'x~ᡣU/OpDžOγO,?}/*)_G&I3 kҴAlҷ'>̒=]츱/#oG{áǢ¸kqJb8_{EXW{:vuR&|8+3}g7tT(rLRd=W^,Z!j$}Cۻ?zfXƧSF _ =LQTi U Ji+GwVU럆3QiܑoV_Y@]L MswV}|WI+l4֓sO2}^?ieDv1Np7P"7 .oLS;~A;A^ _ z˳Ͽ/QDW2zX>L^0drw "ʊ^ï=I|yoߴ.O9kq<\>9qH$ӧ>۟l~wboy"[S#ÏCޮB4wbrr}r/~ljz]y wY% }.cSrIԽ۳OĖ&ie񧾻$+ԗw@~T$Qoo1uXАDԩ߸6mǛ&m]{ՑQjb8󇏜Wv=fDwJɩB(޾ӷw~髿Su8Оr~;Ytյbk_յX,&h|nv]J=<:AoɡZs\Wx* cGHT6VEꬕ%/W!aSޟЕhQjL%9$ o{M:uv2bv-L&|~TDOQ7 KD$ 8IǒQ g<:UsQO è㣾:[RJ=H]Z_h+$RKlDv,]xVtDD&Qߋ4_北rxN/Pe&ڳS JKyre'_̼N]R Re28ޫ?9=2&&Xz?lLzw*Өy|jCσ{+mnrxe8weTv{s;g\ O֫Ф*grlw֬]^C_?;_Ȥpb?&WI8in^Xy<~Kv-}154|Z (1_(ĤȫF&|8CL84=ZRϜx[<ږT ,;w[n<@(;{7IWx_ɡF呓߼^ =1uP|[ ]Y"Z1yzQ+I7q\T6Whop]aCatA r9tm/&ʩ& ȷSʱ_^2-vۓsbR[r^vs>?4dպIFoww' =fyY"dq»WO̽^h4i2_a 93J'?}w9!CCNwzU[beɋ5աC3gΩ%ju3:t+^MÕAZj1jP8wR׉NZAޓ͟uvygvA :iBN5zE!IOS-Q6Z86V*haQ^x~3MQE~)GVmMt%e֍ WO_2˕mϝ'=ROWn5yczBZZ YCR<QN亍΋ƒ˗R';D ]ƴ؊ˣDž.g.oyaA7r+ڱ@q#g-;~SUsjI#'o!BeIc2Nm]޿hZYOVzs2js.ZvUN |qѵg.':tŤ퉕'9'ri7U[M ?uOE6N:o9jȏb ^=#%>&%gM*N3XHi\7wupv,?n(ݿI[ymkM ?uGߕו UxV=.p3ujc6Ъ3ڜK
;sqqdFtkhorN#?VN]hY=1fQFG! !+{/5BW?/pUA;kM=(@3 .dk!v"fBsvP&ݐ)b#c8, c/u0u6tJ=Fq3V q;̋r!hTl7X@:졸q~ _gءbmJܿp. <y##FeeYpU. |T,beGǫ){yJIPF4B5hU~k2X*"Re$|'wJ\N-IM37a5P8cYP'u:*ԃ4\eC{rx]s**&6)C)Sh Qi䴠GX!FP~YFq'V'=Gzji;?p=8~JOw5=5T>`,.Vș5- ѭ{ENc柾8X+ a3eR"RFw_KI=V%"9SyPOr;/ Sla}s;iPc7X< *WYdGEUJ;.BOZ ;s\7&zH∈8w#@@sxJITm MCnݗ8xia/ĩ @x܇?wc?WՀZ(b)'!>GgkDz%nS{_ʖ˒/ =iU;)C'[s yRLqG"DZԧ~oTr,=v'G܂܈(ȼ䈤N"JTW"th`]E}!.y&R4^q&[6v Z"T*64JZ=xP@:IHTb,;%Sd)jՅ%yti@-`}Rh𴥻/I,jWq$`Ū&v˼yS|gh Xuk^SG pcϝ03fNb ^8,#!o&iѺ>b8E[U0@V---=\K{Iz!JJܺ`p_ҧsQc|bZ4N%Fm?ʷ̌}ƝGlE!"F;`>UfsndH].7W`PRC"zp>Y8ĊB*%Rl*}/#wq*%VL_3qWX#J #u3`ƶEq4,|ݳ{/0Ksԉ^Z"v#XaE,&ZA%?-Z֌eAq֌'BhVKV0_>WZD :ٲ'Yf]Pe*O $힗wqgZ'333s玹yOE\I3djXΝ;X[!烙e[uƾT6Offf,F iZsss@`ii+x#7/].**@ WxA Bh^Fn֬9*ooBhIsyQ~A!iLAv6[ # }!(IDATk,OZ!]ZTuqub!߂"a:]:ZaRuj'"NlX1V ɔOITo,Ox@j=)NYwl'T >2KBhIq%st 6VLWb(n !4!_?vNUy!4~% [ WC{w&Uz=)^J">ݲO> eH~vf-mΒ4(Ύ:rF[s֍WFޓCUOWnXYq4Y>n-NjwRl |Ef+LBhZCvS{~z^i݅nv[ nbh;I‚ g$g$1!Bжk/Ǫ~~~-4k׼7oM*Qɥ[wMބ9Bcs9:WwN;>{Fm9w7fS7er"KnxNZv<]I?zGuϿj/ 33E¯eGmggfRk0y~#6@I¥^d+ƪE^]PZ+nR^t=w #4GD!m_E<A%?-ZV˂gFE*Z-[aa |< :m9`^mINjm>Hli!R hAˉ޻+"&U ]NY<ƝեVwItIU}ĜH1Jt .dkʲ9KR֭7cfZ*.bv.>@D̮դR[Wݭ Ӏвs;Bo:MG3\9E-\ wƆ- ^n='JAD3ڜKU*1eeұ{H~cY*"#14:xh7{+&;&GFVuDq1AC\a-WY`(~Y'p J4Ɗ[g3F/~qC zwG pWhֵcxߌ[:r]pഅOp^DwkׅwK%ye3{R2'iɛ0ZWbym[-V---=\K{Iz!JJܺ`p_ҧYJU"2CfsK l`tļ%e>$G ~zᓅCK-zwCiI!x* _޽K6uBh!>V˚,(z/N.(U*>BZ‡aZ*Z'333;[V +WlW} \IĶ]^RgZ'333s玹yOE\I3djXΝ;X[ [`xB :YZZXaKKZmdjaaaiiiiiiffV;^m !4@+dffj;837!1dRD4U{ͨH]jQ9M9Afk骹ZtMMbv^9[F-`Z-`Θ(|Pp3à"<{;{t[ө6jS̬Bݩ]lam"+Ip7-j- B< >s+? @ŅH@ -4RhB ZhB )H@ -4RhB ZhB EG s55KUU+5Tu0}=׮]dڭ~~~^OAڛZQSWW@g 5˥*???O]ikU+w^o|hk5=55? O ZhB )w!aa\//gE@3:sAѯQi#>\Uȥ'Z>cӤkO>I&>Ǫ*4^i?޿+MM`ͅ?||nBHO 5Zh0͓B9j?KJ#bXja!Dǿ,3qABc?-؎bw B/mA:NxuS{K%n:.٧_~v?Q#TxJw Sk \u. ]~.">AqfFȀ٭kPCih+]B !BkD_ BFfhx__ZE>[Xky\ [5G\@|bĎ=oQCu-w9ͦkζBqЈguuo_q\ntUBچr 2vJԯN{dr"SrOI oň˺mf/VU?!GN~ԡKc>;؅sUϝ-ݕ_iw3P`ٽsYߟjԩu⅃ΟlR{>䱘!bN3q9)ٯ\z|+'o4oYh?B~sg BmY vg iܸ{_#gĘ#YYž}A[യn4&i¸/"й#;D{"SoyGn" ~7ڵkl}w? wҷ+O}oQ.Ϛ|VlTЃYh'*/DWs|{q@Y;T~AV~k:!~q&w i2gikиN5mm-uhtnF ] :jC;:nF ]3\[W{UEOW^|@{WSS{*+5tءS` i…H@ -4RhB ZhB )H@ -4RhB ZhB )H@ -4RhB ZhB )H@ -4RhB ZhxByag fcS-~\maE.ouZ$M}͜ÖxVdѣIaUqfc Q;g5'=њ;cWOlrf<Ӱ7&[J;YU)3vGY8|-H:ODMZtI|oK'eLVaIFEiuKH A+2'G"J VZabḨ9i)Ylx󔕋 z">KU!DҬs3%-Jhh_ylUG>uvl:X4+K;p+xL%n/Vr+zh2G=89ceZUV4-]wj> 7&SXdaJn/>KIECĄGF)k?&/}'9ƑƄҎmV'e}~Sw[qز%t-zvm |U>&,ǯ Ba=}Os̠)"5 wgU%2fĬ7R1! x{>gՇmAC&/yus,l|gEe{۟/\94mqsrFĚ&,ö ҆犠A+v=\e9sѴ(!ƿQb֊Dǿ8$g>š5=ZFԂ2lOO{*Φ TEn"3űb[Pl&W>`բmk(#NQ2< ;x;7fuXbhZ(w|7ڰxj)C›v8^n^a{q!km/SM}RI1hRne^J'8vY]o5;)ÒՖ"s-.Jϔ4ZƂ="i6/76Zr`_yL?Zr%wotՙ3o~Q6_IE[KssW(cƈg|cI+amY;eܶzV C4 M_ٮJ؈۲vf͌s9S 34&CB5l_>t+F-Iq+cevUFdlZBͪ18sނO*c_\~QJR6^39kv'O'svM.o^:蜳]l:j~ֶK^Zm7^nc֬eCa.OӜ=-hЬWŚLɔׇeT "A џ:ЖO+SJ7zrR6[mRg'iZTͲ<蜗nA2b[)ݒGKQg%6{'Rzuwl*'et: ͏)iQ4j%跏 <آtMvtnN~9P"j}_:^TaW"DT6dzRT\_eힼ"Փ"&ƲW'$<Pe>Ҥw֊܍sfzúIM =8WkTiWKwjae~adZxDsXfZBۊO)GZ=y$04"_/{uyi)/NZ%fƄFl.ժЛ&-GqM#&m&@)4a&!9<2bOM=Y2j"]"jq vٱ(7 [%k~17E>JE_f5mKߙ7ȷoJ"K>MlWa[_06UX {^4VO}TzӍ"D}ť9y3z!}}>szxM1lV хynsv_CyRLaE^բj}MZMm2(Wx&" ס幩'WV>A{4:wu3E 3s`ߐ9K?j}ivLvGqBj)?s|Ϧo-~vh՚CQ*VjXmDb. a X (/I %QIK|Q7{Y-(/[(y}MZB(zoqD +sNܿ>#Cvrf˚}KmX-7 "Oͽf>fߴb);~_l7 MbvGq(r;gw+Ay|Z=5mVכ1:{%v[+̛6 U 5I7`:}jH (:>y߱l85Kqc S,L wVx؏o8ֵ:| 1#7e/J_l>If&Zt^iM9LⅻQ$^ඹڵkm#S3ƝT ^;ĺ̔6Dhtm&ӊŖ % v@A[N~{4ȶDhYha+* SXOԆ]Qj(2hB ZhB )H@ -4RhB ZhB )H@ -4RhTj'܅ IENDB`python-fritzhome-0.6.14/doc/fritzbox_user_smarthome.png000066400000000000000000002000731474575644000234110ustar00rootroot00000000000000PNG  IHDR I\gAMA a cHRMz&u0`:pQ<bKGD oFFs50i vpAg@$aIIDATx{XTw}Fk4nzLz& }a0!%t$< t+q+Q_CFA7D/4DN7nZuϗ>FŘ _=j~p}i޷/3F,kzhl‹֯6EGf`OcoM/_D㋞_?U6uo6?ц?z6 omUW| }7kϕN:=C'۞|/58T fOa#?\틽of%`{-6S`+~᧟䃿sk/NƎ=זsr` G?U6w#lkUy=ѵ_&4t~y#.ΊY[L=$ע{UZ?C"2#ʞW{54>ɗ"z_c{mLկoMڮ|}w{So.8Cq&k+:k?.W__n-⏚o+>k#,x4/}M]_] JRgow3~^g7?/TDODw%v77?拭 ?~퐺Nay0WpJo~կմyinvSu`>0mWzl;I?]: ?~q[O{12v|"_şGJ?=~3v&kE޵?2Lqo\?|j]vmW5|_Yǎ#"?=L8k7Ḃ?o{@.u&> Dg_Y ~Oco`4#cDp"R͖Dyx_:mx}ܩ-޷_~}mӌ=?v>/o|퟾lD?>ǝZ=:_I~8nՉ߿=$\kZ/o_giao}`4zǯ5}Pf 8|!rXs忝}w+/y륿xԬ|`=v|/~WCk^Fuğ}}oϿ'ʛ͟9KU?x{v?|}ᳫqx`:_9'7f[rv_鮜1Ǝ~Oh?؛m>0_6`}4W{ [o cv'Ox@C3sϵt:7/%q_oS/#M̩}ODN}WY󐳯Kk?""/+zS[6oɸ1c}s ,A| c~)v$9qkMDl_~#"CgV.z7S~W>Nc䟞Dڢ;k_"Rg~@ |0&J\~7?}Nm_'XC 9g횈|O?|ϻ뉏=;f_Rz""Ͼz&D}?CZc~?0ȫ_NPD@D.}3x`?ɺ^'o.(SxgWjtp?ɐU&d˟K;y?ǽ~mcj'>~?+՗_>W|`gO&>ϵ}б?z:O&U(|r.{=F=L7?إ-xN 6ྤ!G=/z~ݟw_-MIpMW;6VK YY)`[ۗXX?P߯X]~/~&#l~<_7c'wcu|붫U|smY`hF`lt@ 6: @`1׮]%`lt@ 6: @`lt@ 6: @`lt@ 6: @`ltuݸgNK3we~eCDD:+'|rW:75y[_)G㱼 O&]Ho%^4gVHkIfzʕ{O^,G7CR37V<F3qO?u=smi,o=Ys_k 5Rnewܛ]U&<TWMø.jwsΡ{=YtC^;=͆oN|ߡzFʫi9qO~V.I-ioVWIw>5QOF8p fo; wR)dN}0Qed9Z쓷oAd}g˪%e!>˵OSEFWF@ wlV EDEDRLm.^juRZXӯoV5w ="`}ח)"bPMl9?pzum/zU "ʭ֗M],j:)'w˨F"u%!} K']1i^.Q.DLs9 .icvw[WO3ErI-d2D45eEO]j鏴8EF L;z|KJܦEDL\XҠFψJGwyJd_!/ng;d2/_znem5-Pӯo@5DD":8uൣnE1p_(VwwoKڋfѽj7d#2IU5JH&U>t xtr8Ődn:8iGEڮ%DWkq\>UqEzjj\rܕeF.ٻ Cs9]UVC]c‰}?8N{prZqJW'AOJqiGEwm<9+55!5s;r*#÷Q]anjjBrfzpmG&dm۫gҋ5wŁ,[jjmAS";j/zcӎsћ'=ݚ)߳~0Udaܝ=? yBw=`tN=:YSjy)tY~"=j>$tT{%g4^ZTÊ917Dֲoúʞå[|RwZ^߲uʐQc_J*'k#m..sVZi;{ҢׯښÊ919öDD}k `ѦWֆ:BgDw^jh7:桃oڽ1eGEe`誊*M?5mDs2| \۱O:FP&ņ(}"q? |tPn po4hg6n(kx6Y\n΢M  7 N;{tlq:5#:nɔ{<֯zҲ1(NdW ;+qwdi$5w`gF3lO[8jݾi#n,g'Ĵ)栗LADLM[d̐f1L^ާ3dgOgx*"psK^A/xUuJͣhL0h =@9U>Ewb0f;( _[R|>zTȭ9Œh\iW%ow+Ԯ&6I,[VnM zk?um^('פ1lJJ ?,߶,"'tmxہLLY^фf/lϻ{DqރO|7޻WEMt;뜔jD44*) rJ˥}5VLgŻ)3*DjZ%4Ћ~^O 9L3޺ح+bL")gT-k$Twj|y4GSk2nӂe^O{wv:kv: CG7EQDv'3h :\!}DKk$ ],8w9F\Z7W{>4();f;Jw{MuHPߏ8Ζ|4i+L]gOzCN"k޲KBp;0[6)ᨚ8P\nZqbei=xcTC<]dI Y9Y*ۧ 94`tuq&qP!3}-툏öxTtj$CWi k-.9̏.A55|+-S1E+{jO_B]Gwid/d^[΅kO}NH`F<5.oYLy >^n_Yqg'[OM<8o{rfIZSFyd46߲dslבfdvZ;jꋷ4[m;H֪틗eo8au)u+8G|lޮl6*T3$L1)Q;E=#/cm6Ȗsا#Ŏ]{jM3*ެڈM;Ş{ZT9^L1wVf};[Ճ58Ls[{}yM^QEDk.+>ez!#>su{I3bM"A)z*wnFqu&7%A(Ѻ6mtMہr=vUqkr"W5Y[OKLqI9.[au|lU7VC;*W|*S&gƘ<}x=j{qG0tGuXlnvԿdw I"D2%ISTIDBWtGN8d/\M3Z1q~b1[,~3:j/{7o[vES~P3_ |ƮgEw#0 VS(#[2nt+/}xZ-޹Zsb 88MX2msJ /ޖg[NM1FϜ逰$mSzeAn[T[$%lYNK/]~ g7%cźKO1~FǞ²%33G7W\_:[yѱ{-ybWqC s/w(a㳶:d ~AnFqu&%Y)lSrؙ%{dӗ֭OdQ76d<-m+I{qT\RMK_/ag ʕ7^r<#6:VάaTv\}p[>J-3L9*KJiQZE;Z쓷oAmj s(noiI(8aMѪH¢Hʖ7¬=+ϭ}ͬ{Iۡj\[C)ݔ8( ="`}ח)"bPMl9og?_&D;p>`F`b0&ɼ|饺uִ6jvGm "[$q-)~QrRmbpfSM.K;7fj2(>Eup"kGFcR,EQ mof8`Tvw{ԸU^0ҪsS<'k)k~SӋZiؕCDv'luVf=L\j^/i֤'f鸱}Sdo}q࢖hչ[l,AoLMZf#K2m^j3E^OM;+\g=wSS37t .Sۑsu*gkuӶn禬wo87}jDZZ09Q=c}iQLEnٷfݱ[mʈjiqZ37){q@DR}ڦ󍊯mq4%R*Vi3s*ܘ'ܶRzGe~>o}s }^DJ¢ Iݫ4xm x>oRi96RnaD nz4̩P8:I{Vy1mɃjbp~XHW䢭E[Ft)iDD},kI}jC*u-ZsizGg-[DQ[v\Kٱ*%HDĒ9/a"":/otUD,)񯟪kin?U+FUUD;EU"".O\X4lǗxrweǻ3'E$sw_7[H-'fX;hK 0? WED d1)-"d$p>7.;}:+ zTbvt"bH>1C2qžn/">ED,i_M~QxxIʎtgN0gD^væfN`""J肃;L77%cCN3|2qq+lѶqF Xfq*jyaYJj9b}~ :Gr\$Gw,YmƆꖥVoHͧ>>h}LhڈN0wJ!;~Ee&#){2)6DuQOĨvcXY+ED,I3/۔5#efRdhU}nk7>!S90:B)8y-U{],~~autz?G;F0nAS#7C_LC][rd//_/)xΞabPD#/e+cdvMsNݶCZy1իto!gF$"psK^1LLۑbR=7OW<裼9ٳԫoEMt;뜔jD44*)"-W)55|+-S1E+{jO_B”)0vVNloֲGҶw8ʲ`b4)Cr˹KzgddK9߬O)=?-8P_%xv3JʎNbrݞvD}}os]MZ;5SyX~ 4i=6f[ۛl)8g}j̞9xKAMfo~a2ν]im:U|ÓL lݵ4s^bڡ GSLJrxGk{suQ~yy-S"͟Tk0ED$tj|EMGpTq.AnMŽ9{:lba<~]dDvCn_oq;TpQN/$ˈFH3˓-՘[vlnv6֔:ҬRnK7=Qkٞ*Gԋ)vlMrV:bք⼒3mͧ4MH{)F酌'1󴞷(+SWCn=@֋DD >Gn˘f,{Ko۩)i_+YMX.ciKm.ؔUbWqCpO#~ۏ\vVV'Si[c7=JYu1p/z=gM5as\o|֩iSn[s]zm Z->ڡXXqK9)-9Z٭|L扱ɳ3fttC  =lt@ 6: @`lt@ 6: @jF ]c]F0J"6: @`lt@ 6: @`lt@ 6: @`lt@ 8/ת7랾w/dd)tg]|xc6nD{=i~ʷ}- տpN. F`FX$"b_v""b?XmC,A1f"噋KLYNuhjȌ^5Hî2#pNXΞW鯞uTOC_AqxiLKgu[(|bWy]JS)C/|ZED L矛D0^._D$4=2#Yy3}}4UL}bcک$ͮw㉣6&/QCfDl0W~yz\~;2`F֣i"N 6|ب%I gߠ("6!ƕ"Z&җ\۱枀gsg0L+J"]j%"bPs@(?Zz0wSy 7UC3\_:Q}!a i}4 ejED zrv2@g# U &E QDDȥ;{K_>ޠ#$ff%vV7i1̳\[>.ns]5VqR:֯z7P{):s5ZQz&`6: @`lt@ 6: @`lt@ 6: @`lt0iڿ|͐Ucǎ5LSLQM {dZ4-tV˘Ίf:+-+QH0uFw&p˜1cbbbo~sWk( 0ho(.]+Eil~jO޾wa݆ݐ9eDq3AAAwVkpp>|*d2LO._ΪQn.P L}կntʕ۷_ti_|իWGlg?vMDDc-e&$y?X^ڟLîU'"R3=a2gR)x˖׷j$tT{%g4)G/-*|aŜws|7^Fcۑe3֗M{_oO\oMaͪ#]m]Uzٜ{hنK7۶*khEwg*_->?tA)JSSԋjjAlaw)_}??w{wDoo/5'&$?0ome츖vUc~K`t؞}B-}2bKښC1"LF(h24lǗx2b6JnݾJndUzS86?wa\HH%]Gj_vZ!e6%,s-A3]hZA M6TM_.w2o w?`SP_d2hΖ3%+ݽYݲԊʹWZs}Lh6N0wJo@.G5(|ȴm뿦"~)[# m2=b@ֳU7uں4 t)OYGtsR4# wٸ;kUU-[x-h7Dv]wvK"i;rS{S ޔ~xode("3ʂז^1Uuߋ h7~ ۼ4PNI~S1SuagN&E|رXy^~eiT!Ze]`hiW,?u]QѺ96m:A]nhY!g4foYwÃids>OP6O*?cHYXI`7UgĪȧb|ݙ{ܸq"2f̘z_~6xccm6Ȗsاjꋷ4[m;&!cpەM֦S3'(곝NvמZy7k&ejČXHPʝ-)̚P_Wr i/ w Ʋy(r\Ck޸/ؐ, c{'Gq8;soKKKpp Cֶxr5[k=%2E$l<ޒvj90za{˯,VdXUʴ%6lʪTtWGL=ڷ6Cy rQggmu2婯Y[nS[Vne[].~DL3,jƆ}-y%i?t/؍Yn 1jveK21׮]vvv644lovȪcN~3!8")ciZg}u,cw 87nx1b]nEۭl~jSM%ru=|[d4\qS{mULnmuzUn(e%|]:75Xk;y sv;*'Ӱ+=uILOvAzԼ:9O?w_fAӰ96ION: |dԸ+2؍6g&f{Us4Wn]}kubegW|UH!Wg<;7ھXJ[zI^swq'V'\Ћou͘1c,'DDde1H̢>^"kM~z#>_30<-ȕ,!9;5kC3M"۰p!l<ؿe[vNY_Tb’3DK X1':huCj۩NHՠ!vYzN[w&wQU ӷܠb״aX2H6$Ee<c?׷x_rw֫WEƋOkU[W#=>22}r+5WCYr5Yc<,UsM}ŷbdgp'lQID,~juk$b#7x}Cwor.[?4|}9.Οyi\ Ȝ3*+OS\s82^l[ʈ[q9F tuԻ^}oE~] vjxWEQD{۴DG8w9e9z!ewN=6owץ o9&>9='ij>=x|N}Ox?|9D5'wsoW6uZNl*x0ybk>imt8jw5͜{vh"#&ȕs m{w7R jgWmZ?yuyX:wXkN|isMYN7Zyd5k>+n}vGjV~kYY_ϫEXsS+4p'чz񀈈hmӌ"x[eos;5=sްjK/]~ g7%cźKORe,`SV󬧻=rteѾѯKPGPK5lcC>Җ_ѡ 1֫!&Kz~qMgC6F>:xQ"?1Չ!egkܔ(r9qVD&>Q3޻:m?QcYzcbȧW?좍NpGvѣGy`:w`p#@E RG:יִ:l-5#zW_|9r{ݾJ޻ 6#ZunzE ]/s'b?XmC>pxEDg*T9 ).Jpri~4 VSe MY0[5rh>gn2M`[Z˾5uzhSFDWK{ڎ(랱(oֲoúʞå[|R@8ߵJ{P95C\-t^p'`kͥz_+ H228!Fɭj.~Xga+M^3*_hӝb#"5%$LDeǻ3'E$A9|_ɾeǵRDD,b?ȯnZfvOp$IemtBp9{.mhlNzAQb\?i%hFϖ&|Ei1%zN[w&יޜEKMm$"n:Zֶ_j&D]%_Z?[Z1QEBg^X)3;jF̤fED,Jp/VJ˥}w+_?LLY^фfΟyi\\sn`jiW,~l;+'67kYEuNMpw9zl6?L6FόzMWQxZuo\r+Ѻֵ9bIXb9Q[}Y.Qq\]_xjꋷ4[m;q\ܛf먯ت-F ]'чz[ܓkc\onNٲ/ŦK5lcC>ҖoH bYLYWa rНj:ůSXDy8zfƒJ\O [-ϲdƹb9O1&-ߴUSī"NHpOs5ZG>uG 6:~e=J#ux8:qt@ 6: @ ]pQy]{N/\;[6: @ܱqϜeojM{zN8.S}λ;~V}ӻ̭;Z쓷oA0c|x[W|뭟^=oQ/muy(w}{ivGe_sKyv|\#z, v\ ^$/+=5.9=}k횈hչZ[NOMHo{ڎ,MҷUϤ5t1?55֗]vzjBrj⼊&\W&=95HGmʬKͫc{|Cb/)b c!ZL~kX۰p!l<ؿ-Z,/*]1^aMܮ 7u^7*Gn4%RZYu+rҢ #rה4]OȎKfE/-*|aŜws>+o Ww^0ELt1#|MDuO WeǺ. _$Hc8>/_dlRE*"d4wpg%ZD,CZw K۱*#?mohbYjBr v[]ۊǤ]|2)6Diimtm%ҒkJmys&488Ѣ)bjhi?Ю ߻V2Sgܬ޲ma+ʴ%Ӵ[gcvѣGy`:wle`q4#=J#ux8:qt@ 6: @`lt@ 6: @`lt@ 6: @`lt0Ϋi2wAXXWۏv(q8: @`lt@ 6: @`lt@ 6: @`lt@ 6: VBK6n.L7K|zcjӋJ.{ټ_={v{8/7GzNq4WU~tELC.N]Yѽ/fJ|,IHî2#pNXΞW$ΗÁkwO_JY~Z2} E5Eaނc-^*WYqU}֯u*V!DuȮU禿zGD=S->}Kgu[(kG[v {o \}5dqOzvt* }-闧'̞928˗ZzD ](W^nh?}[̊l&&kE4gFsZ SIC4Yl(<훸";KpUW%""V,[[p7vVE!W'54S'UVx،9r{Ay5dFJi"Wnp ;2,' u[ڸdڐڠ("6!*{{kϭ~BuohUD43wsfG.I p9l61MbzbȚYUy.+6?YIacFmj"p\8)a8'--1"ҭuk"nepM\2c) /L@ yus -Rc$UqhgNX͓- &ƚ•z<(li,L~֦Mx<,m$2!`pnqu+ArC,32*s^[W^v+CрЉ[]u\ KHף +rSvLpλk͵"rʏ{!"jlfVb}Bږ~O^k1O *tzi,)bg'IsU[UJ97l\rʚufˆUjҝT}ӗ{oո˟v[ű:e"7epn̵kny&. ۰ocZlYy룫-QsIq#Gϕ[hRzlOIM ܖ+hN;Lf X2-TuՇڹngE.d=])>罹*][3vSw},gyyR2ehEe5X5cp܋>۲[Oocڂ=y"Zg-ڔ;/K ~~fDk]txwve[vNY_Tb’3ۤKӕ#ks[-/Y?OлQћ0Z=xh󜉚1$}k `ѦA6;))9\uy'4j 4o^Y]X5|`ω6uwK X1':hQ=c}iQL?}<O|s"u %i}aQ$U e ŨmxYg͎2[BHkCTED ܴafK?GG>f1ĥimM]n6MʎtgNLΘX_ a4yu;' ddX>+䆿O8jˎwg.O N^dJKY*1?%0:s^lOmuڼtz/0Rg.OXBRf=7/bTUED1&Uq+ M4]%2$ZÛLG)"">̘`9౴/LTkn2.I9ԁJSmQOh͕5sg*?ouwi8pIWǤJn2MZ4(k\ayYg^X)3;jF̤K-%ꖥV d#ʩ*4;ÊAUŮi"cC8LjPx'iMdSjV}u>v؎u攛mfp1~Nbٲ¬gvkb0E_Oo]=|y5oLB&47#K ?,߶,"'u v/PM#*ȶa7m"#Th"~Wܙ{ܸa;v vQRv̾(uwsm*ljUK8i n*?cHٝ$"b.at]<9]k ^Y9Y*ۧ_nHUe^t5?~s~:p^>aO%FE_mh}KBLlv _ӱkdo䬌A""V?iiwH*""#{ ն83{ivJ*QĔ]:zmFÜo)"wV̓_zyIBoǹuNuhjȌ^5iչ鯞uTO(\\9e8Pax;IVʌ9"b};{^icD;L}E*e/!/qڪhwv2`ks]b]kEkVՂ͜`ݵ2vRkܷ6 vF-}7ۮ2yF|Į(O$D xniCo R~n$9GCrZ;O pʳ){Wg NUE$wnveGl%W""woaɱO4㌼œhu 8{΅A/_nԀNXMŴ5nQ䯝#{b,0\*5ۻiR;;rB\N%冖q|1A۶_{ag{jKcCKf h28-:b u֘hG4/W Sk ;آ>w㉣6&/QCfDl0W\miMD+S.yݶuzJJy^kkhz4MD4I59i7DWjK`ÇU9\vʠtkNs{{kϭ~BuohoEf9DTq^7]yWꏕEwKS3E2##2 Q~=pa7GȈ\^պ(?iPﳠpNT{=rHXBZ-Rc$UqhgNX͓-_dOE煂ܼ:sWI]yݍQD yR g(""jҝ%/ToṔ]\vbkqo{O~b2O~b@U[\WAͿU?;0+^:V+ܗYZ\[2϶smvǵܷ\v岹˪6["",[V_mKTtj˸r++l?7ѤZϟn/ջ 5徾-c.tvkybtBZƬnH p_lnhF`lt@ 6: @`lt@ 6: @`lt0iڿ|͐Ucǎ5LSLQ+\vZ[[;f̘gN77)4}%J_a7 tjPwiwZVMIN\r We4߸؜Xx0fCoτiѦQ5J*-+QHܪѪsw侳vꍯ3W?󘘘oaQn1*qrZwdoAtS7,s/-s_]_rA5`ֿԫ+h3֛bQ@s7T˕ҢѵJ>(#6?'o߂`;Jw&zW_}~SQ &fijʊԼiqSE5DD":8uൣnE1p_(YF&yKu+kiR#]^;z|pݙhn蚡M jĥB[EQq#y ɩ*.{:'/h͠ac:75yZ[MNSgtjj憂Mngz¶ Zgeĥ-+=5.9=}k[bqjzQkۆ]驫N:-Η.MMM{+ߵmRp"..S&F^Z]Ǵ ' dPCGhh-֬:hkiօ]ekJ4QZe/+~&b=~ڐ|]Sx&"x*mfCEL^ZTÊ917Dֲoúʞå[|R@(yIrۖJmE%? 3Lnsw3ݻEךKӓVBܑday#ߔ 9^>{޲[bn˕H|&-EDeǺm[gŹZkf.uF#W^ V< B-%%SuM]2O d1)$(};NRvJ d΋ iAXHiID3O̭arw3[ |.m4duU+gO+ETg-g*KV.k[{ER&2FFidz\>m;S_=}Lh&"bIZyaݦ)3'\loohnYjEG%䩶'ʚV:\.{=`bM-Vn_E˴ o+>PgNlɪ2!gs]TGb0ZJ֟();f;Jwݷ7(&lL:$gK >zw̉ j3ܲY9Y*ۧ{Z_,U-%|En(u_eYztZC1&up/39miiqd2鐍%iaO^m-Sz1m9vn;&9}Ézz7WRpTU=kB}q^ə &\V|BF|Г9n*BO길7'wouSQ_U1Z""&P}PcfϜP_j]|h} ۹+:M 6{Dm|ɪ8׹}\hhh8{~;dرc}}}#""G|蓅% gl剎G7WEȍҥz񀈈hmӌ"-O}`r޲jv""m +{geV+v7W['rOIUФ囖j_xʚxUDDdMYW_-ϲdƹb97bYL8/vŊߔ2/==|Y3Klz/y8~Ѻ ~ask׮\5M믝;v=$r>8^_fŘ}^}1w (OZlʙ(?U5px޹ ިmq-ؼbEDbҊHî ԺtDzDˊ߷X~IDAT6$-\)JN <0J¢ Iݫ4wv7Co^&,s ս}[k.MO> ңLZq8jˎk);VX2~_ݴ ,TDDB-%%SuM]2UŨ2pjRDVZ O:>[ͽy!%m FIhMw%-;Y<," ՖߜB2bR,qa7F#Rs#E""""(8Сm5_Ge^1;:H1g$[!Ï6#4[mUK&}5=h8,Z3 8]UJX} /T_t ".88Cqʓ2xuv3aG;6MؐǬ- _%憸\zˉrKe*Q='8XD}͙1XҖPzUhUЛ>ˌf]u?I&Cfl9SYrYݫ -ݟ-KLx{.>>}Lhڈn;UPq)ĵQ w\/bԾo)U὜Z )Q6sQФm̎23)~YMRsoC紎wh`tRpT[ԓZseM?ϓz.2,5rۡjǣTR cdvM W/G95٪j:m]:F07Fjq6>/&^hZ̈́4duU+gO+ET&AnzrsPV|ƂY>(ݚIh,"r/??U:D: u60M|$3]){!gF$"psK^1LLۑbM'7OW@T5^v:r_HS`쬜߬egT#{ՖRUwsyTm%"8ʲ`b4)\6ycVoȉWd-[63|I[&lޚuH(~h4]f=U~Ɛ;FP-sC}utѴ {Vu/10B#Zdz%+?WTzkp0V_zePtEl|  &Al66D>Ӎ.8[Hʹ B}7lu`͓;`) 19Omzۿim;$(i^G3WjN:k⒎480m8X_oMْ]]]-,.4΢ӥqp6&z.io N2lQ٩̜4@R ,ݖCΆΧ{H"UyDj{N(jk1*2wHlʂgmfaʠ܂sYSXXjfqfGo L yRI]`Y"s {j!oXj%yX&Cّ?_s8&⽃wʘmgF_SQ:H_]P,^TzmKV:D wF[qDwPrQ^b~ }'/I U%fsg2#ӕ#Yfa:Hu819/m:1 Xfݐ[57+ nM/+38>FhF_l)W"~o꾺\^8jOƱ٘oh=uyCF }th6+lsڨSy&om+ɪ'ObSqxٱ6CyB+:ԧShnE}`aꌣO8xg<+;Z`lxj>5)/%<.95ޟ.J-!peql7@ H 6 p$n `l7@ H 6 p$n ,^EQ_|sV^ UO^R_:NDGE;.-HD$x"ũי?p^}[=Q,iR)GF9b|SB8QS"4 W_}_LݷcaDDۅc/o&a\t*%yO˲jMxwBIC1:DDL*a8B5b*.KcY0q[,=O/r<"A~J/""ѩ|NZv-@ %ӧbk~޳]UGv$$Dm{ΰh`wա+z"MJHKqw@zY7Ҏ75MloRBT˝^]/ʽؚW|υҺO'E/6VDBI7JIzRBD\Rҁ3#8- -KjO_鎄hIWNo0pIƢRTC1f}Lk2oP ]!f}#LW&'!iOI90$$U MWwKp`sSI qFm5燙_qSQ7qvhNGY6o22h2}8-F1ܓW?8LVVy)ϫ>r޼$VI$4UWߛx!Ŷʺx%dbsV;ZfYU]veMɫBQPмUTi!YU%6d;{iޫ\Wq[ۘlc$28*ə:(}ku}7[#*>veL%LbytǤ pOsG1;lj졼+c몊3 ,8e+tg\S,n:0ўS1;af+읗ˎk!"qmlg\enyk$.uh UvVBO:>jn<> &""nifի #'L!2 _z)fk6z{{¥&rqjsu#LOPv[-@$ܭ>\w|z༶̈́t3Q5z(K;&+JNXWd6ժFWP7=:熘7U>So|F<[&#Ƚ<kŝhPx" :s-MvZ Ny\ -'[17Gu7Tq3uz.io N2l~1F_ zo)h$ M+*58lo/2g Jzw02*pɱEڶA4N{4uW-3r H-9յZI(i^GxE~W#7%i "lB#SI226w>^x9:|rGouIM`GPo|ƞ_6ڵz fէ #Uesr`SS!h jk[F)dn@pE< ݻ}ݜUW^vm@@1˥3qu;3fny|=yz#S>:#+͙N^<~A K|/&/1350SEg Jcaaǵқ,sDQ[sQɽCbSJDÈas-ɞx%)&-]t UYs#2^6ԏ X(}j^ңmz>lWOk"Ma9:|ډpx䢼d?'zRm_3=L[z.cdG=57jf`x 5d teigq7b4cYWkeGrn@pUO!sme ҒThmkxd:IlTyTmd/d JI=7DESUuͨj#"ͩhiq)O9szhR?E3ʯn'"ᣢ $"p(Ѡ5\ -͋ ;-bl~å_MC`Ծ}(;R1\yWwUg9Lkho\jb +X̊ sMa٭vY*^* r~Ϗ>~V|d|#ѝ#`pe>n.<ٯ 5U|(Xo&|e*.Og$ۑݹ Ԧӂ!okX2~~WeD_\ю,rd_ɞMjkcbuީNE1uڱ­ԞrϦ(0 WQ*Oe`\jƩ wdmQ+^H8<06WkܹٕliLq ]7D:`YYT eVƸ(_WԉvqH"xl SH|r\%5N9POMuIq Qq Qq q>4ڬ3T^]stج*-?';$nGbzc#0@N$ 0|ܿ` sVXzSruDjN/svXvngAznwgDzyFk%8pD޸~xZjʸ>,uNϙޭWqe+('(Hdz^LTa"VCε&u9l,;s;ϙs=G򢝼p~?cwx`o;_9ޡ-f3#ގ}o9ӂ!okX+2~q>2nJ5k>wl*nHZ9P*9q [~0hQW<&@D#}:Q )Jޡ0ɈcFDqbщƱں9p""U?AW."5oD3H7*Vِӏ,+}ôIڌhs\Yz@Ļu\m gjUخkgfK.ܗ|J;cT7;:V?Zȹ{Y l&;,;s;lΙˍxH^kGsr&a98_}cZ=ʡ-b7#ގ} yѳV`O` XZG@LŹ'G3v[.:wqUOq.YS'rKqGJwNTѲ+*Ih wO=$օcwd&Sxy,D2؅ĥ~w䋋q;?#gq-DD4.u qܑZ$T.d{[j}*t٤쪩s~:0c,f0::.qqInOa*:k=S}9u+µ{:{t]\(gOE>F=zK/2/+s垉a-gta|'tWkfm6S,G3WT\e7uX\oGVqpjJ?mY+MEÈ2)Hz UVυ 퍝MyƇh&sO/j~0)_lM.\*m @\E--)gժ7ϵ(&"aڮpNpЛk6FcDyё7sZ 9Ѧ>`w54HW^錀өܓ=:vKG:6}SȦdOzHv)}m}ذw9GO>?~1qa9<.oY$ t֖(3*c,?7w~ R3YEW#7QJ=ܳjl 3'yη ٙJTs(Gw'>8_4L;u.WvN*o,n8:#Om8?ϙy[SNJ xXw -'[17Gu7Tq[K[}![c3aP3MN\hj\Qah[}A?|p4^}wG #[]m4M4^',KSwE;ó|9s9wZ՞qQ0?2DIDaڪ8X_oMْ]]]-8 Gjea]{ѡG`Q}02ZU88khv,4:BP B ] n'IH4zQ29'D7nԉD\hrGouIM`GPgAdw\Yn4ŬwdJ+ؔpf2MFi@`_6M`|hx{fS4{-LvWjhS=(i^G,+Zw5rD&T _;t/˘?#ގpDawJ5-6<-+؊ F8řq37q>ʼj)w͉q]ߜ#bNlSUs8&⽃w\yg Jcaaǵқ,sDQ[sQɽCbSd?lؾ%ӲΖ3C}gU퐅Tb|Jҗ#wF)Ki*cDߛ|+.]K||F)Rs Reu,ܺܣBEe}rHljc5fFna,o}ݦp*$>ROWv\/Fmu'7&G77^(jfFr!Ɖޔn9-9fu۞:V^ɓ'+T/<=};e'}NAe&k+z=<"Z `9pJx, q? %_&Tge}`%{=c|eR} s 6J=SJ7p˥~6|PfQ"寄g˥h% ۊEY[Ox p$n `l7@ H 6 p$n `l7@ H`͒U__|ŷ~;gիy b{`Xɓg׮UVV7!Pe*FE&M?ۣ؞8rێĝ^/Gd`?~ J/p()pwqåxL)<-KH!XE-2/:Hj 7yq|Gܼfo6y!Jm͵.@gh]eNp.~gdSK^~ooؔ yvz+] ](t$V[]@.xoe]q08bW].߫66_}o:r޼$VUUL]rjCٵ;{(X⺪⌀C5S5ԝ=Rp4U\+T{|?yfvjځĒ_~y׮]nέUYBc󹢛OU/}ڦܓ#;SgyӭCI]: &Sxg~KfB^'y%TF6{?D[{͚5Djժ}{s~{v  #&S>2L$'.;%v_;4NZzLɞGO>^Na ?4 ڵku:J"[Ymh9K7T۷cJ?H$rfk;+sυ&{dWp)ў4|v#T(x X"-KO #:d hi쀀w~wsV^zڵW/6^h7ׅ,ΌS:Q~s_3ߋ93ײ̰c܏VD:t6S(h+7J.KsGzωEm͹;FE&M S~F6ds75iG{NWKo=ڰw9Tv7tVIGBN.4;XbL=}[\Y\{i a!Tٍ;]z.gf'DļS*6iaKΔS*FDrMyC41|jON6ghR?E[[ljHhDžu$QQC8:S΋oOJ2&!b* #}MuTW腱bݲji] JI=7gQ 2FDS K<+xTh+Jxu:$er oY 61 x 2?=Ko^/XkߠN(x(FGX%xI%gY7B~YLerr1&avG ^eښςh6 R0n\{?>pON98OỎzZw"JשIX^l/$'q~2O-y$]*!>Q1/SZ.~gdu%9eP}DDL{}αث'98[{[Ů8uy(9™i ^x6VCΘOI#CsG n潊u9jr^{h ECAeUuە5\S,n:0wdWN՛7U:] n.PkcS_WM7YLaeUݑeGa?p~2q*|)^x6VSęTJd漭]7D:0k;uڱ­ԞrϦ(LRE{Eh"ؼTi9Q^!q4>G+H_ݢVyg`]R\BT\BT\B܁s=G|zmYdsoQT䝯+&ژzg~FoL+]T^][dSH|r\%5N9PO[+RgmR)[5jbs| _MkldP': .u(q#b`r"y`9IƮ'Z9"ka3l E7,T{Z]JdPB11g:C[KL`UQ_$͑rtb4C7?O 6P9 l;dc^rw%gٌs!X%s`HN9񾌈+zUZ(20.5ydƜұi|˦rCzlV7*̟s|? EH0ypsNFtϺ'4M-)4gSpށQyL$o&N{IyDqv>H'zhVjcwG0%,7~Xv yywW=yeF Q]gśÇtL[KD`xo6ߒX+lz0-^`s-Ԥ3Eq#I$ExB}n( ca?܇H:K=iQ|RZa1F͜観R*`X_I{fBRɉz@4Lj;X9\Z: G}^*XJetkwt[SU#]#da`&EL֏6v9VI;9/87x&6&rMXTZSOr}p_V)Ex?WQ Gd949:g`%%؂ynyגWOkd3 YgTܓ#;SgyӭCImCyrrČMTgb*tWk<ȇq4!gSP(72oA`$+-J"GL\$ )=jךt7wG:\I9 nRgDYv\ DmC=YhzMϮ{!M-JD7TRx"-N{%ۭ%ͻ$>)'Lx;\́.p9 l;#[[8WMktYБ;ZI`vvngc2Qs=ej|c0L&V_ ݣ䊀Di_?x=v=x[9 fc=_E-Y)єgk|NUyN[|h~]ąLT_vI%Ηv2ͩd+guvgw9SSo|&K[}`ym #dk^EVC]ظd3犋lRG6wC' 3 &SXV4չ'{u=YzHv)}m}ƕo~}&fTHy$_Wq.K^a=~{3xq.HF'ow)C ކvƼ)n1ct> !+i̮wx|vxM#=hܡvN^fY {_<Ԍ`VHMT3gT-`FhU~oWNܼ7.49֣H6h0ivo !c6 :Sp`Q}02ZU88~]Ɉv_luC-Bh4MCqIGn浪dlR푄B=zKj:G;Ζh<rD$W[S#}lwWv lqQ0?2DIV"LBd]P^}wG #gS;uB!W( 226w>j<&'jtNƙrǩ ,. SH=l5 k5BsI{kpؠ^v9dkʵ=>yuzYHlg,0uV8Zd3-vƼ)-JNF!X%s6;u@o4{>10BFą6՗^>93;4<; 8t>$u!Go >S9Q9wtǨ!)ތ͙N^<~A K|/WA{TS^M|cn{7Ve͍ĩnM/+3?12>熴V%"TZa2"R(h+7J.KcD4|y<,^IDɷrKr9כW_SQ0J&#Lğ, =J̰c܏VD>~F0eMχ ACdw3|Q;QDC%>>ÔA:,׾QU2|ќ({|\StVckϖh[$#[;8w+9u·Co6ٹ(keo"^9Msw)7mrl&?ImJRBNxdL>q$'N$z=<ԗ3`]8;{p#[9y酝0 `E=EVOuQ٧Vrl3oB~Fˏ!kku"{Ǧdlm"qy{[Jop.lB,-w6T?nrMQKS*u+ 4I.R)GR 2mGq,!VA/E~ M]}5b*+Sw7>~?cXz'o?b2¼ 7-m$K14 (_`Q%m.JQM=YM/&"Ҥ# %Q^Sz*=nop 2/#M|:GTzK'H"v4y㶍&~NO{gXv6n,J9# k2KlnIں9p""U?AW.qgSM{PM ib" ?P6jcfY7ҧ3ֽ?izq4-\b&Bwnf޳$7En_VH>@S۟:sOgL9M%]Y@QLյݞ)uqrf \>ˎk!"qm-c XZGMV K/.ʉWOkd;]$k,իW|i'ӜJV]#lK0-P|\".l\`L)єgk|FQ-QfTZnn)vio[HJD4ժFw:#`"W=Ӯ#o) SȤL"aDd >4̱T`=UԢa ?FHKkt6 Y۾+Zw5rh'Lr2Q@XcV`6>~d Le1o0{LG\AQ4M gDu;9-h Ý:YhtR+rBZZNٿc@o0:KoⶨFn1&!</`ƾֻQ M.Ҷ w[pwnΪիW]6 wo}_Y,-(Roۗ6{S|,<ֿ( Q3wx$T^ɟ:[EMޯ>|4NH`r.DqfU:BDmz>lWs~s6}FZrifm~L|]̸༭c teigfKo~t rωEm͹;FE&Mzɳ߫(fov1ܼW㝂ˎM>M4V{Zy6UE4Wcϟ48:J{ ^K[Ks{j>5)/%<.95ޟ_`%xl%4!p$n `l7@ H 6 p$n `2N+GĿG+F'& InzDlO+tspCQ״鮜L^ 0~E?wyʚMH_6|+ͬ&ێD%t/<@lO{+jǙnjҒ^6Hr@sd7v䞼ҷF]p>\] #z>qݧ,v$*wqf|0|Ў#[CQ)S mͲ=N,̓(5US]Uws,ZZrvj Q)o&\WL8kϺhyqQW)Nejy1mndjN,MCWjXݱh~fCuZխD$|Tº}DDvH0&!b<@DDƶ5<2N $ FDU "cD)j^zQJ]eZ0<·j)ߑ_g0Ι:{{Ze3 I[Xt z-e<su6$z)ܼlƥKxlQ5yK'&7*dSi}_S\L C btx'"bR #Yb'$$1 {.ɡa2<(Oe19\3u~"eyon󡆳!\CZwGYCs&κ ʹh_GD.GUg,U^t<+k]:)^Ɉ9ҋ]:Ax&iOVS{4agCI}:)ﶙ~$}!3;H,;gũ7q+Ng)ɤ I30BYQYߩyCJ0&WB,G35:l&m7 :kK[zGEN}WF Gr)z%u5t2(qR""f^n١*[l㽚97'k)V1`7rlc8x[Ds$5pǠQ`8pIƢRThsi fhUNahn  Ǹ*91 m}˟D1@)@͵.@gh]eD$-K?6XUI$WL%bGWLv?ԝNT88T~S>M^71cx3ojgewfmb v##A"a{z׬D}QH=Z/81:5ym>[O)b,ӲV6ɉ0ЖcX%wNN`8{sgӈ}9T8N؟3<0uvޠhvn~4O x473|~#vh r8V.[!9 }>6D$)E*.~=z~룺 ECAeUuە5C*yɣK.5\m6pRK9R. 1o=_9Uo\XWU;7vujF6(V=P [YW*9l&G;{ژzgqCձԀ1ȸF^INiqpWD`  =24]9]=ؖ_YWQ$Lj^ӡۊFO5AOU].)zd3PوKs%8bW].߫66_}O\ <2 *>vew4VSثj}:4U,i1qUrd$[ٺgoW&yPޕuUcjD"6g 7ηJbrf=cum]6W|q;/3yt٥.0𫦛,H SW'.Nn -F1ܓW}3ʹ`eUݑ-fg333S1SSqa-Yeswc;aZF3 9ӹBI9uaHg;[b#a%w8by'`]R\BT[Q)k+w "9٨T(CRǻZK)ڢVȽ"4Jqx`l}01"2c4q$/_x/f= srr~_M*2pFMc\l~NB᫉ D5"tSDzyzHJ f^\qD.ȬlV^0_ KM8]V:V[SH|r\%5N9PO Y4'[S+ܺI)WlJR TwQݡ%an]kUvˢZ̅c\l Qq q>4?S@z$/'X 6q1_/O=~cW"".<+E=p)|*{'oZ{&Pc76)oq#e t{dPB11g:C[K@;496$*$ v$g'JlRϩ=NVɩ٘~\JͩWO-4lnDh}\h˵G;q#Ν`8ys&L[ӈ}9}B8~X3,dD3ݑq}1`|#vJEJ,prOq4l9pAYYk"MSHSQ$9Eѕ۵՟x,LL#zLe8ƑD&W$G&7GDM|٨_LNci{7kbc"bUDctCCVp/qmLqdbJs%mhQW<&Zw;h;sT+4ʻتV# ŶǸ*90 'wYn +@z'Qm0HHo &5[rq >аqvŘt}!b7:N&sq19|P*9Qox{]|6eݔj||ETܐknhgj )wQc-{d1 o#})>q'7߸"M_]0V*K(/ alDݑ[#0H3qRȩc;8X9)<<)^~莂]ۈ$$f^HFę:k=S,=:Lt敎}z%* u|xbO}@vM h=bj+*Ih wOOU[8$sB[\?.#n:poL{x;sR) _iFlnX3쫫:6-sˁ[/mPиǯ;Y+!gHyE7~27eh6O%Y*t֖(3*-noseSIro?ȓlWXudSJO/j~0)_T[ 7ϵ(&ŀmJskl;%lёC^~K;Tʉb)sju:҃vbZ̕c|U ٙJTs(GKDudωz{3S4:uCo8mP1b7sG;ꍝcw@GuL/7EÈOՈ)dR&0usdNѹ-q#n

~d ĀmJ[舜/{ C\|4&!pCR Y~=b[|gx1 o3trDQͱڎ0qRus `ϙ+jp˱[}Ү@o0{坾g:]'Q._g-jZlq|版$RkOɈHD"{Ħ,8v%>>ÔA:_XjfqfGo L yRI]`Y"s {j!oXj%yX&Cّ?_s8&⽃wʘmg9ˇTb|җ#wF)Ā [-tD7g;yH*,1󽘓?3Oğ, = {~\З©_JV]oև=hg,x)Wi$pۻ!*kn$V;)ݐ sy D wF[qDwPrQ^#k1eJ"RM[ZWoy~ 6mc؃oh=uyCF }4'Jd/71˥3qu;3f|=yYushNټ9>8P˯GDÈas-u]l:w1ZٚF8r0S\q x$/ǡ5gyS,o9bչ'N #NFy;IrG;vL~1_; o|~7[{||\k||~iOýQ`W* 2 KآlsFC#zjy>EG_Mm|إ7X{a@R^64Q)p[qc]'LFzE"z;?C^$I'7h\1oH[F,fž"Q$WuIq QqoE{ITXK=TSMM[&bDŚZq.(|sHd]yp /a/6[Ks{͚իW/Tu}BYרL)W(ldDDۥ|\fF n4 ڵku:: CU#-5#^㜮wrZp}< bin{w}7gի׮]p)H:k uRRJGU%z=cͨj#"ͩhÄe ҒTK9",[aA# =& e'(Hdz^LTaigT. `[9#"b {=( |o*q.)<Ѹ8.QM=lj{jxzɱy%kֶ^Iؿ{`n^WOkd9v)U$p3u~rEGk<;#;]rN OHuVfAKsp}:FM鯢Sɜy$_W9~UyNlcy͋1F<`2:t.<עj n1H-QfTZZK5.rLs*JNXWnw[qzLɞ;5g组uڇT_"'jh4S>|P\ґt'^clR푄B=zKj:G;Ζh<rD$W[S#}lwWvk;.`s8Oɉo>ݨt{u.zQ2""^}wG #3 n,&(Pi>>ؗ'" jӽ6*؟s0JW@| )x8ʰ`)B#=#pa)~]#N{N(jk1*2wHlDOFSW=ddGsB,2PQYXt%>>ÔA:(0<ֿ(u0JAYj$|0$K%QKs nȭʚqA_W \ !"sǯ8t>DD'RdDN;QVn4\LjhZexLY{oV[H޹72ږʭJ[,.wu.xX&Cّ K ;=`eA3uD3xgs7;OxDD=_w0mʧlz ZEaSqxٱ}CyB+:}fD 68eSqu352X4\~j>5)/%<.95ޟG `ѐ`~8s 6 p$n `l7@ H 6 p$n `~"z Z`p8 p$n `l7@ H 6AZFp+꒴ H 6 p$n `l7@ H 6d{_l gp߸O䓬lwc?\xFqZ}UJ~z`,I?~}c? "JJ__8_uVdkl_ p` m_K;n|yUT_vVdS(ٞo٫NhʼM+?_[砨Wﳯ:?r_^*w^CIXuFqWP{˪D_YFZdϾ:?7g5GkßŢ6]WDwOٞU?gj9g4=͓=?Z}M\t_|1rwO碯Ǭ+A?ߙ]vM_}bԫRUwOhɻc#{߾?QZKh3>Af߬4b[_~obrhƵ/؎g&lxqIKDdܜwo96.5EQ?l?&ƽy#Jr횎#uPzdv}97-l#Rp󇯿ޚUk|ᯆ&9f%[L//??}͕?'6_o7FDʙ׿j& n~s{p'VMPp9`λᲿ[yMtL}1ND!}2:ym;C?(K[%Ozz!dwfkVɟyU}ozg—_}KvyM$߾}M't3 U/%#Dt#cۯWXSD?u_ѪUKlKߓ""CUDD ؏ ;FD:4[A /kր?EW"z˗W&3+~׷uv8oWR#5wfˢ^* cY|Ԫ|!~}9oTV9|^h_SEּgR|FǟJWR##gĿ_L6ǫ\&y%g3U3]_m_<|͏k>;L6Q6TV9\ڿ?/vee/ o_~kn8[o_B~+U釿1[ާ47_>Ïw87O n?vVnw}R0ٿ/ZYr,dЪKҶvq7@ H 6 p$n `l7X" 6 p$n `l7v int: """Get the node value as integer.""" return int(self.get_node_value(elem, node)) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicefeatures.py000066400000000000000000000012131474575644000273510ustar00rootroot00000000000000"""The feature list class.""" from enum import IntFlag class FritzhomeDeviceFeatures(IntFlag): """The feature list class.""" HANFUN_DEVICE = 0x0001 # bit 0 LIGHTBULB = 0x0004 # bit 2 ALARM = 0x0010 # bit 4 BUTTON = 0x0020 # bit 5 THERMOSTAT = 0x0040 # bit 6 POWER_METER = 0x0080 # bit 7 TEMPERATURE = 0x0100 # bit 8 SWITCH = 0x0200 # bit 9 DECT_REPEATER = 0x0400 # bit 10 MICROPHONE = 0x0800 # bit 11 HANFUN_UNIT = 0x2000 # bit 13 SWITCHABLE = 0x8000 # bit 15 LEVEL = 0x10000 # bit 16 COLOR = 0x20000 # bit 17 BLIND = 0x40000 # bit 18 HUMIDITY = 0x100000 # bit 20 python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicehumidity.py000066400000000000000000000020701474575644000273710ustar00rootroot00000000000000"""The humidity device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceHumidity(FritzhomeDeviceBase): """The Fritzhome Device class.""" rel_humidity = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_humidity_sensor: self._update_humidity_from_node(node) # Humidity @property def has_humidity_sensor(self): """Check if the device has humidity function.""" return self._has_feature(FritzhomeDeviceFeatures.HUMIDITY) def _update_humidity_from_node(self, node): _LOGGER.debug("update humidity device") humidity_element = node.find("humidity") try: self.rel_humidity = self.get_node_value_as_int( humidity_element, "rel_humidity" ) except ValueError: pass python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicelevel.py000066400000000000000000000031771474575644000266550ustar00rootroot00000000000000"""The level device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceLevel(FritzhomeDeviceBase): """The Fritzhome Device class.""" level = None levelpercentage = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_level: self._update_level_from_node(node) # Level @property def has_level(self): """Check if the device has level function.""" return self._has_feature(FritzhomeDeviceFeatures.LEVEL) def _update_level_from_node(self, node): _LOGGER.debug("update level device") levelcontrol_element = node.find("levelcontrol") try: self.level = self.get_node_value_as_int(levelcontrol_element, "level") self.levelpercentage = self.get_node_value_as_int( levelcontrol_element, "levelpercentage" ) except Exception: pass def get_level(self): """Get the level.""" return self.level def get_level_percentage(self): """Get the level in percentage.""" return self.levelpercentage def set_level(self, level, wait=False): """Set the level.""" self._fritz.set_level(self.ain, level, wait) def set_level_percentage(self, levelpercentage, wait=False): """Set the level in percentage.""" self._fritz.set_level_percentage(self.ain, levelpercentage, wait) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicelightbulb.py000066400000000000000000000106021474575644000275110ustar00rootroot00000000000000"""The light bulb device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceLightBulb(FritzhomeDeviceBase): """The Fritzhome Device class.""" state = None hue = None saturation = None unmapped_hue = None unmapped_saturation = None color_temp = None color_mode = None supported_color_mode = None fullcolorsupport: bool = False def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_lightbulb: self._update_lightbulb_from_node(node) # Light Bulb @property def has_lightbulb(self): """Check if the device has LightBulb function.""" return self._has_feature(FritzhomeDeviceFeatures.LIGHTBULB) @property def has_color(self): """Check if the device has LightBulb function.""" return self._has_feature(FritzhomeDeviceFeatures.COLOR) def _update_lightbulb_from_node(self, node): _LOGGER.debug("update light bulb device") state_element = node.find("simpleonoff") try: self.state = self.get_node_value_as_int_as_bool(state_element, "state") except ValueError: pass if self.has_color: colorcontrol_element = node.find("colorcontrol") try: self.color_mode = colorcontrol_element.attrib.get("current_mode") self.supported_color_mode = colorcontrol_element.attrib.get( "supported_modes" ) self.fullcolorsupport = bool( colorcontrol_element.attrib.get("fullcolorsupport") ) except ValueError: pass try: self.hue = self.get_node_value_as_int(colorcontrol_element, "hue") self.saturation = self.get_node_value_as_int( colorcontrol_element, "saturation" ) self.unmapped_hue = self.get_node_value_as_int( colorcontrol_element, "unmapped_hue" ) self.unmapped_saturation = self.get_node_value_as_int( colorcontrol_element, "unmapped_saturation" ) except ValueError: # reset values after color mode changed self.hue = None self.saturation = None self.unmapped_hue = None self.unmapped_saturation = None try: self.color_temp = self.get_node_value_as_int( colorcontrol_element, "temperature" ) except ValueError: # reset values after color mode changed self.color_temp = None def set_state_off(self, wait=False): """Switch light bulb off.""" self.state = True self._fritz.set_state_off(self.ain, wait) def set_state_on(self, wait=False): """Switch light bulb on.""" self.state = True self._fritz.set_state_on(self.ain, wait) def set_state_toggle(self, wait=False): """Toogle light bulb state.""" self.state = True self._fritz.set_state_toggle(self.ain, wait) def get_colors(self): """Get the supported colors.""" if self.has_color: return self._fritz.get_colors(self.ain) else: return {} def set_color(self, hsv, duration=0, wait=False): """Set HSV color.""" if self.has_color: self._fritz.set_color(self.ain, hsv, duration, True, wait) def set_unmapped_color(self, hsv, duration=0, wait=False): """Set unmapped HSV color (Free color selection).""" if self.has_color and self.fullcolorsupport: self._fritz.set_color(self.ain, hsv, duration, False, wait) def get_color_temps(self): """Get the supported color temperatures energy.""" if self.has_color: return self._fritz.get_color_temps(self.ain) else: return [] def set_color_temp(self, temperature, duration=0, wait=False): """Set white color temperature.""" if self.has_color: self._fritz.set_color_temp(self.ain, temperature, duration, wait) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicepowermeter.py000066400000000000000000000033621474575644000277330ustar00rootroot00000000000000"""The powermeter device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDevicePowermeter(FritzhomeDeviceBase): """The Fritzhome Device class.""" power = None energy = None voltage = None current = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_powermeter: self._update_powermeter_from_node(node) # Power Meter @property def has_powermeter(self): """Check if the device has powermeter function.""" return self._has_feature(FritzhomeDeviceFeatures.POWER_METER) def _update_powermeter_from_node(self, node): _LOGGER.debug("update powermeter device") val = node.find("powermeter") try: self.power = int(val.findtext("power")) except Exception: pass try: self.energy = int(val.findtext("energy")) except Exception: pass try: self.voltage = int(val.findtext("voltage")) except Exception: pass if ( isinstance(self.power, int) and isinstance(self.voltage, int) and self.voltage > 0 ): self.current = self.power / self.voltage * 1000 else: self.current = None def get_switch_power(self): """Get the switch state.""" return self._fritz.get_switch_power(self.ain) def get_switch_energy(self): """Get the switch energy.""" return self._fritz.get_switch_energy(self.ain) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicerepeater.py000066400000000000000000000011771474575644000273530ustar00rootroot00000000000000"""The repeater device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceRepeater(FritzhomeDeviceBase): """The Fritzhome Device class.""" def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return # Repeater @property def has_repeater(self): """Check if the device has repeater function.""" return self._has_feature(FritzhomeDeviceFeatures.DECT_REPEATER) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedeviceswitch.py000066400000000000000000000051561474575644000270460ustar00rootroot00000000000000"""The switch device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceSwitch(FritzhomeDeviceBase): """The Fritzhome Device class.""" switch_state = None switch_mode = None lock = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_switch: self._update_switch_from_node(node) # Switch @property def has_switch(self): """Check if the device has switch function.""" if self._has_feature(FritzhomeDeviceFeatures.SWITCH): # for AVM plugs like FRITZ!DECT 200 and FRITZ!DECT 210 return True if self._has_feature( FritzhomeDeviceFeatures.SWITCHABLE ) and not self._has_feature(FritzhomeDeviceFeatures.LIGHTBULB): # for HAN-FUN plugs return True return False def _update_switch_from_node(self, node): _LOGGER.debug("update switch device") if self._has_feature(FritzhomeDeviceFeatures.SWITCH): val = node.find("switch") try: self.switch_state = self.get_node_value_as_int_as_bool(val, "state") except Exception: self.switch_state = None self.switch_mode = self.get_node_value(val, "mode") try: self.lock = self.get_node_value_as_int_as_bool(val, "lock") except Exception: self.lock = None # optional value try: self.device_lock = self.get_node_value_as_int_as_bool(val, "devicelock") except Exception: pass else: val = node.find("simpleonoff") try: self.switch_state = self.get_node_value_as_int_as_bool(val, "state") except Exception: self.switch_state = None def get_switch_state(self): """Get the switch state.""" return self._fritz.get_switch_state(self.ain) def set_switch_state_on(self, wait=False): """Set the switch state to on.""" return self._fritz.set_switch_state_on(self.ain, wait) def set_switch_state_off(self, wait=False): """Set the switch state to off.""" return self._fritz.set_switch_state_off(self.ain, wait) def set_switch_state_toggle(self, wait=False): """Toggle the switch state.""" return self._fritz.set_switch_state_toggle(self.ain, wait) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicetemperature.py000066400000000000000000000024501474575644000300740ustar00rootroot00000000000000"""The temperature device class.""" # -*- coding: utf-8 -*- import logging from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceTemperature(FritzhomeDeviceBase): """The Fritzhome Device class.""" offset = None temperature = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_temperature_sensor: self._update_temperature_from_node(node) # Temperature @property def has_temperature_sensor(self): """Check if the device has temperature function.""" return self._has_feature(FritzhomeDeviceFeatures.TEMPERATURE) def _update_temperature_from_node(self, node): _LOGGER.debug("update temperature device") temperature_element = node.find("temperature") try: self.offset = ( self.get_node_value_as_int(temperature_element, "offset") / 10.0 ) except ValueError: pass try: self.temperature = ( self.get_node_value_as_int(temperature_element, "celsius") / 10.0 ) except ValueError: pass python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomedevicethermostat.py000066400000000000000000000143421474575644000277340ustar00rootroot00000000000000"""The thermostat device class.""" # -*- coding: utf-8 -*- import logging import time from .fritzhomedevicebase import FritzhomeDeviceBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeDeviceThermostat(FritzhomeDeviceBase): """The Fritzhome Device class.""" actual_temperature = None target_temperature = None eco_temperature = None comfort_temperature = None device_lock = None lock = None error_code = None window_open = None window_open_endtime = None boost_active = None boost_active_endtime = None adaptive_heating_active = None adaptive_heating_running = None summer_active = None holiday_active = None nextchange_endperiod = None nextchange_temperature = None def _update_from_node(self, node): super()._update_from_node(node) if self.present is False: return if self.has_thermostat: self._update_hkr_from_node(node) # Thermostat @property def has_thermostat(self): """Check if the device has thermostat function.""" return self._has_feature(FritzhomeDeviceFeatures.THERMOSTAT) def _update_hkr_from_node(self, node): _LOGGER.debug("update thermostat device") hkr_element = node.find("hkr") try: self.actual_temperature = self.get_temp_from_node(hkr_element, "tist") except ValueError: pass try: self.target_temperature = self.get_temp_from_node(hkr_element, "tsoll") except ValueError: self.target_temperature = None self.eco_temperature = self.get_temp_from_node(hkr_element, "absenk") self.comfort_temperature = self.get_temp_from_node(hkr_element, "komfort") # optional value try: self.device_lock = self.get_node_value_as_int_as_bool( hkr_element, "devicelock" ) self.lock = self.get_node_value_as_int_as_bool(hkr_element, "lock") self.error_code = self.get_node_value_as_int(hkr_element, "errorcode") # keep battery values as fallback for Fritz!OS < 7.08 if hkr_element.find("batterylow") is not None: self.battery_low = self.get_node_value_as_int_as_bool( hkr_element, "batterylow" ) self.battery_level = int( self.get_node_value_as_int(hkr_element, "battery") ) self.window_open = self.get_node_value_as_int_as_bool( hkr_element, "windowopenactiv" ) self.window_open_endtime = ( self.get_node_value_as_int(hkr_element, "windowopenactiveendtime") - time.time() ) if self.window_open_endtime < 0: self.window_open_endtime = 0 self.summer_active = self.get_node_value_as_int_as_bool( hkr_element, "summeractive" ) self.holiday_active = self.get_node_value_as_int_as_bool( hkr_element, "holidayactive" ) nextchange_element = hkr_element.find("nextchange") self.nextchange_endperiod = int( self.get_node_value_as_int(nextchange_element, "endperiod") ) self.nextchange_temperature = self.get_temp_from_node( nextchange_element, "tchange" ) if hkr_element.find("boostactive") is not None: self.boost_active = self.get_node_value_as_int_as_bool( hkr_element, "boostactive" ) self.boost_active_endtime = ( self.get_node_value_as_int(hkr_element, "boostactiveendtime") - time.time() ) if self.boost_active_endtime < 0: self.boost_active_endtime = 0 if hkr_element.find("adaptiveHeatingActive") is not None: self.adaptive_heating_active = self.get_node_value_as_int_as_bool( hkr_element, "adaptiveHeatingActive" ) self.adaptive_heating_running = self.get_node_value_as_int_as_bool( hkr_element, "adaptiveHeatingRunning" ) except Exception: pass def get_temperature(self): """Get the device temperature value.""" return self._fritz.get_temperature(self.ain) def get_target_temperature(self): """Get the thermostate target temperature.""" return self._fritz.get_target_temperature(self.ain) def set_target_temperature(self, temperature, wait=False): """Set the thermostate target temperature.""" return self._fritz.set_target_temperature(self.ain, temperature, wait) def set_window_open(self, seconds, wait=False): """Set the thermostate to window open.""" return self._fritz.set_window_open(self.ain, seconds, wait) def set_boost_mode(self, seconds, wait=False): """Set the thermostate into boost mode.""" return self._fritz.set_boost_mode(self.ain, seconds, wait) def get_comfort_temperature(self): """Get the thermostate comfort temperature.""" return self._fritz.get_comfort_temperature(self.ain) def get_eco_temperature(self): """Get the thermostate eco temperature.""" return self._fritz.get_eco_temperature(self.ain) def get_hkr_state(self): """Get the thermostate state.""" try: return { 126.5: "off", 127.0: "on", self.eco_temperature: "eco", self.comfort_temperature: "comfort", }[self.target_temperature] except KeyError: return "manual" def set_hkr_state(self, state, wait=False): """Set the state of the thermostat. Possible values for state are: 'on', 'off', 'comfort', 'eco'. """ try: value = { "off": 0, "on": 100, "eco": self.eco_temperature, "comfort": self.comfort_temperature, }[state] except KeyError: return self.set_target_temperature(value, wait) python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhomeentitybase.py000066400000000000000000000046041474575644000265310ustar00rootroot00000000000000"""The entity base class.""" # -*- coding: utf-8 -*- from __future__ import print_function from abc import ABC import logging from xml.etree import ElementTree from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeEntityBase(ABC): """The Fritzhome Entity class.""" _fritz = None ain: str = None _functionsbitmask = None supported_features = None def __init__(self, fritz=None, node=None): """Create an entity base object.""" if fritz is not None: self._fritz = fritz if node is not None: self._update_from_node(node) def __repr__(self): """Return a string.""" return "{ain} {name}".format( ain=self.ain, name=self.name, ) def _has_feature(self, feature: FritzhomeDeviceFeatures) -> bool: return feature in FritzhomeDeviceFeatures(self._functionsbitmask) def _update_from_node(self, node): _LOGGER.debug(ElementTree.tostring(node)) self.ain = node.attrib["identifier"] self._functionsbitmask = int(node.attrib["functionbitmask"]) self.name = node.findtext("name").strip() self.supported_features = [] for feature in FritzhomeDeviceFeatures: if self._has_feature(feature): self.supported_features.append(feature) @property def device_and_unit_id(self): """Get the device and possible unit id.""" if self.ain.startswith("tmp") or self.ain.startswith("grp"): return (self.ain, None) elif self.ain.startswith("Z") and len(self.ain) == 19: return (self.ain[0:17], self.ain[17:]) elif "-" in self.ain: return tuple(self.ain.split("-")) return (self.ain, None) # XML Helpers def get_node_value(self, elem, node): """Get the node value.""" return elem.findtext(node) def get_node_value_as_int(self, elem, node) -> int: """Get the node value as integer.""" return int(self.get_node_value(elem, node)) def get_node_value_as_int_as_bool(self, elem, node) -> bool: """Get the node value as boolean.""" return bool(self.get_node_value_as_int(elem, node)) def get_temp_from_node(self, elem, node): """Get the node temp value as float.""" return float(self.get_node_value(elem, node)) / 2 python-fritzhome-0.6.14/pyfritzhome/devicetypes/fritzhometemplate.py000066400000000000000000000032051474575644000261710ustar00rootroot00000000000000"""The template class.""" # -*- coding: utf-8 -*- import logging from .fritzhomeentitybase import FritzhomeEntityBase from .fritzhomedevicefeatures import FritzhomeDeviceFeatures _LOGGER = logging.getLogger(__name__) class FritzhomeTemplate(FritzhomeEntityBase): """The Fritzhome Template class.""" devices = None features = None apply_hkr_summer = None apply_hkr_temperature = None apply_hkr_holidays = None apply_hkr_time_table = None apply_relay_manual = None apply_relay_automatic = None apply_level = None apply_color = None apply_dialhelper = None def _update_from_node(self, node): _LOGGER.debug("update template") super()._update_from_node(node) self.features = FritzhomeDeviceFeatures(self._functionsbitmask) applymask = node.find("applymask") self.apply_hkr_summer = applymask.find("hkr_summer") is not None self.apply_hkr_temperature = applymask.find("hkr_temperature") is not None self.apply_hkr_holidays = applymask.find("hkr_holidays") is not None self.apply_hkr_time_table = applymask.find("hkr_time_table") is not None self.apply_relay_manual = applymask.find("relay_manual") is not None self.apply_relay_automatic = applymask.find("relay_automatic") is not None self.apply_level = applymask.find("level") is not None self.apply_color = applymask.find("color") is not None self.apply_dialhelper = applymask.find("dialhelper") is not None self.devices = [] for device in node.find("devices").findall("device"): self.devices.append(device.attrib["identifier"]) python-fritzhome-0.6.14/pyfritzhome/errors.py000066400000000000000000000011311474575644000214120ustar00rootroot00000000000000"""Project specific exceptions.""" class LoginError(Exception): """The LoginError Exception.""" def __init__(self, user): """Initialize the an loginError.""" self.user = user def __str__(self): """Return the error.""" return 'login for user="{}" failed'.format(self.user) class NotLoggedInError(Exception): """The NotLoggedInError Exception.""" def __str__(self): """Return the error.""" return "not logged in, login before doing any requests." class InvalidError(Exception): """The InvalidError Exception.""" pass python-fritzhome-0.6.14/pyfritzhome/fritzhome.py000066400000000000000000000434331474575644000221200ustar00rootroot00000000000000"""The main fritzhome handling class.""" # -*- coding: utf-8 -*- from __future__ import print_function import hashlib import logging import time from xml.etree import ElementTree from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from requests import Session from .errors import InvalidError, LoginError, NotLoggedInError from .fritzhomedevice import FritzhomeDevice from .fritzhomedevice import FritzhomeTemplate from typing import Dict _LOGGER = logging.getLogger(__name__) class Fritzhome(object): """Fritzhome object to communicate with the device.""" _sid = None _session = None _devices: Dict[str, FritzhomeDevice] = None _templates: Dict[str, FritzhomeTemplate] = None def __init__(self, host, user, password, ssl_verify=True): """Create a fritzhome object.""" self._host = host self._user = user self._password = password self._session = Session() self._ssl_verify = ssl_verify def _request(self, url, params=None, timeout=10): """Send a request with parameters.""" rsp = self._session.get( url, params=params, timeout=timeout, verify=self._ssl_verify ) rsp.raise_for_status() return rsp.text.strip() def _login_request(self, username=None, secret=None): """Send a login request with paramerters.""" url = self.get_prefixed_host() + "/login_sid.lua?version=2" params = {} if username: params["username"] = username if secret: params["response"] = secret plain = self._request(url, params) dom = ElementTree.fromstring(plain) sid = dom.findtext("SID") blocktime = int(dom.findtext("BlockTime")) challenge = dom.findtext("Challenge") return (sid, challenge, blocktime) def _logout_request(self): """Send a logout request.""" _LOGGER.debug("logout") url = self.get_prefixed_host() + "/login_sid.lua" params = {"security:command/logout": "1", "sid": self._sid} self._request(url, params) @staticmethod def _create_login_secrete_pbkdf2(challenge, password): challenge_parts = challenge.split("$") # Extract all necessary values encoded into the challenge iter1 = int(challenge_parts[1]) salt1 = bytes.fromhex(challenge_parts[2]) iter2 = int(challenge_parts[3]) salt2 = bytes.fromhex(challenge_parts[4]) # Hash twice, once with static salt... # hash1 = hashlib.pbkdf2_hmac("sha256", password.encode(), salt1, iter1) kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt1, iterations=iter1 ) hash1 = kdf.derive(password.encode()) # Once with dynamic salt. # hash2 = hashlib.pbkdf2_hmac("sha256", hash1, salt2, iter2) kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt2, iterations=iter2 ) hash2 = kdf.derive(hash1) return f"{challenge_parts[4]}${hash2.hex()}" @staticmethod def _create_login_secret_md5(challenge, password): """Create a login secret.""" to_hash = (challenge + "-" + password).encode("UTF-16LE") hashed = hashlib.md5(to_hash).hexdigest() return "{0}-{1}".format(challenge, hashed) def _aha_request(self, cmd, ain=None, param=None, rf=str): """Send an AHA request.""" url = self.get_prefixed_host() + "/webservices/homeautoswitch.lua" _LOGGER.debug("self._sid:%s", self._sid) if not self._sid: raise NotLoggedInError params = {"switchcmd": cmd, "sid": self._sid} if param: params.update(param) if ain: params["ain"] = ain plain = self._request(url, params) if plain == "inval": raise InvalidError if rf == bool: return bool(int(plain)) return rf(plain) def login(self): """Login and get a valid session ID.""" (sid, challenge, blocktime) = self._login_request() _LOGGER.info("sid:%s, challenge:%s, blocktime:%s", sid, challenge, blocktime) if sid == "0000000000000000": if blocktime > 0: time.sleep(blocktime) # PBKDF2 (FRITZ!OS 7.24 or later) if challenge.startswith("2$"): secret = self._create_login_secrete_pbkdf2(challenge, self._password) # fallback to MD5 else: secret = self._create_login_secret_md5(challenge, self._password) (sid2, challenge, _) = self._login_request( username=self._user, secret=secret ) if sid2 == "0000000000000000": _LOGGER.warning("login failed %s", sid2) raise LoginError(self._user) self._sid = sid2 def logout(self): """Logout.""" self._logout_request() self._sid = None def get_prefixed_host(self): """Choose the correct protocol prefix for the host. Supports three input formats: - https://(requests use strict certificate validation by default) - http:// (unecrypted) - (unencrypted) """ host = self._host if host.startswith("https://") or host.startswith("http://"): return host else: return "http://" + host def update_devices(self, ignore_removed=True): """Update the device.""" _LOGGER.info("Updating Devices ...") if self._devices is None: self._devices = {} device_elements = self.get_device_elements() for element in device_elements: if element.attrib["identifier"] in self._devices.keys(): _LOGGER.info( "Updating already existing Device " + element.attrib["identifier"] ) self._devices[element.attrib["identifier"]]._update_from_node(element) else: _LOGGER.info("Adding new Device " + element.attrib["identifier"]) device = FritzhomeDevice(self, node=element) self._devices[device.ain] = device if not ignore_removed: for identifier in list(self._devices.keys()): if identifier not in [ element.attrib["identifier"] for element in device_elements ]: _LOGGER.info("Removing no more existing device " + identifier) self._devices.pop(identifier) return True def _get_listinfo_elements(self, entity_type): """Get the DOM elements for the entity list.""" plain = self._aha_request("get" + entity_type + "listinfos") dom = ElementTree.fromstring(plain) _LOGGER.debug(dom) return dom.findall("*") def wait_device_txbusy(self, ain, retries=10): """Wait for device to finish command execution.""" for _ in range(retries): plain = self.get_device_infos(ain) dom = ElementTree.fromstring(plain) txbusy = dom.findall("txbusy") if txbusy[0].text == "0": return True time.sleep(0.2) return False def get_device_elements(self): """Get the DOM elements for the device list.""" return self._get_listinfo_elements("device") def get_device_element(self, ain): """Get the DOM element for the specified device.""" elements = self.get_device_elements() for element in elements: if element.attrib["identifier"] == ain: return element return None def get_devices(self): """Get the list of all known devices.""" return list(self.get_devices_as_dict().values()) def get_devices_as_dict(self): """Get the list of all known devices.""" if self._devices is None: self.update_devices() return self._devices def get_device_by_ain(self, ain): """Return a device specified by the AIN.""" return self.get_devices_as_dict()[ain] def get_device_infos(self, ain): """Get the device infos.""" return self._aha_request("getdeviceinfos", ain=ain) def get_device_present(self, ain): """Get the device presence.""" return self._aha_request("getswitchpresent", ain=ain, rf=bool) def get_device_name(self, ain): """Get the device name.""" return self._aha_request("getswitchname", ain=ain) def get_switch_state(self, ain): """Get the switch state.""" return self._aha_request("getswitchstate", ain=ain, rf=bool) def set_switch_state_on(self, ain, wait=False): """Set the switch to on state.""" result = self._aha_request("setswitchon", ain=ain, rf=bool) wait and self.wait_device_txbusy(ain) return result def set_switch_state_off(self, ain, wait=False): """Set the switch to off state.""" result = self._aha_request("setswitchoff", ain=ain, rf=bool) wait and self.wait_device_txbusy(ain) return result def set_switch_state_toggle(self, ain, wait=False): """Toggle the switch state.""" result = self._aha_request("setswitchtoggle", ain=ain, rf=bool) wait and self.wait_device_txbusy(ain) return result def get_switch_power(self, ain): """Get the switch power consumption.""" return self._aha_request("getswitchpower", ain=ain, rf=int) def get_switch_energy(self, ain): """Get the switch energy.""" return self._aha_request("getswitchenergy", ain=ain, rf=int) def get_temperature(self, ain): """Get the device temperature sensor value.""" return self._aha_request("gettemperature", ain=ain, rf=float) / 10.0 def _get_temperature(self, ain, name): plain = self._aha_request(name, ain=ain, rf=float) return (plain - 16) / 2 + 8 def get_target_temperature(self, ain): """Get the thermostate target temperature.""" return self._get_temperature(ain, "gethkrtsoll") def set_target_temperature(self, ain, temperature, wait=False): """Set the thermostate target temperature.""" temp = int(16 + ((float(temperature) - 8) * 2)) if temp < min(range(16, 56)): temp = 253 elif temp > max(range(16, 56)): temp = 254 self._aha_request("sethkrtsoll", ain=ain, param={"param": temp}) wait and self.wait_device_txbusy(ain) def set_window_open(self, ain, seconds, wait=False): """Set the thermostate target temperature.""" endtimestamp = int(time.time() + seconds) self._aha_request( "sethkrwindowopen", ain=ain, param={"endtimestamp": endtimestamp} ) wait and self.wait_device_txbusy(ain) def set_boost_mode(self, ain, seconds, wait=False): """Set the thermostate to boost mode.""" endtimestamp = int(time.time() + seconds) self._aha_request("sethkrboost", ain=ain, param={"endtimestamp": endtimestamp}) wait and self.wait_device_txbusy(ain) def get_comfort_temperature(self, ain): """Get the thermostate comfort temperature.""" return self._get_temperature(ain, "gethkrkomfort") def get_eco_temperature(self, ain): """Get the thermostate eco temperature.""" return self._get_temperature(ain, "gethkrabsenk") def get_device_statistics(self, ain): """Get device statistics.""" plain = self._aha_request("getbasicdevicestats", ain=ain) return plain # Lightbulb-related commands def set_state_off(self, ain, wait=False): """Set the switch/actuator/lightbulb to on state.""" self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 0}) wait and self.wait_device_txbusy(ain) def set_state_on(self, ain, wait=False): """Set the switch/actuator/lightbulb to on state.""" self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 1}) wait and self.wait_device_txbusy(ain) def set_state_toggle(self, ain, wait=False): """Toggle the switch/actuator/lightbulb state.""" self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 2}) wait and self.wait_device_txbusy(ain) def set_level(self, ain, level, wait=False): """Set level/brightness/height in interval [0,255].""" if level < 0: level = 0 # 0% elif level > 255: level = 255 # 100 % self._aha_request("setlevel", ain=ain, param={"level": int(level)}) wait and self.wait_device_txbusy(ain) def set_level_percentage(self, ain, level, wait=False): """Set level/brightness/height in interval [0,100].""" if level < 0: level = 0 elif level > 100: level = 100 self._aha_request("setlevelpercentage", ain=ain, param={"level": int(level)}) wait and self.wait_device_txbusy(ain) def _get_colordefaults(self, ain): plain = self._aha_request("getcolordefaults", ain=ain) return ElementTree.fromstring(plain) def get_colors(self, ain): """Get colors (HSV-space) supported by this lightbulb.""" colordefaults = self._get_colordefaults(ain) colors = {} for hs in colordefaults.iter("hs"): name = hs.find("name").text.strip() values = [] for st in hs.iter("color"): values.append((st.get("hue"), st.get("sat"), st.get("val"))) colors[name] = values return colors def set_color(self, ain, hsv, duration=0, mapped=True, wait=False): """Set hue and saturation. hsv: HUE colorspace element obtained from get_colors() duration: Speed of change in seconds, 0 = instant """ params = { "hue": int(hsv[0]), "saturation": int(hsv[1]), "duration": int(duration) * 10, } if mapped: self._aha_request("setcolor", ain=ain, param=params) else: # available since Fritz!OS 7.39 self._aha_request("setunmappedcolor", ain=ain, param=params) wait and self.wait_device_txbusy(ain) def get_color_temps(self, ain): """Get temperatures supported by this lightbulb.""" colordefaults = self._get_colordefaults(ain) temperatures = [] for temp in colordefaults.iter("temp"): temperatures.append(temp.get("value")) return temperatures def set_color_temp(self, ain, temperature, duration=0, wait=False): """Set color temperature. temperature: temperature element obtained from get_temperatures() duration: Speed of change in seconds, 0 = instant """ params = {"temperature": int(temperature), "duration": int(duration) * 10} self._aha_request("setcolortemperature", ain=ain, param=params) wait and self.wait_device_txbusy(ain) # blinds # states: open, close, stop def _set_blind_state(self, ain, state): self._aha_request("setblind", ain=ain, param={"target": state}) def set_blind_open(self, ain, wait=False): """Set the blind state to open.""" self._set_blind_state(ain, "open") wait and self.wait_device_txbusy(ain) def set_blind_close(self, ain, wait=False): """Set the blind state to close.""" self._set_blind_state(ain, "close") wait and self.wait_device_txbusy(ain) def set_blind_stop(self, ain, wait=False): """Set the blind state to stop.""" self._set_blind_state(ain, "stop") wait and self.wait_device_txbusy(ain) # Template-related commands def has_templates(self): """Check if the Fritz!Box supports smarthome templates.""" plain = self._aha_request("gettemplatelistinfos") try: ElementTree.fromstring(plain) except ElementTree.ParseError: return False return True def update_templates(self, ignore_removed=True): """Update the template.""" _LOGGER.info("Updating Templates ...") if self._templates is None: self._templates = {} template_elements = self.get_template_elements() for element in template_elements: if element.attrib["identifier"] in self._templates.keys(): _LOGGER.info( "Updating already existing Template " + element.attrib["identifier"] ) self._templates[element.attrib["identifier"]]._update_from_node(element) else: _LOGGER.info("Adding new Template " + element.attrib["identifier"]) template = FritzhomeTemplate(self, node=element) self._templates[template.ain] = template if not ignore_removed: for identifier in list(self._templates.keys()): if identifier not in [ element.attrib["identifier"] for element in template_elements ]: _LOGGER.info("Removing no more existing template " + identifier) self._templates.pop(identifier) return True def get_template_elements(self): """Get the DOM elements for the template list.""" return self._get_listinfo_elements("template") def get_templates(self): """Get the list of all known templates.""" return list(self.get_templates_as_dict().values()) def get_templates_as_dict(self): """Get the list of all known templates.""" if self._templates is None: self.update_templates() return self._templates def get_template_by_ain(self, ain): """Return a template specified by the AIN.""" return self.get_templates_as_dict()[ain] def apply_template(self, ain): """Appliy a template.""" self._aha_request("applytemplate", ain=ain) python-fritzhome-0.6.14/pyfritzhome/fritzhomedevice.py000066400000000000000000000020311474575644000232650ustar00rootroot00000000000000"""Toplevel device for pyfritzhome.""" # -*- coding: utf-8 -*- from .devicetypes import FritzhomeTemplate # noqa: F401 from .devicetypes import ( FritzhomeDeviceAlarm, FritzhomeDeviceBlind, FritzhomeDeviceButton, FritzhomeDeviceHumidity, FritzhomeDeviceLevel, FritzhomeDeviceLightBulb, FritzhomeDevicePowermeter, FritzhomeDeviceRepeater, FritzhomeDeviceSwitch, FritzhomeDeviceTemperature, FritzhomeDeviceThermostat, ) class FritzhomeDevice( FritzhomeDeviceAlarm, FritzhomeDeviceBlind, FritzhomeDeviceButton, FritzhomeDeviceHumidity, FritzhomeDeviceLevel, FritzhomeDeviceLightBulb, FritzhomeDevicePowermeter, FritzhomeDeviceRepeater, FritzhomeDeviceSwitch, FritzhomeDeviceTemperature, FritzhomeDeviceThermostat, ): """The Fritzhome Device class.""" def __init__(self, fritz=None, node=None): """Create a device object.""" super().__init__(fritz, node) def _update_from_node(self, node): super()._update_from_node(node) python-fritzhome-0.6.14/setup.cfg000066400000000000000000000030501474575644000167670ustar00rootroot00000000000000[metadata] name = pyfritzhome version = 0.6.14 description = Fritz!Box Smarthome Python Library long_description = file: README.rst long_description_content_type = text/x-rst url = http://github.com/hthiery/python-fritzhome author = Heiko Thiery author_email = heiko.thiery@gmail.com license = MIT license_file = LICENSE classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy [options] packages = find: include_package_data = true test_suite = tests setup_requires = setuptools install_requires = requests; cryptography tests_requires = pytest [options.entry_points] console_scripts = fritzhome=pyfritzhome.cli:main [options.packages.find] exclude = tests* testing* [flake8] max-line-length = 88 exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ [tool:pytest] pythonpath = . testpaths = tests norecursedirs = .git addopts = --strict-markers [coverage:run] omit = pyfritzhome/cli.py */python?.?/* */site-packages/nose/* */tests/* */pyasn1/* */ndg/* */doc/* [pep257] ignore = D203 match-dir = pyfritzhome/* [pydocstyle] ignore = D203,D213 match-dir = pyfritzhome/* python-fritzhome-0.6.14/setup.py000066400000000000000000000001401474575644000166550ustar00rootroot00000000000000"""The setup of the project.""" # -*- coding: utf-8 -*- from setuptools import setup setup() python-fritzhome-0.6.14/tests/000077500000000000000000000000001474575644000163125ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/__init__.py000066400000000000000000000000371474575644000204230ustar00rootroot00000000000000"""Init file for the tests.""" python-fritzhome-0.6.14/tests/helper.py000066400000000000000000000010731474575644000201440ustar00rootroot00000000000000import logging _LOGGER = logging.getLogger(__name__) class Helper(object): __responses = {} @staticmethod def response(filename: str) -> str: if filename not in Helper.__responses: with open( "tests/responses/" + filename + ".xml", "r", encoding="UTF-8" ) as file: _LOGGER.debug(f"{filename} not cached yet. Adding to cache.") Helper.__responses[filename] = file.read() _LOGGER.debug(f"Returning response for {filename} ") return Helper.__responses[filename] python-fritzhome-0.6.14/tests/responses/000077500000000000000000000000001474575644000203335ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/alarm/000077500000000000000000000000001474575644000214275ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/alarm/device_alert_no_alertstate.xml000066400000000000000000000007161474575644000275270ustar00rootroot00000000000000 1 Fenster 406 514 256 python-fritzhome-0.6.14/tests/responses/alarm/device_alert_off.xml000066400000000000000000000007641474575644000254400ustar00rootroot00000000000000 1 Fenster 406 514 256 0 python-fritzhome-0.6.14/tests/responses/alarm/device_alert_on.xml000066400000000000000000000007641474575644000253020ustar00rootroot00000000000000 1 Fenster 406 514 256 1 python-fritzhome-0.6.14/tests/responses/alarm/device_magenta_smoke_alarm.xml000066400000000000000000000007601474575644000274610ustar00rootroot00000000000000 1 Rauchmelder 406 516 256 python-fritzhome-0.6.14/tests/responses/base/000077500000000000000000000000001474575644000212455ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/base/device_list.xml000066400000000000000000000051271474575644000242660ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 0 707 285 0 1 FRITZ!DECT Rep 100 #1 288 0 1 Badezimmer 205 -15 41 36 36 42 0 0 0 0 1508342400 42 1 Fenster 406 514 256 1 1 Gruppe 1 auto 0 17 python-fritzhome-0.6.14/tests/responses/base/device_list_removed_device.xml000066400000000000000000000042331474575644000273230ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 0 707 285 0 1 FRITZ!DECT Rep 100 #1 288 0 1 Badezimmer 205 -15 41 36 36 42 0 0 0 0 1508342400 42 1 Gruppe 1 auto 0 17 python-fritzhome-0.6.14/tests/responses/base/device_no_devicelock_element.xml000066400000000000000000000013131474575644000276210ustar00rootroot00000000000000 1 FRITZ!DECT 200 #1 1 manuell 1 114580 87830 220 0 python-fritzhome-0.6.14/tests/responses/base/device_not_present.xml000066400000000000000000000013231474575644000256450ustar00rootroot00000000000000 0 Kitchen 0 0 0 255 python-fritzhome-0.6.14/tests/responses/base/device_not_txbusy.xml000066400000000000000000000011531474575644000255240ustar00rootroot00000000000000 0 0 Kitchen 0 0 0 255 python-fritzhome-0.6.14/tests/responses/base/device_txbusy.xml000066400000000000000000000011531474575644000246440ustar00rootroot00000000000000 0 1 Kitchen 0 0 0 255 python-fritzhome-0.6.14/tests/responses/base/device_with_umlaut_in_name.xml000066400000000000000000000013001474575644000273300ustar00rootroot00000000000000 1 äöü 1 manuell 1 114580 87830 220 0 python-fritzhome-0.6.14/tests/responses/blind/000077500000000000000000000000001474575644000214235ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/blind/device_blind_rollotron1213.xml000066400000000000000000000021301474575644000271710ustar00rootroot00000000000000 1 0 Wohnzimmer 1 0 Wohnzimmer 1 manuell 252 99 406 281 256,513,516,517 0 1645029180 python-fritzhome-0.6.14/tests/responses/button/000077500000000000000000000000001474575644000216465ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/button/device_button_fritzdect440.xml000066400000000000000000000046521474575644000275370ustar00rootroot00000000000000 1 0 Taster Wohnzimmer 100 0 215 0 1 0 Taster Schlafzimmer 100 0 220 0 python-fritzhome-0.6.14/tests/responses/button/device_button_fritzdect440_fw_05_10.xml000066400000000000000000000021521474575644000310300ustar00rootroot00000000000000 1 0 Taster Wohnzimmer 100 0 215 0 44 python-fritzhome-0.6.14/tests/responses/groups/000077500000000000000000000000001474575644000216525ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/groups/device_list_thermostat.xml000066400000000000000000000063771474575644000271550ustar00rootroot00000000000000 1 0 Wohnzimmer Couch 80 0 220 -10 44 43 35 43 0 0 0 0 0 0 0 0 80 0 43 0 0 1 0 1 0 Wohnzimmer Tisch 80 0 220 -10 44 43 35 43 0 0 0 0 0 0 0 0 80 0 43 0 0 1 0 1 0 Wohnzimmer 43 35 43 0 0 0 0 0 0 0 0 43 0 0 1 0 0 16,17 python-fritzhome-0.6.14/tests/responses/groups/device_list_thermostat_without_tsoll.xml000066400000000000000000000063661474575644000321530ustar00rootroot00000000000000 1 0 Wohnzimmer Couch 80 0 220 -10 44 43 35 43 0 0 0 0 0 0 0 0 80 0 43 0 0 1 0 1 0 Wohnzimmer Tisch 80 0 220 -10 44 43 35 43 0 0 0 0 0 0 0 0 80 0 43 0 0 1 0 1 0 Wohnzimmer 35 43 0 0 0 0 0 0 0 0 43 0 0 1 0 0 16,17 python-fritzhome-0.6.14/tests/responses/lightbulb/000077500000000000000000000000001474575644000223075ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/lightbulb/device_FritzDECT500_34_12_16.xml000066400000000000000000000022571474575644000274570ustar00rootroot00000000000000 1 0 FRITZ!DECT 500 Büro 1 0 FRITZ!DECT 500 Büro 1 3 1 358 180 0 255 407 278 512,514,513 python-fritzhome-0.6.14/tests/responses/lightbulb/device_FritzDECT500_34_12_16_color_temp_mode.xml000066400000000000000000000022501474575644000326770ustar00rootroot00000000000000 1 0 FRITZ!DECT 500 Büro 1 0 FRITZ!DECT 500 Büro 1 3 1 0 255 2800 407 278 512,514,513 python-fritzhome-0.6.14/tests/responses/lightbulb/device_Telekom_Magenta_NonColorBulb.xml000066400000000000000000000017161474575644000320270ustar00rootroot00000000000000 1 0 Telekom Lampe 1 0 Telekom White Dimmable Bulb 1 255 100 401 265 512,513 python-fritzhome-0.6.14/tests/responses/lightbulb/getcolors_FritzDECT500_34_12_16.xml000066400000000000000000000062351474575644000302210ustar00rootroot00000000000000 Rot Orange Gelb Grasgrün Grün Türkis Cyan Himmelblau Blau Violett Magenta Pink python-fritzhome-0.6.14/tests/responses/login_rsp_with_valid_sid.xml000066400000000000000000000002441474575644000261220ustar00rootroot00000000000000000000000000000144b750c00python-fritzhome-0.6.14/tests/responses/login_rsp_with_valid_sid_pbkdf2.xml000066400000000000000000000003521474575644000273520ustar00rootroot0000000000000000000000000000012$10000$a64b986b521fcbc44d7a9f0adad34b14$1000$b9c232dea345233f5a893b2284931ac80python-fritzhome-0.6.14/tests/responses/login_rsp_without_valid_sid.xml000066400000000000000000000002441474575644000266520ustar00rootroot00000000000000000000000000000044b750c00python-fritzhome-0.6.14/tests/responses/login_rsp_without_valid_sid_pbkdf2.xml000066400000000000000000000003521474575644000301020ustar00rootroot0000000000000000000000000000002$10000$a64b986b521fcbc44d7a9f0adad34b14$1000$b9c232dea345233f5a893b2284931ac80python-fritzhome-0.6.14/tests/responses/powermeter/000077500000000000000000000000001474575644000225245ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/powermeter/device_list.xml000066400000000000000000000012601474575644000255370ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 1000 707 230000 285 0 python-fritzhome-0.6.14/tests/responses/powermeter/device_list_faulty.xml000066400000000000000000000011121474575644000271170ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 285 0 python-fritzhome-0.6.14/tests/responses/switch/000077500000000000000000000000001474575644000216345ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/switch/device_list.xml000066400000000000000000000025531474575644000246550ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 0 707 285 0 1 0 Telekom Steckdose 1 0 Telekom Steckdose 1 402 262 512 python-fritzhome-0.6.14/tests/responses/temperature/000077500000000000000000000000001474575644000226705ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/temperature/device_temperature.xml000066400000000000000000000011741474575644000272710ustar00rootroot00000000000000 1 Steckdose 1 auto 0 0 0 707 205 -10 python-fritzhome-0.6.14/tests/responses/templates/000077500000000000000000000000001474575644000223315ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/templates/template_list.xml000066400000000000000000000062211474575644000257220ustar00rootroot00000000000000 python-fritzhome-0.6.14/tests/responses/templates/template_list_removed_template.xml000066400000000000000000000056401474575644000313420ustar00rootroot00000000000000 python-fritzhome-0.6.14/tests/responses/thermostat/000077500000000000000000000000001474575644000225255ustar00rootroot00000000000000python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_fritzos_7.xml000066400000000000000000000021501474575644000273560ustar00rootroot00000000000000 1 Thermostat Wohnzimmer Seite 0 70 205 -10 41 253 36 42 1 1 0 0 0 100 70 1538341200 42 1 0 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_fritzos_7_57.xml000066400000000000000000000025361474575644000277010ustar00rootroot00000000000000 1 0 Wohnzimmer 90 0 265 0 53 47 36 44 0 0 0 0 0 1 1704630842 0 90 1704650400 44 0 0 1 0 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_fw_03_50.xml000066400000000000000000000010211474575644000266460ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 45 32 45 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_fw_03_54.xml000066400000000000000000000016121474575644000266600ustar00rootroot00000000000000 1 Badezimmer 0 80 205 -15 41 36 36 42 0 0 0 0 80 1508342400 42 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_no_temp_values.xml000066400000000000000000000016141474575644000304540ustar00rootroot00000000000000 1 Thermostat Wohnzimmer Seite 0 80 44 42 36 42 0 0 5 0 80 1519160400 36 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_state_comfort.xml000066400000000000000000000010211474575644000302750ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 48 45 48 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_state_eco.xml000066400000000000000000000010211474575644000273720ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 45 45 48 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_state_manual.xml000066400000000000000000000010211474575644000301010ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 40 45 48 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_state_off.xml000066400000000000000000000010221474575644000273770ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 253 32 45 python-fritzhome-0.6.14/tests/responses/thermostat/device_hkr_state_on.xml000066400000000000000000000010221474575644000272410ustar00rootroot00000000000000 1 Comet DECT #2 230 -10 46 254 32 45 python-fritzhome-0.6.14/tests/responses/thermostat/device_list_battery_low.xml000066400000000000000000000016261474575644000301610ustar00rootroot00000000000000 1 Badezimmer 10 1 205 -15 41 36 36 42 0 0 0 1 10 1508342400 42 python-fritzhome-0.6.14/tests/responses/thermostat/device_list_battery_ok.xml000066400000000000000000000016261474575644000277710ustar00rootroot00000000000000 1 Badezimmer 0 80 205 -15 41 36 36 42 0 0 0 0 80 1508342400 42 python-fritzhome-0.6.14/tests/test_fritzhome.py000066400000000000000000000301131474575644000217300ustar00rootroot00000000000000# -*- coding: utf-8 -*- from requests.exceptions import ConnectionError from unittest.mock import MagicMock, patch import pytest from pyfritzhome import Fritzhome, InvalidError, LoginError, NotLoggedInError from .helper import Helper class TestFritzhome(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "admin123") self.fritz._request = self.mock self.fritz._sid = "0000001" def test_login_fail(self): self.mock.side_effect = [ Helper.response("login_rsp_without_valid_sid"), Helper.response("login_rsp_without_valid_sid"), ] with pytest.raises(LoginError) as ex: self.fritz.login() assert str(ex.value) == 'login for user="user" failed' def test_login_connection_error(self): self.mock.side_effect = ConnectionError with pytest.raises(ConnectionError): self.fritz.login() def test_login(self): self.mock.side_effect = [ Helper.response("login_rsp_without_valid_sid"), Helper.response("login_rsp_with_valid_sid"), ] self.fritz.login() def test_login_pbkdf2(self): self.mock.side_effect = [ Helper.response("login_rsp_without_valid_sid_pbkdf2"), Helper.response("login_rsp_with_valid_sid_pbkdf2"), ] """ challenge was generated using http://home.mengelke.de/login_sid.lua?version=2 (Fritzbox Anmeldesimulator) response was generated using python code from https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM%20Technical%20Note%20-%20Session%20ID_deutsch%20-%20Nov2020.pdf the example challenge and response in this document is faulty and does not work with given example code """ self.fritz.login() self.fritz._request.assert_called_with( "http://10.0.0.1/login_sid.lua?version=2", { "username": "user", "response": "b9c232dea345233f5a893b2284931ac8$" "2825c7fbd8cdbcbaf93ca2e8d0798c31cf38394469a9ce89365778dc9103ad82", }, ) def test_logout(self): self.fritz.logout() self.fritz._request.assert_called_with( "http://10.0.0.1/login_sid.lua", {"sid": "0000001", "security:command/logout": "1"}, ) def test_not_logged_in_error(self): self.fritz._sid = None with pytest.raises(NotLoggedInError) as ex: self.fritz.update_devices() assert str(ex.value) == "not logged in, login before doing any requests." def test_aha_request(self): self.fritz._aha_request(cmd="testcmd") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "switchcmd": "testcmd"}, ) self.fritz._aha_request(cmd="testcmd", ain="1") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "switchcmd": "testcmd", "ain": "1"}, ) self.fritz._aha_request(cmd="testcmd", ain="1", param={"a": "1", "b": "2"}) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "sid": "0000001", "switchcmd": "testcmd", "ain": "1", "a": "1", "b": "2", }, ) def test_aha_request_invalid(self): self.mock.side_effect = [ "inval", ] with pytest.raises(InvalidError): self.fritz._aha_request(cmd="estcmd") def test_get_device_element(self): self.mock.side_effect = [ Helper.response("base/device_list"), Helper.response("base/device_list"), Helper.response("base/device_list"), ] element = self.fritz.get_device_element("08761 0000434") assert element.attrib["identifier"] == "08761 0000434" assert element.attrib["fwversion"] == "03.33" element = self.fritz.get_device_element("08761 1048079") assert element.attrib["identifier"] == "08761 1048079" assert element.attrib["fwversion"] == "03.44" element = self.fritz.get_device_element("unknown") assert element is None def test_get_device_by_ain(self): self.mock.side_effect = [ Helper.response("base/device_list"), Helper.response("base/device_list"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.ain == "08761 0000434" def test_aha_get_devices(self): self.mock.side_effect = [ Helper.response("base/device_list"), ] self.fritz.update_devices() devices = self.fritz.get_devices() assert devices[0].name == "Steckdose" assert devices[1].name == "FRITZ!DECT Rep 100 #1" self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "switchcmd": "getdevicelistinfos"}, ) def test_get_device_name(self): self.mock.side_effect = ["testname"] assert self.fritz.get_device_name(ain="1234") == "testname" self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "ain": "1234", "switchcmd": "getswitchname"}, ) def test_get_template_by_ain(self): self.mock.side_effect = [ Helper.response("templates/template_list"), Helper.response("templates/template_list"), ] self.fritz.update_templates() device = self.fritz.get_template_by_ain("tmp0B32F7-1B0650682") assert device.ain == "tmp0B32F7-1B0650682" def test_aha_get_templates(self): self.mock.side_effect = [ Helper.response("templates/template_list"), ] self.fritz.update_templates() templates = self.fritz.get_templates() assert templates[0].name == "Base Data" assert templates[1].name == "One Device" self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "switchcmd": "gettemplatelistinfos"}, ) def test_aha_apply_template(self): self.fritz.apply_template("1234") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "ain": "1234", "switchcmd": "applytemplate"}, ) def test_set_target_temperature(self): self.fritz.set_target_temperature("1", 25.5) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "ain": "1", "switchcmd": "sethkrtsoll", "param": 51}, ) self.fritz.set_target_temperature("1", 0.0) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "ain": "1", "switchcmd": "sethkrtsoll", "param": 253}, ) self.fritz.set_target_temperature("1", 32.0) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"sid": "0000001", "ain": "1", "switchcmd": "sethkrtsoll", "param": 254}, ) def test_set_state(self): self.fritz.set_state_off("1") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 0, "ain": "1"}, ) self.fritz.set_state_on("1") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 1, "ain": "1"}, ) self.fritz.set_state_toggle("1") self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 2, "ain": "1"}, ) def test_set_level(self): self.fritz.set_level("1", 10) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setlevel", "sid": "0000001", "level": 10, "ain": "1"}, ) self.fritz.set_level("1", -1) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setlevel", "sid": "0000001", "level": 0, "ain": "1"}, ) self.fritz.set_level("1", 256) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"switchcmd": "setlevel", "sid": "0000001", "level": 255, "ain": "1"}, ) def test_set_level_percentage(self): self.fritz.set_level_percentage("1", 10) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setlevelpercentage", "sid": "0000001", "level": 10, "ain": "1", }, ) self.fritz.set_level_percentage("1", -1) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setlevelpercentage", "sid": "0000001", "level": 0, "ain": "1", }, ) self.fritz.set_level_percentage("1", 101) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setlevelpercentage", "sid": "0000001", "level": 100, "ain": "1", }, ) def test_set_color_temp(self): self.fritz.set_color_temp("1", 3500) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setcolortemperature", "sid": "0000001", "temperature": 3500, "duration": 0, "ain": "1", }, ) self.fritz.set_color_temp("1", 3500, 2) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setcolortemperature", "sid": "0000001", "temperature": 3500, "duration": 20, "ain": "1", }, ) @patch("time.time", MagicMock(return_value=1000)) def test_set_window_open(self): self.fritz.set_window_open("1", 25) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "sid": "0000001", "ain": "1", "switchcmd": "sethkrwindowopen", "endtimestamp": 1000 + 25, }, ) @patch("time.time", MagicMock(return_value=1000)) def test_set_boost_mode(self): self.fritz.set_boost_mode("1", 25) self.fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "sid": "0000001", "ain": "1", "switchcmd": "sethkrboost", "endtimestamp": 1000 + 25, }, ) def test_wait_tx_busy(self): self.mock.side_effect = [ Helper.response("base/device_txbusy"), Helper.response("base/device_not_txbusy"), ] assert self.fritz.wait_device_txbusy("11960 0089208") def test_wait_tx_busy_failed(self): self.mock.side_effect = [ Helper.response("base/device_txbusy"), Helper.response("base/device_txbusy"), ] assert not self.fritz.wait_device_txbusy("11960 0089208", 1) python-fritzhome-0.6.14/tests/test_fritzhomedevicealarm.py000066400000000000000000000035671474575644000241420ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceAlarm(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_alert_on(self): self.mock.side_effect = [ Helper.response("alarm/device_alert_on"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("05333 0077045-1") assert device.present assert device.alert_state assert device.supported_features == [ FritzhomeDeviceFeatures.ALARM, FritzhomeDeviceFeatures.HANFUN_UNIT, ] def test_device_alert_off(self): self.mock.side_effect = [ Helper.response("alarm/device_alert_off"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("05333 0077045-2") assert device.present assert not device.alert_state def test_device_alert_no_alertstate(self): self.mock.side_effect = [ Helper.response("alarm/device_alert_no_alertstate"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("05333 0077045-3") assert device.present assert device.alert_state is None def test_magenta_smoke_alarm(self): self.mock.side_effect = [ Helper.response("alarm/device_magenta_smoke_alarm"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("11324 0244498-1") assert device.present assert device.alert_state is None python-fritzhome-0.6.14/tests/test_fritzhomedevicebase.py000066400000000000000000000105611474575644000237500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from pyfritzhome.devicetypes.fritzhomeentitybase import FritzhomeEntityBase from .helper import Helper class TestFritzhomeDeviceBase(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_init(self): self.mock.side_effect = [Helper.response("base/device_list")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.ain == "08761 0000434" assert device.fw_version == "03.33" assert device.present assert device.has_switch assert device.has_temperature_sensor assert device.has_powermeter assert device.supported_features == [ FritzhomeDeviceFeatures.POWER_METER, FritzhomeDeviceFeatures.TEMPERATURE, FritzhomeDeviceFeatures.SWITCH, ] def test_device_init_present_false(self): self.mock.side_effect = [ Helper.response("base/device_not_present"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("11960 0089208") assert device.ain == "11960 0089208" assert not device.present def test_device_init_no_devicelock_element(self): self.mock.side_effect = [ Helper.response("base/device_no_devicelock_element"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0373130") assert device.ain == "08761 0373130" assert device.present def test_device_umlaut(self): self.mock.side_effect = [ Helper.response("base/device_with_umlaut_in_name"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0373130") assert device.ain == "08761 0373130" assert device.name == "äöü" def test_device_update(self): self.mock.side_effect = [ Helper.response("thermostat/device_list_battery_ok"), Helper.response("thermostat/device_list_battery_low"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("11959 0171328") assert not device.battery_low device.update() assert device.battery_low def test_get_device_present(self): self.mock.side_effect = [ Helper.response("base/device_list"), "1", "0", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.get_present() assert not device.get_present() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "getswitchpresent", "sid": "0000001"}, ) def test_device_removed(self): self.mock.side_effect = [ Helper.response("base/device_list"), Helper.response("base/device_list_removed_device"), Helper.response("base/device_list_removed_device"), ] self.fritz.update_devices() assert len(self.fritz.get_devices()) == 5 self.fritz.update_devices() assert len(self.fritz.get_devices()) == 5 self.fritz.update_devices(ignore_removed=False) assert len(self.fritz.get_devices()) == 4 def test_device_and_unit_id(self): device = FritzhomeEntityBase() device.ain = "11630 0114733" assert device.device_and_unit_id == ("11630 0114733", None) device.ain = "11630 0114733-1" assert device.device_and_unit_id == ("11630 0114733", "1") device.ain = "ZA4C1380C30E07AB1" assert device.device_and_unit_id == ("ZA4C1380C30E07AB1", None) device.ain = "ZA4C1380C30E07AB101" assert device.device_and_unit_id == ("ZA4C1380C30E07AB1", "01") device.ain = "grp303E4F-3F7D9BE07" assert device.device_and_unit_id == ("grp303E4F-3F7D9BE07", None) device.ain = "tmp816271-3F6EB615E" assert device.device_and_unit_id == ("tmp816271-3F6EB615E", None) python-fritzhome-0.6.14/tests/test_fritzhomedeviceblind.py000066400000000000000000000106221474575644000241240ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceBlind(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_response(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), ] self.fritz.update_devices() device1 = self.fritz.get_device_by_ain("14276 1234567") assert device1.present assert not device1.tx_busy assert device1.supported_features == [FritzhomeDeviceFeatures.HANFUN_DEVICE] device2 = self.fritz.get_device_by_ain("14276 1234567-1") assert device2.present assert not device2.tx_busy assert device2.endpositionsset assert device2.supported_features == [ FritzhomeDeviceFeatures.ALARM, FritzhomeDeviceFeatures.HANFUN_UNIT, FritzhomeDeviceFeatures.LEVEL, FritzhomeDeviceFeatures.BLIND, ] assert device2.level == 252 assert device2.levelpercentage == 99 assert device2.get_level() == 252 assert device2.get_level_percentage() == 99 def test_set_level(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), None, ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("14276 1234567-1") device.set_level(100) device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setlevel", "sid": "0000001", "ain": "14276 1234567-1", "level": 100, }, ) def test_set_level_percentage(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), None, ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("14276 1234567-1") device.set_level_percentage(50) device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setlevelpercentage", "sid": "0000001", "ain": "14276 1234567-1", "level": 50, }, ) def test_set_blind_open(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), None, ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("14276 1234567-1") device.set_blind_open() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setblind", "sid": "0000001", "ain": "14276 1234567-1", "target": "open", }, ) def test_set_blind_close(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), None, ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("14276 1234567-1") device.set_blind_close() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setblind", "sid": "0000001", "ain": "14276 1234567-1", "target": "close", }, ) def test_set_blind_stop(self): self.mock.side_effect = [ Helper.response("blind/device_blind_rollotron1213"), None, ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("14276 1234567-1") device.set_blind_stop() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setblind", "sid": "0000001", "ain": "14276 1234567-1", "target": "stop", }, ) python-fritzhome-0.6.14/tests/test_fritzhomedevicebutton.py000066400000000000000000000034771474575644000243610ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceButton(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_button_fritzdect440(self): self.mock.side_effect = [ Helper.response("button/device_button_fritzdect440"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345 0000001") assert device.present assert device.alert_state is None assert device.has_temperature_sensor assert device.has_button assert not device.battery_low assert device.battery_level == 100 assert not device.tx_busy assert device.supported_features == [ FritzhomeDeviceFeatures.BUTTON, FritzhomeDeviceFeatures.TEMPERATURE, ] button = device.get_button_by_ain("12345 0000001-1") assert button.name == "Taster Wohnzimmer: Oben rechts" assert button.last_pressed == 1608557681 button = device.get_button_by_ain("12345 0000001-2") assert button.name == "Taster Wohnzimmer: Unten rechts" assert button.last_pressed == 1608557682 def test_button_fritzdect440_humidity(self): self.mock.side_effect = [ Helper.response("button/device_button_fritzdect440_fw_05_10"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345 0000002") assert device.present assert device.rel_humidity == 44 python-fritzhome-0.6.14/tests/test_fritzhomedevicelightbulb.py000066400000000000000000000254331474575644000250160ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceLightBulb(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_init(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.ain == "12345" assert device.fw_version == "34.10.16.16.009" assert device.present # Lightbulb has power and is connected assert device.supported_features == [FritzhomeDeviceFeatures.HANFUN_DEVICE] # Get sub-device device = self.fritz.get_device_by_ain("12345-1") assert device.has_lightbulb assert device.has_level assert device.has_color assert device.state # Lightbulb is switched on assert device.color_mode == "1" assert device.supported_color_mode == "5" assert device.fullcolorsupport assert device.hue == 358 assert device.saturation == 180 assert device.color_temp is None assert device.name == "FRITZ!DECT 500 Büro" assert device.supported_features == [ FritzhomeDeviceFeatures.LIGHTBULB, FritzhomeDeviceFeatures.HANFUN_UNIT, FritzhomeDeviceFeatures.SWITCHABLE, FritzhomeDeviceFeatures.LEVEL, FritzhomeDeviceFeatures.COLOR, ] def test_device_init_non_color_bulb(self): self.mock.side_effect = [ Helper.response("lightbulb/device_Telekom_Magenta_NonColorBulb") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12701 0072784") assert device.ain == "12701 0072784" assert device.fw_version == "34.09.15.16.018" assert device.present # Lightbulb has power and is connected # Get sub-device device = self.fritz.get_device_by_ain("12701 0072784-1") assert device.has_lightbulb assert device.has_level assert not device.fullcolorsupport assert device.state # Lightbulb is switched on assert device.name == "Telekom White Dimmable Bulb" def test_device_init_color_temp_mode(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16_color_temp_mode") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.ain == "12345" assert device.fw_version == "34.10.16.16.009" assert device.present # Lightbulb has power and is connected # Get sub-device device = self.fritz.get_device_by_ain("12345-1") assert device.has_lightbulb assert device.state # Lightbulb is switched on assert device.color_mode == "4" assert device.supported_color_mode == "5" assert device.fullcolorsupport assert device.hue is None assert device.saturation is None assert device.color_temp == 2800 assert device.name == "FRITZ!DECT 500 Büro" def test_get_colors(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") self.mock.side_effect = [ Helper.response("lightbulb/getcolors_FritzDECT500_34_12_16") ] colors = device.get_colors() # fmt: off expected_colors = { 'Rot': [ ('358', '180', '230'), ('358', '112', '237'), ('358', '54', '245') ], 'Orange': [ ('35', '214', '255'), ('35', '140', '255'), ('35', '72', '255') ], 'Gelb': [ ('52', '153', '252'), ('52', '102', '252'), ('52', '51', '255') ], 'Grasgrün': [ ('92', '123', '248'), ('92', '79', '250'), ('92', '38', '252') ], 'Grün': [ ('120', '160', '220'), ('120', '82', '232'), ('120', '38', '242') ], 'Türkis': [ ('160', '145', '235'), ('160', '84', '242'), ('160', '41', '248') ], 'Cyan': [ ('195', '179', '255'), ('195', '118', '255'), ('195', '59', '255') ], 'Himmelblau': [ ('212', '169', '252'), ('212', '110', '252'), ('212', '56', '255') ], 'Blau': [ ('225', '204', '255'), ('225', '135', '255'), ('225', '67', '255') ], 'Violett': [ ('266', '169', '250'), ('266', '110', '250'), ('266', '54', '252') ], 'Magenta': [ ('296', '140', '250'), ('296', '92', '252'), ('296', '46', '255') ], 'Pink': [ ('335', '180', '255'), ('335', '107', '248'), ('335', '51', '250') ] } # fmt: on assert colors == expected_colors def test_get_colors_NonColorBulb(self): self.mock.side_effect = [ Helper.response("lightbulb/device_Telekom_Magenta_NonColorBulb") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12701 0072784") temps = device.get_colors() # No colors and no exception assert temps == {} def test_get_color_temps(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") self.mock.side_effect = [ Helper.response("lightbulb/getcolors_FritzDECT500_34_12_16") ] temps = device.get_color_temps() expected_temps = [ "2700", "3000", "3400", "3800", "4200", "4700", "5300", "5900", "6500", ] assert temps == expected_temps def test_get_color_temps_NonColorBulb(self): self.mock.side_effect = [ Helper.response("lightbulb/device_Telekom_Magenta_NonColorBulb") ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12701 0072784") temps = device.get_color_temps() # No color temps and no exception assert temps == [] def test_set_color(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_color(["180", "200"], 0) device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setcolor", "sid": "0000001", "hue": 180, "saturation": 200, "duration": 0, "ain": "12345-1", }, ) def test_set_color_temp(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_color_temp(3000, 0) device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setcolortemperature", "sid": "0000001", "temperature": 3000, "duration": 0, "ain": "12345-1", }, ) def test_set_unmapped_color(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_unmapped_color(["180", "200"], 0) device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setunmappedcolor", "sid": "0000001", "hue": 180, "saturation": 200, "duration": 0, "ain": "12345-1", }, ) def test_set_state_off(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_state_off() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 0, "ain": "12345-1", }, ) def test_set_state_on(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_state_on() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 1, "ain": "12345-1", }, ) def test_set_state_toggle(self): self.mock.side_effect = [ Helper.response("lightbulb/device_FritzDECT500_34_12_16"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345-1") device.set_state_toggle() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "switchcmd": "setsimpleonoff", "sid": "0000001", "onoff": 2, "ain": "12345-1", }, ) python-fritzhome-0.6.14/tests/test_fritzhomedevicepowermeter.py000066400000000000000000000050321474575644000252240ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDevicePowermeter(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_get_switch_power(self): self.mock.side_effect = [ Helper.response("powermeter/device_list"), "18000", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.get_switch_power() == 18000 assert device.supported_features == [ FritzhomeDeviceFeatures.POWER_METER, FritzhomeDeviceFeatures.TEMPERATURE, FritzhomeDeviceFeatures.SWITCH, ] device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "getswitchpower", "sid": "0000001"}, ) def test_get_switch_energy(self): self.mock.side_effect = [ Helper.response("powermeter/device_list"), "2000", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.get_switch_energy() == 2000 device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "getswitchenergy", "sid": "0000001"}, ) def test_get_switch_powermeter_properties(self): self.mock.side_effect = [ Helper.response("powermeter/device_list"), "2000", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.energy == 707 assert device.power == 1000 assert device.voltage == 230000 assert device.current == 4.3478260869565215 def test_faulty_powermeter_properties(self): self.mock.side_effect = [ Helper.response("powermeter/device_list_faulty"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.energy is None assert device.power is None assert device.voltage is None assert device.current is None python-fritzhome-0.6.14/tests/test_fritzhomedevicerepeater.py000066400000000000000000000006071474575644000246450ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome class TestFritzhomeDeviceRepeater(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" python-fritzhome-0.6.14/tests/test_fritzhomedeviceswitch.py000066400000000000000000000053241474575644000243400ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceSwitch(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_get_switch_state(self): self.mock.side_effect = [ Helper.response("switch/device_list"), "1", "0", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") assert device.get_switch_state() assert not device.get_switch_state() assert device.supported_features == [ FritzhomeDeviceFeatures.POWER_METER, FritzhomeDeviceFeatures.TEMPERATURE, FritzhomeDeviceFeatures.SWITCH, ] device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "getswitchstate", "sid": "0000001"}, ) def test_set_switch_state_toggle(self): self.mock.side_effect = [ Helper.response("switch/device_list"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") device.set_switch_state_toggle() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "setswitchtoggle", "sid": "0000001"}, ) def test_set_switch_state_on(self): self.mock.side_effect = [ Helper.response("switch/device_list"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") device.set_switch_state_on() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "setswitchon", "sid": "0000001"}, ) def test_set_switch_state_off(self): self.mock.side_effect = [ Helper.response("switch/device_list"), "1", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("08761 0000434") device.set_switch_state_off() device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "08761 0000434", "switchcmd": "setswitchoff", "sid": "0000001"}, ) python-fritzhome-0.6.14/tests/test_fritzhomedevicetemperature.py000066400000000000000000000022161474575644000253710ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceTemperature(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_get_temperature(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fritzos_7"), "245", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_temperature() == 24.5 assert device.supported_features == [ FritzhomeDeviceFeatures.THERMOSTAT, FritzhomeDeviceFeatures.TEMPERATURE, ] device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "12345", "switchcmd": "gettemperature", "sid": "0000001"}, ) python-fritzhome-0.6.14/tests/test_fritzhomedevicethermostat.py000066400000000000000000000211761474575644000252340ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock, patch from pyfritzhome import Fritzhome, FritzhomeDevice from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceThermostat(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_hkr_fw_03_50(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fw_03_50"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.present assert device.device_lock is None assert device.lock is None assert device.error_code is None assert device.battery_low is None assert device.supported_features == [ FritzhomeDeviceFeatures.THERMOSTAT, FritzhomeDeviceFeatures.TEMPERATURE, ] def test_device_hkr_fw_03_54(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fw_03_54"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("23456") assert device.present assert device.supported_features == [ FritzhomeDeviceFeatures.THERMOSTAT, FritzhomeDeviceFeatures.TEMPERATURE, ] def test_get_target_temperature(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fritzos_7"), "38", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_target_temperature() == 19.0 device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "12345", "switchcmd": "gethkrtsoll", "sid": "0000001"}, ) def test_get_eco_temperature(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fritzos_7"), "40", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_eco_temperature() == 20.0 device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "12345", "switchcmd": "gethkrabsenk", "sid": "0000001"}, ) def test_get_comfort_temperature(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_fritzos_7"), "41", ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_comfort_temperature() == 20.5 device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", {"ain": "12345", "switchcmd": "gethkrkomfort", "sid": "0000001"}, ) def test_hkr_without_temperature_values(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_no_temp_values"), ] element = self.fritz.get_device_element("11960 0071472") device = FritzhomeDevice(node=element) assert device.ain == "11960 0071472" assert device.offset is None assert device.temperature is None def test_hkr_get_state_on(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_on"), Helper.response("thermostat/device_hkr_state_on"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_hkr_state() == "on" def test_hkr_get_state_off(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_off"), Helper.response("thermostat/device_hkr_state_off"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_hkr_state() == "off" def test_hkr_get_state_eco(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_eco"), Helper.response("thermostat/device_hkr_state_eco"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_hkr_state() == "eco" def test_hkr_get_state_comfort(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_comfort"), Helper.response("thermostat/device_hkr_state_comfort"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_hkr_state() == "comfort" def test_hkr_get_state_manual(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_manual"), Helper.response("thermostat/device_hkr_state_manual"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.get_hkr_state() == "manual" def test_hkr_set_state_on(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_manual"), Helper.response("thermostat/device_hkr_state_manual"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") device.set_hkr_state("on") device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "ain": "12345", "switchcmd": "sethkrtsoll", "param": 254, "sid": "0000001", }, ) def test_hkr_set_state_off(self): self.mock.side_effect = [ Helper.response("thermostat/device_hkr_state_manual"), Helper.response("thermostat/device_hkr_state_manual"), ] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") device.set_hkr_state("off") device._fritz._request.assert_called_with( "http://10.0.0.1/webservices/homeautoswitch.lua", { "ain": "12345", "switchcmd": "sethkrtsoll", "param": 253, "sid": "0000001", }, ) def test_hkr_battery_level(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.battery_level == 70 def test_hkr_window_open(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert not device.window_open @patch("time.time", MagicMock(return_value=1704630800)) def test_hkr_boost_mode(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7_57")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345 6789012") assert device.boost_active is True assert device.boost_active_endtime == 42 def test_hkr_adaptive_heating(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7_57")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345 6789012") assert device.adaptive_heating_active is True assert device.adaptive_heating_running is False def test_hkr_summer_active(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.summer_active def test_hkr_holiday_active(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert not device.holiday_active def test_hkr_nextchange_endperiod(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.nextchange_endperiod == 1538341200 def test_hkr_nextchange_temperature(self): self.mock.side_effect = [Helper.response("thermostat/device_hkr_fritzos_7")] self.fritz.update_devices() device = self.fritz.get_device_by_ain("12345") assert device.nextchange_temperature == 21.0 python-fritzhome-0.6.14/tests/test_fritzhomedevicethermostat_group.py000066400000000000000000000054261474575644000264500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeDeviceThermostat(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" def test_device_alert_on(self): self.mock.side_effect = [ Helper.response("groups/device_list_thermostat"), Helper.response("groups/device_list_thermostat_without_tsoll"), Helper.response("groups/device_list_thermostat"), ] self.fritz.update_devices() group = self.fritz.get_device_by_ain("grp303E4F-3F7D9BE07") assert group.has_thermostat assert group.is_group assert group.group_members == ["16", "17"] assert group.supported_features == [FritzhomeDeviceFeatures.THERMOSTAT] assert group.adaptive_heating_active is True assert group.adaptive_heating_running is False assert group.boost_active is False assert group.window_open is False assert group.boost_active_endtime == 0 assert group.window_open_endtime == 0 assert group.actual_temperature is None assert group.target_temperature == 21.5 self.fritz.update_devices() group = self.fritz.get_device_by_ain("grp303E4F-3F7D9BE07") assert group.has_thermostat assert group.is_group assert group.group_members == ["16", "17"] assert group.supported_features == [FritzhomeDeviceFeatures.THERMOSTAT] assert group.adaptive_heating_active is True assert group.adaptive_heating_running is False assert group.boost_active is False assert group.window_open is False assert group.boost_active_endtime == 0 assert group.window_open_endtime == 0 assert group.actual_temperature is None assert group.target_temperature is None self.fritz.update_devices() group = self.fritz.get_device_by_ain("grp303E4F-3F7D9BE07") assert group.has_thermostat assert group.is_group assert group.group_members == ["16", "17"] assert group.supported_features == [FritzhomeDeviceFeatures.THERMOSTAT] assert group.adaptive_heating_active is True assert group.adaptive_heating_running is False assert group.boost_active is False assert group.window_open is False assert group.boost_active_endtime == 0 assert group.window_open_endtime == 0 assert group.actual_temperature is None assert group.target_temperature == 21.5 python-fritzhome-0.6.14/tests/test_fritzhometemplate.py000066400000000000000000000165721474575644000235010ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from unittest.mock import MagicMock from pyfritzhome import Fritzhome from pyfritzhome.devicetypes.fritzhomedevicefeatures import FritzhomeDeviceFeatures from .helper import Helper class TestFritzhomeTemplate(object): def setup_method(self): self.mock = MagicMock() self.fritz = Fritzhome("10.0.0.1", "user", "pass") self.fritz._request = self.mock self.fritz._devices = {} self.fritz._sid = "0000001" self.mock.side_effect = [Helper.response("templates/template_list")] self.fritz.update_templates() def test_template_init(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B0650682") assert template.ain == "tmp0B32F7-1B0650682" assert template._functionsbitmask == 320 assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper assert template.supported_features == [ FritzhomeDeviceFeatures.THERMOSTAT, FritzhomeDeviceFeatures.TEMPERATURE, ] def test_template_removed(self): self.mock.side_effect = [ Helper.response("templates/template_list"), Helper.response("templates/template_list_removed_template"), Helper.response("templates/template_list_removed_template"), ] self.fritz.update_templates() assert len(self.fritz.get_templates()) == 12 self.fritz.update_templates() assert len(self.fritz.get_templates()) == 12 self.fritz.update_templates(ignore_removed=False) assert len(self.fritz.get_templates()) == 11 def test_template_with_single_device(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B0650234") assert template.devices == ["08735 0525249"] def test_template_with_multiple_devices(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1C40A2B8A") # fmt: off expected_devices = set(["08735 0525249", "08735 0525249", "08735 0340143", "08735 0526125"]) # fmt: on assert len(expected_devices.intersection(template.devices)) == len( expected_devices ) def test_template_applies_hkr_summer(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA20") assert template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_hkr_temperature(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA21") assert not template.apply_hkr_summer assert template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_hkr_holidays(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA22") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_hkr_time_table(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA23") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_relay_manual(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA24") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_relay_automatic(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA25") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_level(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA26") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert template.apply_level assert not template.apply_color assert not template.apply_dialhelper def test_template_applies_color(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA27") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert template.apply_color assert not template.apply_dialhelper def test_template_applies_dialhelper(self): template = self.fritz.get_template_by_ain("tmp0B32F7-1B064FA28") assert not template.apply_hkr_summer assert not template.apply_hkr_temperature assert not template.apply_hkr_holidays assert not template.apply_hkr_time_table assert not template.apply_relay_manual assert not template.apply_relay_automatic assert not template.apply_level assert not template.apply_color assert template.apply_dialhelper def test_has_template(self): self.mock.side_effect = ["invalid_xml"] assert not self.fritz.has_templates()