pax_global_header00006660000000000000000000000064147601523360014521gustar00rootroot0000000000000052 comment=76b2b548bca61efd79252f4bd922bc6c936ab5d4 pyfibaro-0.8.2/000077500000000000000000000000001476015233600133435ustar00rootroot00000000000000pyfibaro-0.8.2/.devcontainer/000077500000000000000000000000001476015233600161025ustar00rootroot00000000000000pyfibaro-0.8.2/.devcontainer/devcontainer.json000066400000000000000000000016441476015233600214630ustar00rootroot00000000000000{ "name": "pyfibaro dev", "image": "mcr.microsoft.com/devcontainers/python:1-3.12", "postCreateCommand": "script/setup", "customizations": { "vscode": { "extensions": [ "ms-python.vscode-pylance", "ms-python.pylint", "ms-python.black-formatter", "ms-python.isort" ], "settings": { "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.analysis.extraPaths": [ ".", "examples", "tests" ], "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnPaste": false, "editor.formatOnSave": true, "files.trimTrailingWhitespace": true, "terminal.integrated.profiles.linux": { "zsh": { "path": "/usr/bin/zsh" } }, "terminal.integrated.defaultProfile.linux": "zsh" } } } }pyfibaro-0.8.2/.github/000077500000000000000000000000001476015233600147035ustar00rootroot00000000000000pyfibaro-0.8.2/.github/workflows/000077500000000000000000000000001476015233600167405ustar00rootroot00000000000000pyfibaro-0.8.2/.github/workflows/codeql.yml000066400000000000000000000053311476015233600207340ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "main" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] schedule: - cron: '43 6 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" pyfibaro-0.8.2/.github/workflows/python-package.yml000066400000000000000000000014471476015233600224030ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Python package on: pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.11", "3.12", "3.13"] 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 run: | python -m pip install --upgrade pip script/setup - name: Test with pytest run: | script/test pyfibaro-0.8.2/.github/workflows/python-publish.yml000066400000000000000000000015611476015233600224530ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries name: Upload Python Package on: release: types: [published] permissions: contents: read jobs: deploy: runs-on: ubuntu-latest environment: pypi steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip script/setup - name: Build package run: python -m build - name: Upload package env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_KEY }} run: python -m twine upload dist/* pyfibaro-0.8.2/.gitignore000066400000000000000000000034071476015233600153370ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ pyfibaro-0.8.2/LICENSE000066400000000000000000000020621476015233600143500ustar00rootroot00000000000000MIT License Copyright (c) 2022 Roman Appenzeller 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. pyfibaro-0.8.2/README.md000066400000000000000000000045461476015233600146330ustar00rootroot00000000000000# pyfibaro [![license](https://img.shields.io/pypi/l/pyatmo.svg)](https://github.com/rappenze/pyfibaro/blob/main/LICENSE) [![pypi package](https://img.shields.io/pypi/v/pyfibaro)](https://pypi.org/project/pyfibaro/) ![python version](https://img.shields.io/pypi/pyversions/pyfibaro) This project has no relation to the fibaro company. Simple API to access fibaro home center from Python 3. For more detailed information about the API see [Home center 2 / Home center lite](https://manuals.fibaro.com/knowledge-base-browse/rest-api/) [Home center 3 / Home center 3 lite / Yubii Home](https://www.fibaro.com/dev/) The [Zooz Z-Box Hub](https://zboxhub.com/) is powered as well by Software from Fibaro and supports currently the same API. This means that the pyfibaro library as well supports the Zooz Z-Box Hub (this support is untested as there is no public API documentation about the Zooz Z-Box Hub). The pyfibaro library was created for integrating the fibaro home center with home assistant but can be used also in other projects. # Install To install pyfibaro simply type `pip install pyfibaro` # Authentication All endpoints of the fibaro home center except info and login status needs an authenticated user. Just create a user in the fibaro home center with enough rights. # Development Easiest way to start developing is to use Visual Studio Code + devcontainer. ## Prerequisites [Docker](https://docs.docker.com/get-docker/) [Visual Studio code](https://code.visualstudio.com/) ## Getting started 1. Fork this repository 2. Enter the following link in your browser: `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=` 3. When Visual Studio Code asks if you want to install the Remote extension, click "Install". For additional information about Visual Studio Code + devcontainer [learn more about devcontainers](https://code.visualstudio.com/docs/devcontainers/containers). # Testing Run the script `script/test` This will run all unit tests with code coverage enabled. # Usage ```python client = FibaroClient("http://192.168.1.2/api/") client.set_authentication("your_fibaro_username", "your_fibaro_password") client.connect() devices = client.read_devices() for device in devices: print(f"Device {device.fibaro_id}: {device.name}") devices[10].execute_action("turnOn") ``` See folder `examples` for additional examples. pyfibaro-0.8.2/examples/000077500000000000000000000000001476015233600151615ustar00rootroot00000000000000pyfibaro-0.8.2/examples/__init__.py000066400000000000000000000004141476015233600172710ustar00rootroot00000000000000"""Examples for using the API. To try out set here your data and start the example you are interested in with python -m examples.test_info """ FIBARO_URL = "http://192.168.1.166/api/" FIBARO_USERNAME = "your_fibaro_username" FIBARO_PASSWORD = "your_fibaro_password" pyfibaro-0.8.2/examples/example_device.py000066400000000000000000000013401476015233600205030ustar00rootroot00000000000000"""Manual test class.""" import logging from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_PASSWORD, FIBARO_URL, FIBARO_USERNAME def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) client.set_authentication(FIBARO_USERNAME, FIBARO_PASSWORD) client.connect() devices = client.read_devices() for device in devices: print(f"Device {device.fibaro_id}: {device.name}") if device.fibaro_id == 72: device.execute_action("turnOn") if device.fibaro_id == 347: device.execute_action("setValue", [99]) if __name__ == "__main__": main() pyfibaro-0.8.2/examples/example_info.py000066400000000000000000000010251476015233600201770ustar00rootroot00000000000000"""Manual test class.""" import logging from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_URL def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) info = client.read_info() print(f"Serial no: {info.serial_number}") print(f"Version: {info.current_version}") print(f"HC Name: {info.hc_name}") print(f"API version: {info.api_version}") if __name__ == "__main__": main() pyfibaro-0.8.2/examples/example_login.py000066400000000000000000000010211476015233600203500ustar00rootroot00000000000000"""Manual test class.""" import logging from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_PASSWORD, FIBARO_URL, FIBARO_USERNAME def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) logged_in = client.connect() client.set_authentication(FIBARO_USERNAME, FIBARO_PASSWORD) logged_in = client.connect() print(f"Logged in: {logged_in}") if __name__ == "__main__": main() pyfibaro-0.8.2/examples/example_room.py000066400000000000000000000010251476015233600202200ustar00rootroot00000000000000"""Manual test class.""" import logging from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_PASSWORD, FIBARO_URL, FIBARO_USERNAME def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) client.set_authentication(FIBARO_USERNAME, FIBARO_PASSWORD) rooms = client.read_rooms() for room in rooms: print(f"Room {room.fibaro_id}: {room.name}") if __name__ == "__main__": main() pyfibaro-0.8.2/examples/example_scene.py000066400000000000000000000012071476015233600203430ustar00rootroot00000000000000"""Manual test class.""" import logging from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_PASSWORD, FIBARO_URL, FIBARO_USERNAME def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) client.set_authentication(FIBARO_USERNAME, FIBARO_PASSWORD) client.connect() scenes = client.read_scenes() for scene in scenes: print(f"Scene {scene.fibaro_id}: {scene.name}") if scene.fibaro_id == 19: scene.start() scene.stop() if __name__ == "__main__": main() pyfibaro-0.8.2/examples/example_state_handler.py000066400000000000000000000012121476015233600220570ustar00rootroot00000000000000"""Manual test class.""" import logging import time from pyfibaro.fibaro_client import FibaroClient from . import FIBARO_PASSWORD, FIBARO_URL, FIBARO_USERNAME def state_callback(event) -> None: """Report state updates to the console.""" print(f"State updates: {event}") def main(): """Main method for testing purposes""" logging.basicConfig(level=logging.DEBUG) print("Start test...") client = FibaroClient(FIBARO_URL) client.set_authentication(FIBARO_USERNAME, FIBARO_PASSWORD) client.connect() client.register_update_handler(state_callback) time.sleep(60) if __name__ == "__main__": main() pyfibaro-0.8.2/pyfibaro/000077500000000000000000000000001476015233600151565ustar00rootroot00000000000000pyfibaro-0.8.2/pyfibaro/__init__.py000066400000000000000000000000501476015233600172620ustar00rootroot00000000000000"""Package containing fibaro client.""" pyfibaro-0.8.2/pyfibaro/common/000077500000000000000000000000001476015233600164465ustar00rootroot00000000000000pyfibaro-0.8.2/pyfibaro/common/__init__.py000066400000000000000000000000771476015233600205630ustar00rootroot00000000000000"""Package containing helper classes for the fibaro client.""" pyfibaro-0.8.2/pyfibaro/common/const.py000066400000000000000000000022201476015233600201420ustar00rootroot00000000000000"""Constants for the fibaro client.""" # Timeout in seconds for http requests DEFAULT_TIMEOUT = 10 # HC 3 needs a big timeout for refresh states request, # it waits up to 30 seconds before a response is sent REFRESH_STATE_TIMEOUT = 35 # Constant http headers sent with each request HTTP_HEADERS = { "Content-Type": "application/json; charset=utf-8", "User-Agent": "pyfibaro", } # Match serial number prefix to a interface version API_VERSION_MATCHER = {"HC2": 4, "HCL": 4, "HC3": 5, "HC3L": 5, "YH": 5, "ZB": 5} # Match serial number prefix to the model name MODEL_NAME_MATCHER = {"HC2": "Home Center 2", "HCL": "Home Center Lite", "HC3": "Home Center 3", "HC3L": "Home Center Lite", "YH": "Yubii Home", "ZB": "Z-Box Hub"} # Match serial number prefix to the manufacturer MANUFACTURER_NAME_MATCHER = {"HC2": "Fibaro", "HCL": "Fibaro", "HC3": "Fibaro", "HC3L": "Fibaro", "YH": "Yubii", "ZB": "ZOOZ"} # Devices which are ignored # iOS_device includes iOS and Android devices IGNORE_DEVICE = ["HC_user", "VOIP_user", "iOS_device"] pyfibaro-0.8.2/pyfibaro/common/rest_client.py000066400000000000000000000051551476015233600213410ustar00rootroot00000000000000"""Rest client for accessing the fibaro API.""" from __future__ import annotations import logging from typing import Any from requests import Response, Session from requests.auth import HTTPBasicAuth from requests.exceptions import JSONDecodeError from .const import DEFAULT_TIMEOUT, HTTP_HEADERS _LOGGER = logging.getLogger(__name__) class RestClient: """Rest client for fibaro home center.""" def __init__( self, url: str, ssl_verify: bool, username: str | None = None, password: str | None = None, ) -> None: """Init""" self._session = Session() self._session.headers = HTTP_HEADERS if url.startswith("https"): self._session.verify = ssl_verify self._base_url = url if username and password: self.set_auth(username, password) def set_auth(self, username: str, password: str) -> None: """Set the credentials for the fibaro home center.""" self._session.auth = HTTPBasicAuth(username, password) def get( self, endpoint: str, json: Any | None = None, timeout: int | None = None, http_headers: dict = None, ) -> Any: """Execute a get request.""" current_timeout = timeout if timeout else DEFAULT_TIMEOUT response = self._session.get( f"{self._base_url}{endpoint}", json=json, timeout=current_timeout, headers=http_headers, ) return self._process_json_result(response) def post( self, endpoint: str, json: Any | None = None, timeout: int | None = None, http_headers: dict = None, ) -> Any: """Execute a post request.""" current_timeout = timeout if timeout else DEFAULT_TIMEOUT response = self._session.post( f"{self._base_url}{endpoint}", json=json, timeout=current_timeout, headers=http_headers, ) return self._process_json_result(response) def close(self) -> None: """Close the session.""" self._session.close() def _process_json_result(self, resp: Response) -> Any: """Do error handling and logging on a HTTP response and covert to json.""" _LOGGER.debug( '%s "%s": %s', resp.request.method, resp.request.url, resp.status_code ) resp.raise_for_status() try: json = resp.json() _LOGGER.debug("Response: %s", json) return json except JSONDecodeError: _LOGGER.debug("No response") return None pyfibaro-0.8.2/pyfibaro/fibaro_client.py000066400000000000000000000111101476015233600203220ustar00rootroot00000000000000"""Main class for accessing fibaro API.""" from requests import HTTPError from .common.rest_client import RestClient from .fibaro_device import DeviceModel from .fibaro_info import InfoModel from .fibaro_login import LoginModel from .fibaro_room import RoomModel from .fibaro_scene import SceneModel from .fibaro_state_handler import FibaroStateHandler class FibaroClient: """Fibaro client. This is the main entry point to access the fibaro API. Usage: Use set_authentication() to provide the credentials Use connect() to establish the connection and check if the credentials are valid Use any other method to access API data and actions """ def __init__(self, url: str, ssl_verify: bool = False) -> None: """Init the fibaro client. The url needs to be in the format http(s):///api/. You can use ssl_verify to enable SSL certificate validation, but be aware that you need to register the fibaro certificates yourself to make it work. Also please be aware that the InsecureRequestWarning is not suppressed by default, so if you need to suppress warnings you need something like: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) """ self._rest_client = RestClient(url, ssl_verify) self._frontend_url = url.removesuffix("/api/") self._api_version: int = None self._state_handler: FibaroStateHandler = None def set_authentication(self, username: str, password: str) -> None: """Set the credentials.""" self._rest_client.set_auth(username, password) def connect(self) -> bool: """Returns the login status. Returns: True if authenticated, False if not authenticated Raises: HTTPError: If there is a connection problem. Most important is HTTPError with status 403 which raised if invalid credentials are provided. """ login, _ = self._login() return login.is_logged_in def connect_with_credentials(self, username: str, password: str) -> InfoModel: """Connect with given credentials. Translate connect errors to easily differentiate auth and connect failures. Returns the hub info if successfully connected. Raises: FibaroAuthenticationFailed: If credentials are invalid FibaroConnectFailed: If connection is not possible """ try: self.set_authentication(username, password) _, info = self._login() return info except HTTPError as http_ex: if http_ex.response.status_code == 403: raise FibaroAuthenticationFailed from http_ex raise FibaroConnectFailed from http_ex except Exception as ex: raise FibaroConnectFailed from ex def _login(self) -> tuple[LoginModel, InfoModel]: login = LoginModel(self._rest_client) info = self.read_info() # Read the API version as it is needed regularly self._api_version = info.api_version return (login, info) def read_info(self) -> InfoModel: """Read the info endpoint from home center.""" return InfoModel(self._rest_client) def read_rooms(self) -> list[RoomModel]: """Read the rooms endpoint from home center.""" return RoomModel.read_rooms(self._rest_client) def read_scenes(self) -> list[SceneModel]: """Read the scenes endpoint from home center.""" return SceneModel.read_scenes(self._rest_client, self._api_version) def read_devices(self) -> list[DeviceModel]: """Read the devices endpoint from home center.""" return DeviceModel.read_devices(self._rest_client, self._api_version) def register_update_handler(self, callback: callable) -> None: """Register a state handler.""" if self._state_handler: raise Exception("There is already a state handler registered") self._state_handler = FibaroStateHandler(self._rest_client, callback) def unregister_update_handler(self) -> None: """Unregister the state handler.""" if self._state_handler: self._state_handler.stop() self._state_handler = None def frontend_url(self) -> str: """Return the url to the web frontend of the fibaro hub.""" return self._frontend_url class FibaroConnectFailed(Exception): """Error to indicate we cannot connect to fibaro home center.""" class FibaroAuthenticationFailed(Exception): """Error to indicate that authentication failed on fibaro home center.""" pyfibaro-0.8.2/pyfibaro/fibaro_data_helper.py000066400000000000000000000030201476015233600213150ustar00rootroot00000000000000"""Fibaro data helper provides static method to read and process data for the different fibaro API endpoints.""" from pyfibaro.fibaro_client import FibaroClient from pyfibaro.fibaro_device import DeviceModel CONTROLLER_TYPES = [ "com.fibaro.zwavePrimaryController", "com.fibaro.zigbeePrimaryController", "com.fibaro.niceEngine", ] def read_rooms(fibaro_client: FibaroClient) -> dict[int, str]: """Read dictionary mapping room ids to room name.""" return {room.fibaro_id: room.name for room in fibaro_client.read_rooms()} def read_devices( fibaro_client: FibaroClient, include_devices_from_plugins: bool = False ) -> list[DeviceModel]: """Read all enabled devices.""" devices = fibaro_client.read_devices() return [ device for device in devices if (not device.is_plugin or include_devices_from_plugins) and device.enabled ] def find_master_devices(devices: list[DeviceModel]) -> list[DeviceModel]: """Find master devices only.""" controller_ids = _get_controller_ids(devices) return [ device for device in devices if _is_master_device(device, controller_ids) ] def _get_controller_ids(devices: list[DeviceModel]) -> set[int]: return {device.fibaro_id for device in devices if device.type in CONTROLLER_TYPES} def _is_master_device(device: DeviceModel, controller_ids: set[int]) -> bool: return device.parent_fibaro_id in controller_ids or ( device.parent_fibaro_id == 0 and device.fibaro_id not in controller_ids ) pyfibaro-0.8.2/pyfibaro/fibaro_device.py000066400000000000000000000446201476015233600203170ustar00rootroot00000000000000"""Endpoint object to access the endpoint settings/info""" from __future__ import annotations import json import logging from typing import Any from .common.const import IGNORE_DEVICE from .common.rest_client import RestClient _LOGGER = logging.getLogger(__name__) def _to_bool(bool_value: bool | str) -> bool: """Convert any value to bool.""" if bool_value is not None: if isinstance(bool_value, bool): return bool_value if isinstance(bool_value, str): return bool_value.lower() == "true" return False class DeviceModel: """Model of a device.""" def __init__(self, data: dict, rest_client: RestClient, api_version: int) -> None: """Constructor.""" self.raw_data = data self._rest_client = rest_client self._api_version = api_version @property def fibaro_id(self) -> int: """Device id""" return self.raw_data.get("id") @property def name(self) -> str: """Device name""" return self.raw_data.get("name") @property def parent_fibaro_id(self) -> int: """Id of the parent device or 0 if there is no parent.""" return int(self.raw_data.get("parentId", 0)) @property def type(self) -> str | None: """Device type.""" return self.raw_data.get("type") @property def base_type(self) -> str | None: """Device base type.""" return self.raw_data.get("baseType") @property def room_id(self) -> int: """Room id of the device or 0 if no room is assigned.""" return int(self.raw_data.get("roomID", 0)) @property def properties(self) -> dict: """Get the properties.""" return self.raw_data.get("properties", {}) @property def actions(self) -> dict[str, int]: """Get the available actions.""" return self.raw_data.get("actions", {}) def has_interface(self, interface_name: str) -> bool: """Returns True if the device has the according interface defined. Interfaces are capabilities of the device. """ return interface_name in self.raw_data.get("interfaces", []) @property def unit(self) -> str | None: """Returns the unit of the devices value attribute or None.""" return self.properties.get("unit") @property def has_unit(self) -> bool: """Returns true if the device has a unit property.""" return "unit" in self.properties @property def endpoint_id(self) -> int: """Returns the endpoint id or 0 if there is no endpoint id. Endpoints are numbered if one physical device exposes different endpoints. """ return int(self.properties.get("endPointId", 0)) @property def has_endpoint_id(self) -> bool: """Returns true if the device has a unit property.""" return "endPointId" in self.properties @property def enabled(self) -> bool: """Returns the enabled flag of the device. If the device does not support that flag, True is returned. """ return self.raw_data.get("enabled", True) @property def visible(self) -> bool: """Returns the visible state of the device.""" return self.raw_data.get("visible", True) @property def is_plugin(self) -> bool: """Returns the device type. Returns: True for virtual devices and Quick Apps False for physical devices and controllers """ return self.raw_data.get("isPlugin", True) @property def battery_level(self) -> int: """Returns the battery level of the device in percent.""" percent = int(self.properties.get("batteryLevel", 0)) return 0 if percent == 255 else percent @property def has_battery_level(self) -> bool: """Returns true if the device has a unit property.""" return "batteryLevel" in self.properties @property def armed(self) -> bool: """Returns the armed state of the device if supported, otherwise False is returned. """ return _to_bool(self.properties.get("armed", False)) @property def has_armed(self) -> bool: """Returns true if the device has a unit property.""" return "armed" in self.properties @property def dead(self) -> bool: """Returns the state if the device is reachable if supported, otherwise False is returned. """ return _to_bool(self.properties.get("dead", False)) @property def has_dead(self) -> bool: """Returns true if the device has a dead property.""" return "dead" in self.properties @property def dead_reason(self) -> str | None: """Returns the dead reason or None if not supported.""" return self.properties.get("deadReason") @property def has_dead_reason(self) -> bool: """Returns true if the device has a deadReason property.""" return "deadReason" in self.properties @property def value(self) -> ValueModel: """Returns the value info.""" return ValueModel(self.properties, "value") @property def value_2(self) -> ValueModel: """Returns the value info.""" return ValueModel(self.properties, "value2") @property def state(self) -> ValueModel: """Returns the state info.""" return ValueModel(self.properties, "state") @property def color(self) -> ColorModel: """Returns the color info.""" return ColorModel(self.properties, "color") @property def last_color_set(self) -> ColorModel: """Returns the last set color info.""" return ColorModel(self.properties, "lastColorSet") @property def brightness(self) -> int: """Returns the brightness of the device. If missing, 0 is returned.""" return int(self.properties.get("brightness", 0)) @property def has_brightness(self) -> bool: """Returns true if the device has a brightness property.""" return "brightness" in self.properties @property def current_program(self) -> int: """Returns the current program of the device or None.""" return int(self.properties.get("currentProgram", 0)) @property def current_program_id(self) -> int: """Returns the current pogram id of the device or 0.""" return int(self.properties.get("currentProgramID", 0)) @property def mode(self) -> int: """Returns the mode or 0 if there is no mode.""" return int(self.properties.get("mode", 0)) @property def has_mode(self) -> bool: """Returns true if the device has a mode.""" return "mode" in self.properties @property def supported_modes(self) -> list[int]: """Returns the supported modes, for example for fan or hvac devices.""" if "supportedModes" in self.properties: modes = self.properties.get("supportedModes") if isinstance(modes, str) and modes != "": return [int(mode) for mode in modes.split(",")] if isinstance(modes, list): return [int(mode) for mode in modes] return [] @property def has_supported_modes(self) -> bool: """Returns true if the device has a supported modes property.""" return "supportedModes" in self.properties @property def operating_mode(self) -> int: """Returns the operating mode or 0 if there is no operating mode.""" return int(self.properties.get("operatingMode", 0)) @property def has_operating_mode(self) -> bool: """Returns true if the device has a operating mode.""" return "operatingMode" in self.properties @property def supported_operating_modes(self) -> list[int]: """Returns the supported operating modes, for example for fan or hvac devices.""" if "supportedOperatingModes" in self.properties: modes = self.properties.get("supportedOperatingModes") if isinstance(modes, str) and modes != "": return [int(mode) for mode in modes.split(",")] if isinstance(modes, list): return [int(mode) for mode in modes] return [] @property def has_supported_operating_modes(self) -> bool: """Returns true if the device has a supported operating modes property.""" return "supportedOperatingModes" in self.properties @property def thermostat_mode(self) -> str | None: """Returns the thermostat mode or None if there is no thermostat mode.""" return self.properties.get("thermostatMode") @property def has_thermostat_mode(self) -> bool: """Returns true if the device has a thermostat mode.""" return "thermostatMode" in self.properties @property def thermostat_operating_state(self) -> str | None: """Returns the thermostat operating state or None if there is no thermostat operating state. """ return self.properties.get("thermostatOperatingState") @property def has_thermostat_operating_state(self) -> bool: """Returns true if the device has a thermostat operating state.""" return "thermostatOperatingState" in self.properties @property def supported_thermostat_modes(self) -> list[str]: """Returns the supported thermostat modes, for example for hvac devices.""" if "supportedThermostatModes" in self.properties: return self.properties.get("supportedThermostatModes") return [] @property def has_supported_thermostat_modes(self) -> bool: """Returns true if the device has a supported thermostat modes property.""" return "supportedThermostatModes" in self.properties @property def heating_thermostat_setpoint(self) -> float: """Returns the heating thermostat setpoint or 0 if there is no value.""" return float(self.properties.get("heatingThermostatSetpoint", 0.0)) @property def has_heating_thermostat_setpoint(self) -> bool: """Returns true if the device has a heating thermostat setpoint property.""" return "heatingThermostatSetpoint" in self.properties @property def heating_thermostat_setpoint_future(self) -> float: """Returns the heating thermostat setpoint future or 0 if there is no value.""" return float(self.properties.get("heatingThermostatSetpointFuture", 0.0)) @property def has_heating_thermostat_setpoint_future(self) -> bool: """Returns true if the device has a heating thermostat setpoint future property.""" return "heatingThermostatSetpointFuture" in self.properties @property def target_level(self) -> float: """Returns the target level or 0 if there is no value.""" return float(self.properties.get("targetLevel", 0.0)) @property def has_central_scene_event(self) -> bool: """Returns true if the device can issue central scene events.""" return "centralSceneSupport" in self.properties @property def central_scene_event(self) -> list[SceneEvent]: """Returns list of potential scene events.""" central_scene_support = [] value = self.properties.get("centralSceneSupport") if isinstance(value, list): central_scene_support = value if isinstance(value, str): central_scene_support = json.loads(value) result = [] for central_scene in central_scene_support: key_id = int(central_scene.get("keyId")) key_attributes = central_scene.get("keyAttributes") result.append(SceneEvent(key_id, key_attributes)) return result def execute_action(self, action: str, arguments: list[Any] | None = None) -> Any: """Execute a device action. Params: action: name of the action to call arguments: list of arguments needed for the action """ if action not in self.actions: _LOGGER.warning( "The device %s has no action %s. Possible actions are %s", self.fibaro_id, action, self.actions, ) url = f"devices/{self.fibaro_id}/action/{action}" args_prepared = {"args": arguments} if arguments else {} _LOGGER.debug( "Execute %s for device %s with args %s.", action, self.fibaro_id, args_prepared, ) return self._rest_client.post(url, json=args_prepared) @staticmethod def read_devices(rest_client: RestClient, api_version: int) -> list[DeviceModel]: """Returns a list of devices.""" raw_data: list[dict] = rest_client.get("devices") devices: list[dict] = [] for device in raw_data: if device.get("type") in IGNORE_DEVICE: _LOGGER.debug("Ignore device: %s", device.get("id")) elif "id" not in device or "name" not in device: _LOGGER.debug( "Ignore device because it does not contain id or name") else: devices.append(device) return [DeviceModel(data, rest_client, api_version) for data in devices] class ValueModel: """Model to read out the value in several ways.""" def __init__(self, properties: dict, property_name: str) -> None: """Constructor.""" self._properties = properties self._property_name = property_name @property def has_value(self) -> bool: """Returns true if the device has a value property.""" return self._property_name in self._properties @property def is_bool_value(self) -> bool: """Returns True if the device value property is a bool, either as string or real bool type. """ if self.has_value: value = self._properties.get(self._property_name) if isinstance(value, bool): return True if isinstance(value, str): return value.lower() in ("true", "false") return False def str_value(self, default: str | None = None) -> str: """Returns the value converted to str or default if the object has no value. Raises: If no default is set, a TypeError is raised if no value exists. """ if self.has_value: return str(self._properties.get(self._property_name, default)) if default is None: raise TypeError("No value attribute available") return default def int_value(self, default: int | None = None) -> int: """Returns the value converted to int or default if conversion failes or the object has no value. Raises: If no default is set, a TypeError is raised for invalid values. """ try: return int(float(self._properties.get(self._property_name))) except TypeError as ex: if default is None: raise ex return default def float_value(self, default: float | None = None) -> float: """Returns the value converted to float or default if conversion failes or the object has no value. Raises: If no default is set, a TypeError is raised for invalid values. """ try: return float(self._properties.get(self._property_name)) except TypeError as ex: if default is None: raise ex return default def bool_value(self, default: bool | None = None) -> bool: """Returns the value converted to bool or default if conversion failes or the object has no value. Raises: If no default is set, a TypeError is raised for invalid values. """ try: value = self._properties.get(self._property_name) if isinstance(value, bool): return value if isinstance(value, str): if self.is_bool_value: return value.lower() == "true" return self.float_value(0) != 0 if isinstance(value, (int, float)): return value != 0 raise TypeError(f"Value cannot be converted to bool {value}") except TypeError as ex: if default is None: raise ex return default def dict_value(self, default: dict | None = None) -> dict: """Returns the value converted to a dict or default if conversion failes or the object has no value. Raises: If no default is set, an error is raised for invalid values. """ try: value = self._properties.get(self._property_name) if isinstance(value, dict): return value if isinstance(value, str): return json.loads(value) raise TypeError(f"Value cannot be converted to dict {value}") except TypeError as ex: if default is None: raise ex return default class ColorModel: """Model to read out the color.""" def __init__(self, properties: dict, property_name: str) -> None: """Constructor.""" self._properties = properties self._property_name = property_name @property def has_color(self) -> bool: """Returns true if the device has a value property.""" if self._property_name in self._properties: try: self.rgbw_color return True except (TypeError, ValueError): return False return False @property def rgbw_color(self) -> tuple[int, int, int, int]: """Returns the color as RGBW value. For RGB devices the white value is reported as 0. Raises: TypeError is raised for invalid values. """ color = self._properties.get(self._property_name) if color is None: raise TypeError("Color is None.") rgbw = tuple(int(i) for i in color.split(",")) if len(rgbw) != 4: raise TypeError(f"Color does not have 4 parts: {color}") return rgbw class SceneEvent: """Model to read out the scene events.""" def __init__(self, key_id: int, key_attributes: list[str]) -> None: """Constructor.""" self._key_id = key_id self._key_attributes = key_attributes @property def key_id(self) -> int: """Returns the key id.""" return self._key_id @property def key_event_types(self) -> list[str]: """Returns the possible key event types.""" return self._key_attributes pyfibaro-0.8.2/pyfibaro/fibaro_device_manager.py000066400000000000000000000040261476015233600220050ustar00rootroot00000000000000"""High level controller to access devices over the Fibaro API. This adds a structural layer over the plain fibaro API which does a lot of device detection logic and exposes properties in a more understandable way. """ from __future__ import annotations import logging from collections.abc import Callable from .fibaro_state_multiplexer import FibaroStateMultiplexer from .fibaro_client import FibaroClient from .fibaro_device import DeviceModel from .fibaro_state_resolver import FibaroEvent _LOGGER = logging.getLogger(__name__) class FibaroDeviceManager: """Controller to access fibaro API in a more structured way.""" def __init__( self, fibaro_client: FibaroClient, include_devices_from_plugins: bool = False ) -> None: """Construct the fibaro device manager. - Load initial data - Open push channel""" self._fibaro_client = fibaro_client self._fibaro_state_multiplexer = FibaroStateMultiplexer( fibaro_client, include_devices_from_plugins ) self._fibaro_state_multiplexer.start() def add_change_listener( self, fibaro_id: int, listener: Callable[[DeviceModel], None] ) -> Callable[[], None]: """Add a listener to get property changes. Provides the updated device data. Returns: Callback which can be used to unregister the listener""" return self._fibaro_state_multiplexer.add_change_listener(fibaro_id, listener) def add_event_listener( self, fibaro_id: int, listener: Callable[[FibaroEvent], None] ) -> Callable[[], None]: """Add scene event listener. Returns: Callback which can be used to unregister the listener""" return self._fibaro_state_multiplexer.add_event_listener(fibaro_id, listener) def get_devices(self) -> list[DeviceModel]: """Get current devices from Fibaro Home Center.""" return self._fibaro_state_multiplexer.get_devices() def close(self) -> None: """Close push channel.""" self._fibaro_state_multiplexer.stop() pyfibaro-0.8.2/pyfibaro/fibaro_info.py000066400000000000000000000051711476015233600200110ustar00rootroot00000000000000"""Endpoint object to access the endpoint settings/info""" import logging from .common.const import ( API_VERSION_MATCHER, MANUFACTURER_NAME_MATCHER, MODEL_NAME_MATCHER, ) from .common.rest_client import RestClient _LOGGER = logging.getLogger(__name__) class InfoModel: """Fibaro info.""" def __init__(self, rest_client: RestClient) -> None: """Load the data.""" self.raw_data: dict = rest_client.get("settings/info") @property def current_version(self) -> str: """Returns the software version.""" return self.raw_data.get("softVersion") @property def serial_number(self) -> str: """Returns the serial number of the home center.""" return self.raw_data.get("serialNumber") @property def hc_name(self) -> str: """Returns the serial number of the home center.""" return self.raw_data.get("hcName") @property def mac_address(self) -> str: """Returns the mac address of the home center.""" return self.raw_data.get("mac") @property def api_version(self) -> int: """Returns the API version. As of writing version 4 and 5 was supported. When the version cannot be evaluated, it will fallback to version 5. """ serial_number = self.serial_number for item in API_VERSION_MATCHER.items(): if serial_number.startswith(item[0]): _LOGGER.debug( "API version %s found by pattern %s", item[1], item[0]) return item[1] return 5 @property def platform(self) -> str: """Returns the model abbreviation of the home center. One of HC3, HC3L, YH, HC2 or HCL. """ # This API is only available on newer models, therefore # we simulate an answer for older models. return self.raw_data.get("platform", self.serial_number[:3]) @property def model_name(self) -> str: """Returns the name of the hub model. When the model cannot be evaluated, it will fallback to Hub. """ serial_number = self.serial_number for item in MODEL_NAME_MATCHER.items(): if serial_number.startswith(item[0]): return item[1] return "Hub" @property def manufacturer_name(self) -> str: """Returns the name of the hub model. When the model cannot be evaluated, it will fallback to Fibaro. """ serial_number = self.serial_number for item in MANUFACTURER_NAME_MATCHER.items(): if serial_number.startswith(item[0]): return item[1] return "Fibaro" pyfibaro-0.8.2/pyfibaro/fibaro_login.py000066400000000000000000000006551476015233600201700ustar00rootroot00000000000000"""Endpoint object to access the endpoint settings/info""" from .common.rest_client import RestClient class LoginModel: """Fibaro login.""" def __init__(self, rest_client: RestClient) -> None: """Load the data.""" self.raw_data: dict = rest_client.get("loginStatus") @property def is_logged_in(self) -> bool: """Returns the login status.""" return self.raw_data.get("status") pyfibaro-0.8.2/pyfibaro/fibaro_room.py000066400000000000000000000013241476015233600200260ustar00rootroot00000000000000"""Endpoint object to access the endpoint settings/info""" from __future__ import annotations from .common.rest_client import RestClient class RoomModel: """Model of a room.""" def __init__(self, data: dict) -> None: """One room.""" self.raw_data = data @property def fibaro_id(self) -> int: """Room id""" return self.raw_data.get("id") @property def name(self) -> str: """Room name""" return self.raw_data.get("name") @staticmethod def read_rooms(rest_client: RestClient) -> list[RoomModel]: """Returns a list of rooms.""" raw_data: list = rest_client.get("rooms") return [RoomModel(data) for data in raw_data] pyfibaro-0.8.2/pyfibaro/fibaro_scene.py000066400000000000000000000054371476015233600201600ustar00rootroot00000000000000"""Endpoint object to access the endpoint settings/info""" from __future__ import annotations import logging from .common.rest_client import RestClient _LOGGER = logging.getLogger(__name__) class SceneModel: """Model of a scene.""" def __init__(self, data: dict, rest_client: RestClient, api_version: int) -> None: """One scene.""" self.raw_data = data self._rest_client = rest_client self._api_version = api_version @property def fibaro_id(self) -> int: """Scene id""" return self.raw_data.get("id") @property def name(self) -> str: """Scene name""" return self.raw_data.get("name") @property def room_id(self) -> int: """Room id of the scene or 0 if no room is assigned.""" if self._api_version == 4: return int(self.raw_data.get("roomID", 0)) else: return int(self.raw_data.get("roomId", 0)) @property def visible(self) -> bool: """Returns the visible state of the scene.""" if self._api_version == 4: return self.raw_data.get("visible", True) else: return not self.raw_data.get("hidden", False) def start(self, user_pin: str | None = None) -> None: """Start a scene.""" if self._api_version == 4: if user_pin: raise NotImplementedError("Not supported on old fibaro hubs") self._send_action_v4("start") else: self._send_action_v5("execute", user_pin) def stop(self, user_pin: str | None = None) -> None: """Stop a scene.""" if self._api_version == 4: if user_pin: raise NotImplementedError("Not supported on old fibaro hubs") self._send_action_v4("stop") else: self._send_action_v5("kill", user_pin) def _send_action_v4(self, action: str) -> None: url = f"scenes/{self.fibaro_id}/action/{action}" self._rest_client.post(url) def _send_action_v5(self, action: str, user_pin: str | None) -> None: url = f"scenes/{self.fibaro_id}/{action}" if user_pin: self._rest_client.post(url, {}, http_headers={"Fibaro-User-PIN": user_pin}) else: self._rest_client.post(url, {}) @staticmethod def read_scenes(rest_client: RestClient, api_version: int) -> list[SceneModel]: """Returns a list of scenes.""" raw_data: list = rest_client.get("scenes") scenes: list[dict] = [] for scene in raw_data: if "id" not in scene or "name" not in scene: _LOGGER.debug("Ignore scene because it does not contain id or name") else: scenes.append(scene) return [SceneModel(data, rest_client, api_version) for data in scenes] pyfibaro-0.8.2/pyfibaro/fibaro_state_handler.py000066400000000000000000000045411476015233600216730ustar00rootroot00000000000000"""State handler for fibaro home center.""" import logging import threading from .common.const import REFRESH_STATE_TIMEOUT from .common.rest_client import RestClient _LOGGER = logging.getLogger(__name__) class FibaroStateHandler(threading.Thread): """State handler which uses the refreshStates endpoint to pull state changes from home center. """ def __init__(self, rest_client: RestClient, callback: callable) -> None: """Create the state handler and start the background thread.""" super().__init__(name=f"Thread {__name__}") self._rest_client = rest_client self._callback = callback self._stop_flag = threading.Event() # stop unconditionally on exit self.daemon = True self.start() def run(self) -> None: """State Handler main loop which runs in this thread.""" _LOGGER.info("Starting the state change handler") last = 0 while not self._is_stopped_flag(): sleep_time = 1 attempt = 1 success = False while not success and not self._is_stopped_flag(): try: state = self._rest_client.get( f"refreshStates?last={last}", timeout=REFRESH_STATE_TIMEOUT ) _LOGGER.debug(state) last = state.get("last") success = True except Exception as ex: _LOGGER.warning("Connection Error (%s). Error: %s", attempt, ex) attempt += 1 if attempt == 3: sleep_time = 30 _LOGGER.info("Fallback to 30-second connection retry timer.") self._stop_flag.wait(sleep_time) if success: try: self._callback(state) except Exception as ex: _LOGGER.warning("Error in state change callback: %s", ex) _LOGGER.info("State change handler stopped.") def _is_stopped_flag(self) -> bool: return self._stop_flag.is_set() def stop(self) -> None: """Stop the state handler.""" _LOGGER.debug("Stopping the state change handler") # no effect on pending request self._rest_client.close() self._stop_flag.set() pyfibaro-0.8.2/pyfibaro/fibaro_state_multiplexer.py000066400000000000000000000074471476015233600226400ustar00rootroot00000000000000"""Fibaro state multiplexer receives all information from the push channel and provides methods to register listeners for specific devices. """ import logging from typing import Any from collections.abc import Callable from .fibaro_client import FibaroClient from .fibaro_device import DeviceModel from .fibaro_data_helper import read_devices from .fibaro_state_resolver import FibaroEvent, FibaroStateChange, FibaroStateResolver _LOGGER = logging.getLogger(__name__) class FibaroStateMultiplexer: """State and event multiplexer.""" def __init__( self, fibaro_client: FibaroClient, include_devices_from_plugins: bool = False ) -> None: """Initialize the fibaro state multiplexer.""" self._fibaro_client = fibaro_client self._include_devices_from_plugins = include_devices_from_plugins self._devices: dict[int, DeviceModel] = {} self._change_listeners: dict[int, list[Callable[[DeviceModel], None]]] = {} self._event_listeners: dict[int, list[Callable[[FibaroEvent], None]]] = {} def start(self) -> None: """Connect push channel and load initial device state. This starts change and event dispatching.""" self._devices = { device.fibaro_id: device for device in read_devices( self._fibaro_client, self._include_devices_from_plugins ) } self._fibaro_client.register_update_handler(self._on_change) def stop(self) -> None: """Disconnect push channel so that no change and events are dispatched anymore.""" self._fibaro_client.unregister_update_handler() self._devices = {} def add_change_listener( self, fibaro_id: int, listener: Callable[[DeviceModel], None] ) -> Callable[[], None]: """Add a listener to get property changes.""" change_listeners = self._change_listeners.setdefault(fibaro_id, []) change_listeners.append(listener) return lambda: change_listeners.remove(listener) def add_event_listener( self, fibaro_id: int, listener: Callable[[FibaroEvent], None] ) -> Callable[[], None]: """Add event listener.""" event_listeners = self._event_listeners.setdefault(fibaro_id, []) event_listeners.append(listener) return lambda: event_listeners.remove(listener) def get_devices(self) -> list[DeviceModel]: """Return the current device state.""" return list(self._devices.values()) def _on_change(self, state: Any) -> None: # update internal device model and notify registered listeners resolver = FibaroStateResolver(state) for state_change in resolver.get_state_updates(): fibaro_id = state_change.fibaro_id device = self._devices.get(fibaro_id) if device: self._update_device_data(device, state_change) for listener in self._change_listeners.get(fibaro_id, []): listener(device) for event in resolver.get_events(): # event does not always have a fibaro id, therefore it is # essential that we first check for it fibaro_id = event.fibaro_id if fibaro_id: for listener in self._event_listeners.get(fibaro_id, []): listener(event) def _update_device_data( self, device: DeviceModel, state_change: FibaroStateChange ) -> None: # update the internal data object to keep it always current for key, value in state_change.property_changes.items(): device.properties[key] = value _LOGGER.debug( "New state %s[%s].%s = %s", device.name, device.fibaro_id, key, str( value) ) pyfibaro-0.8.2/pyfibaro/fibaro_state_resolver.py000066400000000000000000000055701476015233600221220ustar00rootroot00000000000000"""State object resolver for fibaro home center.""" from __future__ import annotations import logging from typing import Any _LOGGER = logging.getLogger(__name__) class FibaroEvent: """A fibaro event returned by state handler.""" def __init__(self, data: dict) -> None: """Constructor to init the object with the raw data.""" self.raw_data = data @property def event_type(self) -> str: """Returns the event type as string. Most known event is "CentralSceneEvent" which is used for button press events. """ return self.raw_data.get("type", "") @property def fibaro_id(self) -> int | None: """The device id which throws the event. Not all events are related to a device.""" data = self.event_data # id is used by HC3, deviceId by HC2 fibaro_id = data.get("id", data.get("deviceId")) if fibaro_id is None: return None return int(fibaro_id) @property def event_data(self) -> dict: """Returns the event data in raw format.""" return self.raw_data.get("data", {}) @property def key_id(self) -> int: """Returns the key id.""" return int(self.event_data.get("keyId", 0)) @property def key_event_type(self) -> str: """Returns the key event attribute. For example Pressed, Released, HeldDown, ... """ return self.event_data.get("keyAttribute", "") class FibaroStateChange: """A fibaro state change returned by state handler.""" def __init__(self, data: dict) -> None: """Constructor to init the object with the raw data.""" self.raw_data = data @property def fibaro_id(self) -> int: """The device id which throws the event.""" return int(self.raw_data.get("id")) @property def property_changes(self) -> dict[str, Any]: """The changes in the device properties.""" result: dict[str, Any] = {} for property_name, value in self.raw_data.items(): # Ignore some attributes which are not relevant or returned separately if property_name in ("log", "logTemp", "id"): continue result[property_name] = value return result class FibaroStateResolver: """State resolver allows typed access to a state object retunred by fibaro home center.""" def __init__(self, data: dict) -> None: """Init the object with the raw event object.""" self.raw_data = data def get_events(self) -> list[FibaroEvent]: """Extract events from the state handle object.""" return [FibaroEvent(data) for data in self.raw_data.get("events", [])] def get_state_updates(self) -> list[FibaroStateChange]: """Extract state changes from the state handle object.""" return [FibaroStateChange(data) for data in self.raw_data.get("changes", [])] pyfibaro-0.8.2/pyproject.toml000066400000000000000000000001271476015233600162570ustar00rootroot00000000000000[build-system] requires = ["setuptools>=70.0"] build-backend = "setuptools.build_meta" pyfibaro-0.8.2/requirements-test.txt000066400000000000000000000000571476015233600176060ustar00rootroot00000000000000requests_mock~=1.12 pytest~=8.3 pytest-cov~=6.0pyfibaro-0.8.2/requirements.txt000066400000000000000000000000161476015233600166240ustar00rootroot00000000000000requests~=2.32pyfibaro-0.8.2/script/000077500000000000000000000000001476015233600146475ustar00rootroot00000000000000pyfibaro-0.8.2/script/distribute000077500000000000000000000003341476015233600167530ustar00rootroot00000000000000#!/usr/bin/env bash # Distribute the library to pypi.org. # Delete dist directory to ensure a clean build rm -r dist python -m build python -m twine upload dist/* # python -m twine upload --repository testpypi dist/* pyfibaro-0.8.2/script/setup000077500000000000000000000004771476015233600157450ustar00rootroot00000000000000#!/usr/bin/env bash # Setup the dependencies. # Stop on errors set -e # Runtime dependencies python -m pip install -r requirements.txt # Test dependencies python -m pip install -r requirements-test.txt # Tools for distribution to pypi.org python -m pip install --upgrade build python -m pip install --upgrade twine pyfibaro-0.8.2/script/test000077500000000000000000000001621476015233600155530ustar00rootroot00000000000000#!/usr/bin/env bash # Run all tests with code coverage. python -m pytest --cov=pyfibaro --cov-report term-missingpyfibaro-0.8.2/setup.cfg000066400000000000000000000017521476015233600151710ustar00rootroot00000000000000[metadata] name = pyfibaro version = 0.8.2 description = Simple API to access fibaro home center from any Python 3 script. Designed for Home Assistant (but not only) long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/rappenze/pyfibaro author = Roman Appenzeller author_email = license = MIT license_files = LICENSE classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only 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 Topic :: Home Automation [options] packages = pyfibaro pyfibaro.common install_requires = requests~=2.32 python_requires = >=3.11 include_package_data = True package_dir = =. [tool:pytest] testpaths = tests pyfibaro-0.8.2/tests/000077500000000000000000000000001476015233600145055ustar00rootroot00000000000000pyfibaro-0.8.2/tests/__init__.py000066400000000000000000000000371476015233600166160ustar00rootroot00000000000000"""Unit tests for pyfibaro.""" pyfibaro-0.8.2/tests/fixture/000077500000000000000000000000001476015233600161735ustar00rootroot00000000000000pyfibaro-0.8.2/tests/fixture/device-armed.json000066400000000000000000000213531476015233600214170ustar00rootroot00000000000000[ { "id": 357, "name": "Main Fenster", "roomID": 8, "type": "com.fibaro.zwaveDevice", "baseType": "com.fibaro.device", "enabled": true, "visible": false, "isPlugin": false, "parentId": 1, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "battery", "zwave", "zwaveAssociation", "zwaveConfiguration", "zwaveMultiChannelAssociation", "zwaveSlaveRouting", "zwaveWakeup" ], "properties": { "batteryLevel": "100", "batteryLowNotification": "true", "categories": "[\"other\"]", "configured": true, "dead": "false", "deadReason": "", "defInterval": "0", "deviceControlType": "0", "deviceIcon": "28", "deviceSpecificData": "h'bc33acfffe4c99e8", "deviceSpecificIdType": "Serial Number", "deviceState": "Configured", "emailNotificationID": "0", "emailNotificationType": "0", "endPointId": "0", "lastWorkingRoute": "[1]", "lastWorkingRouteRequestStatus": "ok", "lastWorkingRouteRequestTimestamp": "0", "lastWorkingRouteResponseTimestamp": "1652642308", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "maxInterval": "0", "minInterval": "0", "model": "", "neighborList": "[1,12,60,64,71,100,106,110,127,134,145,155,165,288,298,329,341]", "neighborListRequestStatus": "ok", "neighborListRequestTimestamp": "0", "neighborListResponseTimestamp": "1652642308", "nodeId": "60", "parameters": [], "parametersTemplate": "0", "pollingTimeSec": 0, "productInfo": "1,154,0,4,0,4,8,26", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "saveLogs": "true", "serialNumber": "h'bc33acfffe4c99e8", "smsNotificationID": "0", "smsNotificationType": "0", "stepInterval": "0", "useTemplate": "false", "userDescription": "", "wakeUpTime": 43200, "zwaveCompany": "Sensative", "zwaveInfo": "3,7,13", "zwaveSoftwareVersion": "{}", "zwaveVersion": "8.26" }, "actions": { "getLastWorkingRoute": 0, "getNeighborList": 0, "getParameter": 1, "reconfigure": 0, "requestNodeNeighborUpdate": 0, "setInterval": 1, "setParameter": 2 }, "created": 1652642219, "modified": 1652642219, "sortOrder": 200 }, { "id": 358, "name": "Unused", "roomID": 8, "type": "com.fibaro.motionSensor", "baseType": "com.fibaro.securitySensor", "enabled": true, "visible": false, "isPlugin": false, "parentId": 357, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "battery", "fibaroAlarm", "fibaroAlarmArm", "fibaroBreach", "fibaroFirmwareUpdate", "zwave", "zwaveIndicator", "zwaveMultiChannelAssociation", "zwaveWakeup" ], "properties": { "pollingTimeSec": 0, "wakeUpTime": 43200, "zwaveCompany": "Sensative", "zwaveInfo": "3,7,13", "zwaveVersion": "8.26", "alarmDelay": "0", "alarmExclude": "false", "alarmTimeTimestamp": "0", "armConditions": "{\"auto\":false,\"devices\":[{\"id\":358,\"propertyName\":\"value\",\"propertyValue\":\"0\"}],\"time\":0}", "armConfig": "0", "armDelay": "0", "armError": "{}", "armTimeTimestamp": "0", "armed": 0, "batteryLevel": "100", "batteryLowNotification": "true", "categories": "[\"security\"]", "configured": true, "dead": "false", "deadReason": "", "defInterval": "0", "deviceControlType": "0", "deviceIcon": "21", "emailNotificationID": "0", "emailNotificationType": "0", "endPointId": "0", "fibaroAlarm": "false", "firmwareUpdate": "{\"info\":\"\",\"progress\":0,\"status\":\"UpToDate\",\"updateVersion\":\"8.26\"}", "indicatorSupportedProperties": "[]", "indicatorValue": "[{\"id\":0,\"properties\":{\"0\":0}}]", "lastBreached": "1667074901", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "maxInterval": "0", "minInterval": "0", "model": "", "nodeId": "60", "parametersTemplate": "0", "productInfo": "1,154,0,4,0,4,8,26", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "saveLogs": "true", "serialNumber": "h'bc33acfffe4c99e8", "smsNotificationID": "0", "smsNotificationType": "0", "stepInterval": "0", "updateVersion": "", "useTemplate": "false", "userDescription": "", "value": "false" }, "actions": { "abortUpdate": 1, "forceArm": 0, "meetArmConditions": 0, "reconfigure": 0, "retryUpdate": 1, "setArmed": 1, "setIndicatorValue": 1, "setInterval": 1, "startUpdate": 1, "updateFirmware": 1 }, "created": 1652642219, "modified": 1652642219, "sortOrder": 201 }, { "id": 359, "name": "Fenster", "roomID": 8, "type": "com.fibaro.windowSensor", "baseType": "com.fibaro.doorWindowSensor", "enabled": true, "visible": true, "isPlugin": false, "parentId": 357, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "battery", "fibaroAlarm", "fibaroAlarmArm", "fibaroBreach", "zwave", "zwaveAlarm", "zwaveWakeup" ], "properties": { "pollingTimeSec": 0, "wakeUpTime": 43200, "zwaveCompany": "Sensative", "zwaveInfo": "3,7,13", "zwaveVersion": "8.26", "alarmDelay": "0", "alarmExclude": "false", "alarmLevel": "0", "alarmTimeTimestamp": "0", "alarmType": "0", "armConditions": "{\"auto\":false,\"devices\":[{\"id\":359,\"propertyName\":\"value\",\"propertyValue\":\"0\"}],\"time\":0}", "armConfig": "0", "armDelay": "0", "armError": "{}", "armTimeTimestamp": "0", "armed": "true", "batteryLevel": "100", "batteryLowNotification": "true", "categories": "[\"security\"]", "configured": true, "dead": "false", "deadReason": "", "defInterval": "0", "deviceControlType": "0", "deviceIcon": "14", "emailNotificationID": "0", "emailNotificationType": "0", "endPointId": "0", "fibaroAlarm": "false", "lastBreached": "1670568872", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "maxInterval": "0", "minInterval": "0", "model": "", "nodeId": "60", "parametersTemplate": "0", "productInfo": "1,154,0,4,0,4,8,26", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "saveLogs": "true", "serialNumber": "h'bc33acfffe4c99e8", "smsNotificationID": "0", "smsNotificationType": "0", "stepInterval": "0", "useTemplate": "false", "userDescription": "", "value": "false" }, "actions": { "forceArm": 0, "meetArmConditions": 0, "reconfigure": 0, "setArmed": 1, "setInterval": 1 }, "created": 1652642219, "modified": 1652642219, "sortOrder": 202 } ]pyfibaro-0.8.2/tests/fixture/device-hc3.json000066400000000000000000000172421476015233600210060ustar00rootroot00000000000000[ { "id": 1, "name": "zwave", "roomID": 219, "view": [], "type": "com.fibaro.zwavePrimaryController", "baseType": "", "enabled": true, "visible": false, "isPlugin": false, "parentId": 0, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "zwave" ], "properties": { "UIMessageSendTime": 0, "autoConfig": 0, "configured": true, "date": "a", "dead": true, "deadReason": "Connection problem", "deviceControlType": 1, "deviceIcon": 28, "deviceRole": "Other", "disabled": 1, "emailNotificationID": 0, "emailNotificationType": 0, "endPoint": 0, "endPointId": 0, "liliOffCommand": "", "liliOnCommand": "", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "nodeID": 1, "nodeId": 1, "parameters": [], "parametersTemplate": 0, "pollingDeadDevice": false, "pollingTime": 1354829138, "pollingTimeNext": 1354837724, "pollingTimeSec": 125, "productInfo": "", "pushNotificationID": 0, "pushNotificationType": 0, "remoteGatewayId": 0, "requestNodeNeighborStat": 0, "requestNodeNeighborStatTimeStemp": "", "requestNodeNeighborState": "", "requestNodeNeighborStateTimeStemp": "", "saveLogs": true, "serialNumber": "", "showChildren": 1, "smsNotificationID": 0, "smsNotificationType": 0, "status": "STAT_IDLE", "sunriseHour": "07:56", "sunsetHour": "16:34", "useTemplate": true, "userDescription": "", "value": 0, "zwaveBuildVersion": "3.67", "zwaveCompany": "Unknown", "zwaveInfo": "", "zwaveRegion": "EU", "zwaveVersion": "4.33" }, "actions": { "pollingDeadDevice": 1, "pollingTimeSec": 1, "reconfigure": 0, "requestNodeNeighborUpdate": 1, "turnOff": 0, "turnOn": 0 }, "created": 1658836340, "modified": 1670349015, "sortOrder": 1 }, { "id": 2, "name": "admin", "roomID": 219, "view": [], "type": "HC_user", "baseType": "com.fibaro.voipUser", "enabled": true, "visible": true, "isPlugin": false, "parentId": 0, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "voip" ], "properties": { "Email": "username@test.com", "HotelModeRoom": 0, "LastPwdChange": 1670349173, "Latitude": 52.4320294933, "Location": "52.4320294933;16.8449900900", "LocationTime": "2012-12-06 12:15", "LocationTimestamp": 1354792521, "Longitude": 16.84499009, "PreviousLatitude": 52.4320252015, "PreviousLocation": "52.4320252015;16.8449947542", "PreviousLocationTime": "2012-12-06 12:14", "PreviousLocationTimestamp": 1354792461, "PreviousLongitude": 16.844994754200003, "SendNotifications": true, "TrackUser": 0, "UserType": "superuser", "atHome": false, "deviceIcon": 91, "fidUuid": "", "firmwareUpdateLevel": 0, "integrationPin": "994edd12ffd74e0bd725c3efe3310f23a10a3bb6", "saveLogs": true, "sipDisplayName": "_", "sipUserEnabled": true, "sipUserID": "1", "sipUserPassword": "", "skin": "light", "skinSetting": "manual", "useIntegrationPin": false, "useOptionalArmPin": false, "usePin": false }, "actions": { "sendDefinedEmailNotification": 1, "sendDefinedSMSNotification": 2, "sendEmail": 2, "sendGlobalEmailNotifications": 1, "sendGlobalPushNotifications": 1, "sendGlobalSMSNotifications": 1, "sendPush": 1, "setSipDisplayName": 1, "setSipUserID": 1, "setSipUserPassword": 1 }, "created": 1658836340, "modified": 1670349173, "sortOrder": 2 }, { "id": 3, "name": "YR Weather", "roomID": 219, "view": [ { "type": "json" } ], "type": "com.fibaro.yrWeather", "baseType": "com.fibaro.weather", "enabled": true, "visible": true, "isPlugin": true, "parentId": 0, "viewXml": true, "hasUIView": false, "configXml": false, "interfaces": [], "properties": { "ConditionCode": 20, "ConditionCodeConverted": 0, "Humidity": 99.0, "Pressure": 1019.2, "Temperature": -0.5, "WeatherCondition": "fog", "Wind": 3.96, "categories": [ "other" ], "dead": false, "deadReason": "", "deviceControlType": 1, "deviceIcon": 158, "deviceRole": "Other", "emailNotificationID": 0, "emailNotificationType": 0, "hidden": false, "icon": { "path": "/plugins/com.fibaro.yrWeather/img/icon.png", "source": "HC" }, "log": "", "logTemp": "", "manufacturer": "", "model": "", "pushNotificationID": 0, "pushNotificationType": 0, "remoteGatewayId": 0, "saveLogs": true, "smsNotificationID": 0, "smsNotificationType": 0, "supportedDeviceRoles": [ "Other" ], "ui.config.hidden.caption": "This device is hidden in the system.", "ui.config.hidden.enabled": true, "ui.config.section1.caption": "Configuration", "ui.config.section1.enabled": true, "userDescription": "", "yrSymbolCode": "fog" }, "actions": {}, "created": 1658836340, "modified": 1670349015, "sortOrder": 3 }, { "id": 7, "name": "Nice Engine", "roomID": 0, "view": [], "type": "com.fibaro.niceEngine", "baseType": "", "enabled": true, "visible": false, "isPlugin": false, "parentId": 0, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [], "properties": { "icon": {} }, "actions": {}, "created": 1658836340, "modified": 1670349015, "sortOrder": 4 }, { "id": 8, "name": "zigbee", "roomID": 0, "view": [], "type": "com.fibaro.zigbeePrimaryController", "baseType": "", "enabled": true, "visible": false, "isPlugin": false, "parentId": 0, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [], "properties": {}, "actions": {}, "created": 1658836340, "modified": 1658836340, "sortOrder": 5 } ]pyfibaro-0.8.2/tests/fixture/device-scene-support-hc3.json000066400000000000000000000401271476015233600236110ustar00rootroot00000000000000[ { "id": 175, "name": "Licht Roman Master", "roomID": 364, "view": [], "type": "com.fibaro.zwaveDevice", "baseType": "com.fibaro.device", "enabled": true, "visible": false, "isPlugin": false, "parentId": 1, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "polling", "zwave", "zwaveAssociation", "zwaveConfiguration", "zwaveMultiChannelAssociation", "zwaveSlaveRouting" ], "properties": { "categories": [ "other" ], "configured": true, "dead": false, "deadReason": "", "deviceControlType": 1, "deviceIcon": 28, "deviceRole": "Other", "deviceSpecificData": "h'00000000000022a1", "deviceSpecificIdType": "Serial Number", "deviceState": "Configured", "emailNotificationID": 0, "emailNotificationType": 0, "endPointId": 0, "icon": {}, "lastWorkingRoute": [ 1 ], "lastWorkingRouteRequestStatus": "ok", "lastWorkingRouteRequestTimestamp": 0, "lastWorkingRouteResponseTimestamp": 1694667148, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "neighborList": [ 1, 36, 80, 84, 91, 95, 116, 120, 126, 130, 147, 152, 154, 159, 165, 180, 185, 219, 222, 271, 308, 331, 361, 377, 381, 389, 394 ], "neighborListRequestStatus": "ok", "neighborListRequestTimestamp": 1596812962, "neighborListResponseTimestamp": 1694667148, "nodeId": 38, "parameters": [ { "id": 9, "lastReportedValue": 1, "lastSetValue": 1, "size": 1, "value": 1 }, { "id": 10, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 11, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 12, "lastReportedValue": 50, "lastSetValue": 50, "size": 2, "value": 50 }, { "id": 13, "lastReportedValue": 5, "lastSetValue": 5, "size": 2, "value": 5 }, { "id": 20, "lastReportedValue": 2, "lastSetValue": 2, "size": 1, "value": 2 }, { "id": 21, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 27, "lastReportedValue": 15, "lastSetValue": 15, "size": 1, "value": 15 }, { "id": 28, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 29, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 30, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 31, "lastReportedValue": 255, "lastSetValue": 255, "size": 2, "value": 255 }, { "id": 32, "lastReportedValue": 0, "lastSetValue": 0, "size": 2, "value": 0 }, { "id": 33, "lastReportedValue": 99, "lastSetValue": 99, "size": 2, "value": 99 }, { "id": 35, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 36, "lastReportedValue": 255, "lastSetValue": 255, "size": 2, "value": 255 }, { "id": 37, "lastReportedValue": 0, "lastSetValue": 0, "size": 2, "value": 0 }, { "id": 38, "lastReportedValue": 99, "lastSetValue": 99, "size": 2, "value": 99 }, { "id": 40, "lastReportedValue": 3, "lastSetValue": 3, "size": 1, "value": 3 }, { "id": 41, "lastReportedValue": 2, "lastSetValue": 2, "size": 1, "value": 2 }, { "id": 42, "lastReportedValue": 3, "lastSetValue": 3, "size": 1, "value": 3 }, { "id": 43, "lastReportedValue": 1, "lastSetValue": 1, "size": 1, "value": 1 }, { "id": 44, "lastReportedValue": 600, "lastSetValue": 600, "size": 2, "value": 600 }, { "id": 50, "lastReportedValue": 20, "lastSetValue": 20, "size": 1, "value": 20 }, { "id": 51, "lastReportedValue": 10, "lastSetValue": 10, "size": 1, "value": 10 }, { "id": 53, "lastReportedValue": 100, "lastSetValue": 100, "size": 2, "value": 100 }, { "id": 58, "lastReportedValue": 3600, "lastSetValue": 3600, "size": 2, "value": 3600 }, { "id": 59, "lastReportedValue": 3600, "lastSetValue": 3600, "size": 2, "value": 3600 }, { "id": 60, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 } ], "parametersTemplate": 779, "pollingInterval": -1, "pollingTimeSec": 0, "productInfo": "1,15,4,3,16,0,3,4", "pushNotificationID": 0, "pushNotificationType": 0, "remoteGatewayId": 0, "saveLogs": true, "securityLevel": "", "securitySchemes": [], "serialNumber": "h'00000000000022a1", "smsNotificationID": 0, "smsNotificationType": 0, "supportedDeviceRoles": [ "Other" ], "useTemplate": true, "userDescription": "", "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveSoftwareVersion": {}, "zwaveVersion": "3.4" }, "actions": { "getParameter": 1, "poll": 0, "setParameter": 2 }, "created": 1672822156, "modified": 1678443238, "sortOrder": 69 }, { "id": 176, "name": "Licht Roman inaktiv", "roomID": 364, "view": [ { "assetsPath": "dynamic-plugins/com.fibaro.binarySwitch", "name": "com.fibaro.binarySwitch", "translatesPath": "/assets/i18n/com.fibaro.binarySwitch", "type": "ts" }, { "assetsPath": "dynamic-plugins/energy", "name": "energy", "translatesPath": "/assets/i18n/energy", "type": "ts" }, { "assetsPath": "dynamic-plugins/power", "name": "power", "translatesPath": "/assets/i18n/power", "type": "ts" } ], "type": "com.fibaro.binarySwitch", "baseType": "com.fibaro.actor", "enabled": true, "visible": false, "isPlugin": false, "parentId": 175, "viewXml": false, "hasUIView": true, "configXml": false, "interfaces": [ "energy", "fibaroFirmwareUpdate", "light", "power", "zwave", "zwaveAlarm", "zwaveMultiChannelAssociation", "zwaveProtection" ], "properties": { "pollingTimeSec": 0, "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveVersion": "3.4", "RFProtectionState": 0, "RFProtectionSupport": 3, "alarmLevel": 0, "alarmType": 0, "categories": [ "lights" ], "configured": true, "dead": false, "deadReason": "", "deviceControlType": 2, "deviceIcon": 2, "deviceRole": "Light", "endPointId": 0, "energy": 48.32, "firmwareUpdate": { "info": "", "progress": 0, "status": "UpToDate", "updateVersion": "3.4" }, "icon": { "path": "/assets/icon/fibaro/onoff/onoff0.png", "source": "HC" }, "includeInEnergyPanel": true, "isLight": true, "localProtectionState": 0, "localProtectionSupport": 5, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "nodeId": 38, "parametersTemplate": 779, "power": 0.0, "productInfo": "1,15,4,3,16,0,3,4", "protectionExclusiveControl": 0, "protectionExclusiveControlSupport": false, "protectionState": 0, "protectionTimeout": 0, "protectionTimeoutSupport": false, "saveLogs": true, "saveToEnergyPanel": false, "serialNumber": "h'00000000000022a1", "showEnergy": true, "state": false, "storeEnergyData": true, "supportedDeviceRoles": [ "Light", "Drencher", "Pin", "NightLamp", "Kettle", "Bracket", "AirConditioner", "AlarmAlarm", "Coffee", "GardenLamp", "TvSet", "CeilingFan", "Toaster", "Radio", "RoofWindow", "Other", "AlarmState", "AlarmArm", "VideoGateBell", "VideoGateOpen", "Valve" ], "updateVersion": "", "useTemplate": true, "userDescription": "", "value": false }, "actions": { "abortUpdate": 1, "reconfigure": 0, "reset": 0, "retryUpdate": 1, "startUpdate": 1, "toggle": 0, "turnOff": 0, "turnOn": 0, "updateFirmware": 1 }, "created": 939668400, "modified": 1694667155, "sortOrder": 72 }, { "id": 387, "name": "175.0.1", "roomID": 364, "view": [], "type": "com.fibaro.remoteController", "baseType": "com.fibaro.actor", "enabled": true, "visible": false, "isPlugin": false, "parentId": 175, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "zwave", "zwaveCentralScene", "zwaveProtection" ], "properties": { "pollingTimeSec": 0, "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveVersion": "3.4", "RFProtectionState": 0, "RFProtectionSupport": 3, "categories": [ "remotes" ], "centralSceneSupport": [ { "keyAttributes": [ "Pressed", "Released", "HeldDown", "Pressed2", "Pressed3" ], "keyId": 1 }, { "keyAttributes": [ "Pressed", "Released", "HeldDown", "Pressed2", "Pressed3" ], "keyId": 2 } ], "configured": true, "dead": false, "deadReason": "", "deviceControlType": 1, "deviceIcon": 103, "deviceRole": "Other", "endPointId": 0, "icon": {}, "localProtectionState": 0, "localProtectionSupport": 5, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "nodeId": 38, "parametersTemplate": 779, "productInfo": "1,15,4,3,16,0,3,4", "protectionExclusiveControl": 0, "protectionExclusiveControlSupport": false, "protectionState": 0, "protectionTimeout": 0, "protectionTimeoutSupport": false, "saveLogs": true, "serialNumber": "h'00000000000022a1", "supportedDeviceRoles": [ "Other" ], "useTemplate": true, "userDescription": "" }, "actions": { "reconfigure": 0 }, "created": 1672841522, "modified": 1678443241, "sortOrder": 147 } ]pyfibaro-0.8.2/tests/fixture/device-scene-support.json000066400000000000000000000374041476015233600231420ustar00rootroot00000000000000[ { "id": 175, "name": "Licht Roman Master", "roomID": 364, "view": [], "type": "com.fibaro.zwaveDevice", "baseType": "com.fibaro.device", "enabled": true, "visible": false, "isPlugin": false, "parentId": 1, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "polling", "zwave", "zwaveAssociation", "zwaveConfiguration", "zwaveMultiChannelAssociation", "zwaveSlaveRouting" ], "properties": { "categories": [ "other" ], "configured": true, "dead": false, "deadReason": "", "deviceControlType": 1, "deviceIcon": 28, "deviceRole": "Other", "deviceSpecificData": "h'00000000000022a1", "deviceSpecificIdType": "Serial Number", "deviceState": "Configured", "emailNotificationID": 0, "emailNotificationType": 0, "endPointId": 0, "icon": {}, "lastWorkingRoute": [ 1 ], "lastWorkingRouteRequestStatus": "ok", "lastWorkingRouteRequestTimestamp": 0, "lastWorkingRouteResponseTimestamp": 1694667148, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "neighborList": [ 1, 36, 80, 84, 91, 95, 116, 120, 126, 130, 147, 152, 154, 159, 165, 180, 185, 219, 222, 271, 308, 331, 361, 377, 381, 389, 394 ], "neighborListRequestStatus": "ok", "neighborListRequestTimestamp": 1596812962, "neighborListResponseTimestamp": 1694667148, "nodeId": 38, "parameters": [ { "id": 9, "lastReportedValue": 1, "lastSetValue": 1, "size": 1, "value": 1 }, { "id": 10, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 11, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 12, "lastReportedValue": 50, "lastSetValue": 50, "size": 2, "value": 50 }, { "id": 13, "lastReportedValue": 5, "lastSetValue": 5, "size": 2, "value": 5 }, { "id": 20, "lastReportedValue": 2, "lastSetValue": 2, "size": 1, "value": 2 }, { "id": 21, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 27, "lastReportedValue": 15, "lastSetValue": 15, "size": 1, "value": 15 }, { "id": 28, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 29, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 30, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 31, "lastReportedValue": 255, "lastSetValue": 255, "size": 2, "value": 255 }, { "id": 32, "lastReportedValue": 0, "lastSetValue": 0, "size": 2, "value": 0 }, { "id": 33, "lastReportedValue": 99, "lastSetValue": 99, "size": 2, "value": 99 }, { "id": 35, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 36, "lastReportedValue": 255, "lastSetValue": 255, "size": 2, "value": 255 }, { "id": 37, "lastReportedValue": 0, "lastSetValue": 0, "size": 2, "value": 0 }, { "id": 38, "lastReportedValue": 99, "lastSetValue": 99, "size": 2, "value": 99 }, { "id": 40, "lastReportedValue": 3, "lastSetValue": 3, "size": 1, "value": 3 }, { "id": 41, "lastReportedValue": 2, "lastSetValue": 2, "size": 1, "value": 2 }, { "id": 42, "lastReportedValue": 3, "lastSetValue": 3, "size": 1, "value": 3 }, { "id": 43, "lastReportedValue": 1, "lastSetValue": 1, "size": 1, "value": 1 }, { "id": 44, "lastReportedValue": 600, "lastSetValue": 600, "size": 2, "value": 600 }, { "id": 50, "lastReportedValue": 20, "lastSetValue": 20, "size": 1, "value": 20 }, { "id": 51, "lastReportedValue": 10, "lastSetValue": 10, "size": 1, "value": 10 }, { "id": 53, "lastReportedValue": 100, "lastSetValue": 100, "size": 2, "value": 100 }, { "id": 58, "lastReportedValue": 3600, "lastSetValue": 3600, "size": 2, "value": 3600 }, { "id": 59, "lastReportedValue": 3600, "lastSetValue": 3600, "size": 2, "value": 3600 }, { "id": 60, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 } ], "parametersTemplate": 779, "pollingInterval": -1, "pollingTimeSec": 0, "productInfo": "1,15,4,3,16,0,3,4", "pushNotificationID": 0, "pushNotificationType": 0, "remoteGatewayId": 0, "saveLogs": true, "securityLevel": "", "securitySchemes": [], "serialNumber": "h'00000000000022a1", "smsNotificationID": 0, "smsNotificationType": 0, "supportedDeviceRoles": [ "Other" ], "useTemplate": true, "userDescription": "", "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveSoftwareVersion": {}, "zwaveVersion": "3.4" }, "actions": { "getParameter": 1, "poll": 0, "setParameter": 2 }, "created": 1672822156, "modified": 1678443238, "sortOrder": 69 }, { "id": 176, "name": "Licht Roman inaktiv", "roomID": 364, "view": [ { "assetsPath": "dynamic-plugins/com.fibaro.binarySwitch", "name": "com.fibaro.binarySwitch", "translatesPath": "/assets/i18n/com.fibaro.binarySwitch", "type": "ts" }, { "assetsPath": "dynamic-plugins/energy", "name": "energy", "translatesPath": "/assets/i18n/energy", "type": "ts" }, { "assetsPath": "dynamic-plugins/power", "name": "power", "translatesPath": "/assets/i18n/power", "type": "ts" } ], "type": "com.fibaro.binarySwitch", "baseType": "com.fibaro.actor", "enabled": true, "visible": false, "isPlugin": false, "parentId": 175, "viewXml": false, "hasUIView": true, "configXml": false, "interfaces": [ "energy", "fibaroFirmwareUpdate", "light", "power", "zwave", "zwaveAlarm", "zwaveMultiChannelAssociation", "zwaveProtection" ], "properties": { "pollingTimeSec": 0, "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveVersion": "3.4", "RFProtectionState": 0, "RFProtectionSupport": 3, "alarmLevel": 0, "alarmType": 0, "categories": [ "lights" ], "configured": true, "dead": false, "deadReason": "", "deviceControlType": 2, "deviceIcon": 2, "deviceRole": "Light", "endPointId": 0, "energy": 48.32, "firmwareUpdate": { "info": "", "progress": 0, "status": "UpToDate", "updateVersion": "3.4" }, "icon": { "path": "/assets/icon/fibaro/onoff/onoff0.png", "source": "HC" }, "includeInEnergyPanel": true, "isLight": true, "localProtectionState": 0, "localProtectionSupport": 5, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "nodeId": 38, "parametersTemplate": 779, "power": 0.0, "productInfo": "1,15,4,3,16,0,3,4", "protectionExclusiveControl": 0, "protectionExclusiveControlSupport": false, "protectionState": 0, "protectionTimeout": 0, "protectionTimeoutSupport": false, "saveLogs": true, "saveToEnergyPanel": false, "serialNumber": "h'00000000000022a1", "showEnergy": true, "state": false, "storeEnergyData": true, "supportedDeviceRoles": [ "Light", "Drencher", "Pin", "NightLamp", "Kettle", "Bracket", "AirConditioner", "AlarmAlarm", "Coffee", "GardenLamp", "TvSet", "CeilingFan", "Toaster", "Radio", "RoofWindow", "Other", "AlarmState", "AlarmArm", "VideoGateBell", "VideoGateOpen", "Valve" ], "updateVersion": "", "useTemplate": true, "userDescription": "", "value": false }, "actions": { "abortUpdate": 1, "reconfigure": 0, "reset": 0, "retryUpdate": 1, "startUpdate": 1, "toggle": 0, "turnOff": 0, "turnOn": 0, "updateFirmware": 1 }, "created": 939668400, "modified": 1694667155, "sortOrder": 72 }, { "id": 387, "name": "175.0.1", "roomID": 364, "view": [], "type": "com.fibaro.remoteController", "baseType": "com.fibaro.actor", "enabled": true, "visible": false, "isPlugin": false, "parentId": 175, "viewXml": false, "hasUIView": false, "configXml": false, "interfaces": [ "zwave", "zwaveCentralScene", "zwaveProtection" ], "properties": { "pollingTimeSec": 0, "zwaveCompany": "Fibargroup", "zwaveInfo": "3,4,24", "zwaveVersion": "3.4", "RFProtectionState": 0, "RFProtectionSupport": 3, "categories": [ "remotes" ], "centralSceneSupport": "[{\"keyAttributes\":[\"Pressed\",\"Released\",\"HeldDown\"],\"keyId\":1},{\"keyAttributes\":[\"Pressed\",\"Released\",\"HeldDown\"],\"keyId\":2},{\"keyAttributes\":[\"Pressed\",\"Released\",\"HeldDown\"],\"keyId\":3},{\"keyAttributes\":[\"Pressed\",\"Released\",\"HeldDown\"],\"keyId\":4}]", "configured": true, "dead": false, "deadReason": "", "deviceControlType": 1, "deviceIcon": 103, "deviceRole": "Other", "endPointId": 0, "icon": {}, "localProtectionState": 0, "localProtectionSupport": 5, "log": "", "logTemp": "", "manufacturer": "", "markAsDead": true, "model": "", "nodeId": 38, "parametersTemplate": 779, "productInfo": "1,15,4,3,16,0,3,4", "protectionExclusiveControl": 0, "protectionExclusiveControlSupport": false, "protectionState": 0, "protectionTimeout": 0, "protectionTimeoutSupport": false, "saveLogs": true, "serialNumber": "h'00000000000022a1", "supportedDeviceRoles": [ "Other" ], "useTemplate": true, "userDescription": "" }, "actions": { "reconfigure": 0 }, "created": 1672841522, "modified": 1678443241, "sortOrder": 147 } ]pyfibaro-0.8.2/tests/fixture/device.json000066400000000000000000000267531476015233600203420ustar00rootroot00000000000000[ { "id": 1, "name": "zwave", "roomID": 0, "type": "com.fibaro.zwavePrimaryController", "baseType": "", "enabled": true, "visible": false, "isPlugin": false, "parentId": 0, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "zwave" ], "properties": { "UIMessageSendTime": "0", "autoConfig": "0", "configured": true, "date": "a", "dead": "false", "deviceControlType": "0", "deviceIcon": "28", "disabled": "1", "emailNotificationID": "0", "emailNotificationType": "0", "endPoint": "0", "endPointId": "0", "homeIdHash": "0fe4e173f811b08ad27592cc36ba851a989b9266", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "model": "", "nodeID": "1", "nodeId": "0", "parameters": [], "parametersTemplate": "0", "pollingDeadDevice": "false", "pollingTime": "1354829138", "pollingTimeNext": "1354837724", "pollingTimeSec": 125, "productInfo": "", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "requestNodeNeighborStat": "0", "requestNodeNeighborStatTimeStemp": "", "requestNodeNeighborState": "", "requestNodeNeighborStateTimeStemp": "", "saveLogs": "true", "serialNumber": "", "showChildren": "1", "smsNotificationID": "0", "smsNotificationType": "0", "status": "STAT_IDLE", "sunriseHour": "07:45", "sunsetHour": "16:38", "useTemplate": "true", "userDescription": "", "value": "0", "zwaveBuildVersion": "3.67b", "zwaveCompany": "Unknown", "zwaveInfo": "", "zwaveRegion": "EU", "zwaveVersion": "3.67" }, "actions": { "pollingDeadDevice": 1, "pollingTimeSec": 1, "reconfigure": 0, "requestNodeNeighborUpdate": 1, "turnOff": 0, "turnOn": 0 }, "created": 1652642209, "modified": 1652642209, "sortOrder": 1 }, { "id": 2, "name": "user@test.com", "roomID": 0, "type": "HC_user", "baseType": "com.fibaro.voipUser", "enabled": true, "visible": true, "isPlugin": false, "parentId": 0, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "voip" ], "properties": { "Email": "user@test.com", "HotelModeRoom": "0", "LastPwdChange": "1667416706", "Latitude": "52.43", "Location": "52.4320294933;16.8449900900", "LocationTime": "2012-12-06 12:15", "LocationTimestamp": "1354792521", "Longitude": "16.84", "PreviousLatitude": "52.43", "PreviousLocation": "52.4320252015;16.8449947542", "PreviousLocationTime": "2012-12-06 12:14", "PreviousLocationTimestamp": "1354792461", "PreviousLongitude": "16.84", "SendNotifications": "true", "TrackUser": "0", "UserType": "superuser", "atHome": "false", "deviceIcon": "91", "fidUuid": "3c9500dd-00a4-427e-aad8-de8f5f72573f", "firmwareUpdateLevel": "0", "initialWizard": "false", "saveLogs": "1", "sipDisplayName": "_", "sipUserID": "1", "sipUserPassword": "", "useOptionalArmPin": "false", "usePin": "false" }, "actions": { "sendEmail": 2, "sendPush": 1, "setSipDisplayName": 1, "setSipUserID": 1, "setSipUserPassword": 1 }, "created": 1652642209, "modified": 1652642209, "sortOrder": 2 }, { "id": 12, "name": "Hifi Master", "roomID": 4, "type": "com.fibaro.zwaveDevice", "baseType": "com.fibaro.device", "enabled": true, "visible": false, "isPlugin": false, "parentId": 1, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "zwave", "zwaveAssociation", "zwaveConfiguration", "zwaveSlaveRouting" ], "properties": { "categories": "[\"other\"]", "configured": true, "dead": "false", "deadReason": "", "deviceControlType": "0", "deviceIcon": "28", "deviceSpecificData": "h'0c000300010106000200000002080000000000", "deviceSpecificIdType": "Serial Number", "deviceState": "Configured", "emailNotificationID": "0", "emailNotificationType": "0", "endPointId": "0", "lastWorkingRoute": "[1]", "lastWorkingRouteRequestStatus": "ok", "lastWorkingRouteRequestTimestamp": "0", "lastWorkingRouteResponseTimestamp": "1652642298", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "model": "", "neighborList": "[1,16,60,71,75,96,100,106,110,127,132,134,139,145,155,165,199,202,251,288,308,311,329,341,357]", "neighborListRequestStatus": "ok", "neighborListRequestTimestamp": "0", "neighborListResponseTimestamp": "1652642298", "nodeId": "5", "parameters": [ { "id": 3, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 80, "lastReportedValue": 0, "lastSetValue": 0, "size": 1, "value": 0 }, { "id": 90, "lastReportedValue": 1, "lastSetValue": 1, "size": 1, "value": 1 }, { "id": 91, "lastReportedValue": 10, "lastSetValue": 10, "size": 2, "value": 10 }, { "id": 92, "lastReportedValue": 5, "lastSetValue": 5, "size": 1, "value": 5 }, { "id": 101, "lastReportedValue": 4, "lastSetValue": 4, "size": 4, "value": 4 }, { "id": 102, "lastReportedValue": 8, "lastSetValue": 8, "size": 4, "value": 8 }, { "id": 103, "lastReportedValue": 3, "lastSetValue": 3, "size": 4, "value": 3 }, { "id": 111, "lastReportedValue": 300, "lastSetValue": 300, "size": 4, "value": 300 }, { "id": 112, "lastReportedValue": 1800, "lastSetValue": 1800, "size": 4, "value": 1800 }, { "id": 113, "lastReportedValue": 720, "lastSetValue": 720, "size": 4, "value": 720 } ], "parametersTemplate": "731", "pollingTimeSec": 0, "productInfo": "0,134,0,3,0,75,3,26", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "saveLogs": "true", "serialNumber": "h'0c000300010106000200000002080000000000", "smsNotificationID": "0", "smsNotificationType": "0", "useTemplate": "true", "userDescription": "", "zwaveCompany": "AEON Labs", "zwaveInfo": "3,3,83", "zwaveSoftwareVersion": "{}", "zwaveVersion": "3.26" }, "actions": { "reconfigure": 0 }, "created": 1652642209, "modified": 1652642209, "sortOrder": 5 }, { "id": 13, "name": "Hifi", "roomID": 4, "type": "com.fibaro.binarySwitch", "baseType": "com.fibaro.actor", "enabled": true, "visible": true, "isPlugin": false, "parentId": 12, "remoteGatewayId": 0, "viewXml": false, "configXml": false, "interfaces": [ "deviceGrouping", "energy", "fibaroFirmwareUpdate", "power", "zwave", "zwaveSceneActivation", "zwaveSwitchAll" ], "properties": { "pollingTimeSec": 0, "zwaveCompany": "AEON Labs", "zwaveInfo": "3,3,83", "zwaveVersion": "3.26", "categories": "[\"multimedia\"]", "configured": true, "dead": "false", "deadReason": "", "deviceControlType": "12", "deviceGroup": "[]", "deviceGroupMaster": "0", "deviceIcon": "2", "emailNotificationID": "0", "emailNotificationType": "0", "endPointId": "0", "energy": "1716.64", "firmwareUpdate": "{\"info\":\"\",\"progress\":0,\"status\":\"UpToDate\",\"updateVersion\":\"3.26\"}", "log": "", "logTemp": "", "manufacturer": "", "markAsDead": "true", "model": "", "nodeId": "5", "parametersTemplate": "731", "power": "14.03", "productInfo": "0,134,0,3,0,75,3,26", "pushNotificationID": "0", "pushNotificationType": "0", "remoteGatewayId": "0", "saveLogs": "true", "sceneActivation": "0", "serialNumber": "h'0c000300010106000200000002080000000000", "showEnergy": "true", "smsNotificationID": "0", "smsNotificationType": "0", "switchAllMode": "", "updateVersion": "", "useTemplate": "true", "userDescription": "", "value": "true" }, "actions": { "abortUpdate": 1, "reconfigure": 0, "reset": 0, "retryUpdate": 1, "sceneActivationSet": 0, "startUpdate": 1, "turnOff": 0, "turnOn": 0, "updateFirmware": 1 }, "created": 1652642209, "modified": 1652642209, "sortOrder": 112 }, { "name": "invalid device" } ]pyfibaro-0.8.2/tests/fixture/info.json000066400000000000000000000016601476015233600200240ustar00rootroot00000000000000{ "serialNumber": "HC2-011111", "hcName": "My Home", "mac": "00:22:4d:b7:13:24", "zwaveVersion": "3.67", "timeFormat": 24, "zwaveRegion": "EU", "serverStatus": 1652642206, "defaultLanguage": "en", "sunsetHour": "16:39", "sunriseHour": "07:44", "hotelMode": false, "temperatureUnit": "C", "batteryLowNotification": true, "smsManagement": true, "forwardNotificationsToPushMessages": true, "date": "21:22 | 26.11.2022", "softVersion": "4.630", "beta": false, "currentVersion": { "version": "4.630", "type": "stable" }, "installVersion": { "version": "", "type": "", "status": "", "progress": 0 }, "timestamp": 1669494127, "online": true, "tosAccepted": true, "updateStableAvailable": false, "updateBetaAvailable": false, "newestStableVersion": "4.630", "newestBetaVersion": "4.621" }pyfibaro-0.8.2/tests/fixture/login_fail.json000066400000000000000000000001201476015233600211620ustar00rootroot00000000000000{ "status": false, "retryNumber": 0, "limit": 5, "timeLeft": 0 }pyfibaro-0.8.2/tests/fixture/login_success.json000066400000000000000000000002441476015233600217260ustar00rootroot00000000000000{ "status": true, "userID": 2, "username": "username@test.com", "type": "superuser", "analyticsUserIdHash": "b1baa0ce9224216158db5f2d8c708a60" }pyfibaro-0.8.2/tests/fixture/refresh.json000066400000000000000000000014421476015233600205250ustar00rootroot00000000000000{ "status": "IDLE", "last": 2639046, "date": "11:26 | 27.11.2022", "timestamp": 1669544810, "logs": [], "events": [ { "type": "DevicePropertyUpdatedEvent", "data": { "id": 28, "property": "value", "oldValue": 232.08, "newValue": 232.87 } }, { "type": "PowerMetricsChangedEvent", "created": 1698950464, "data": { "consumptionPower": 345.408, "productionPower": 0.0 } } ], "changes": [ { "id": 28, "value": "232.88" }, { "id": 13, "value": "true" } ], "alarmChanges": [] }pyfibaro-0.8.2/tests/fixture/refresh_extended.json000066400000000000000000000011001476015233600223740ustar00rootroot00000000000000{ "status": "IDLE", "last": 2639046, "date": "11:26 | 27.11.2022", "timestamp": 1669544810, "logs": [], "events": [ { "type": "DevicePropertyUpdatedEvent", "data": { "id": 28, "property": "value", "oldValue": 232.08, "newValue": 232.87 } } ], "changes": [ { "id": 28, "value": "232.88", "log": "Transfer FAILED", "logTemp": "test" } ], "alarmChanges": [] }pyfibaro-0.8.2/tests/fixture/room.json000066400000000000000000000011701476015233600200410ustar00rootroot00000000000000[ { "id": 4, "name": "Wohnen", "sectionID": 4, "icon": "room_sofa", "defaultSensors": { "temperature": 0, "humidity": 0, "light": 0 }, "defaultThermostat": 0, "sortOrder": 1, "category": "other" }, { "id": 5, "name": "KÃŧche", "sectionID": 4, "icon": "room_dining", "defaultSensors": { "temperature": 141, "humidity": 0, "light": 142 }, "defaultThermostat": 0, "sortOrder": 3, "category": "other" } ]pyfibaro-0.8.2/tests/fixture/scene-hc3.json000066400000000000000000000164261476015233600206470ustar00rootroot00000000000000[ { "id": 1, "name": "Morning Scenario", "type": "scenario", "roomId": 219, "mode": "automatic", "maxRunningInstances": 2, "icon": "morning", "hidden": false, "protectedByPin": false, "stopOnAlarm": true, "restart": true, "enabled": false, "content": "{\"actions\":\"api.post(\\\"/devices/groupAction/open\\\", json.decode(\\\"{\\\\\\\"filters\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"isTypeOf\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"com.fibaro.remoteBaseShutter\\\\\\\",\\\\\\\"com.fibaro.baseShutter\\\\\\\"]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"propertyEquals\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"propertyName\\\\\\\":\\\\\\\"deviceRole\\\\\\\",\\\\\\\"propertyValue\\\\\\\":[\\\\\\\"BlindsWithoutPositioning\\\\\\\",\\\\\\\"BlindsWithPositioning\\\\\\\",\\\\\\\"BlindsWithDriver\\\\\\\",\\\\\\\"BlindsWithDriverImpulse\\\\\\\",\\\\\\\"VenetianBlinds\\\\\\\"]}]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"not\\\\\\\",\\\\\\\"value\\\\\\\":{\\\\\\\"filter\\\\\\\":\\\\\\\"interface\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"shutterWithSilentMove\\\\\\\"]}},{\\\\\\\"filter\\\\\\\":\\\\\\\"visible\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"true\\\\\\\"]}]}\\\")) api.post(\\\"/devices/groupAction/silentOpen\\\", json.decode(\\\"{\\\\\\\"filters\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"isTypeOf\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"com.fibaro.remoteBaseShutter\\\\\\\",\\\\\\\"com.fibaro.baseShutter\\\\\\\"]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"propertyEquals\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"propertyName\\\\\\\":\\\\\\\"deviceRole\\\\\\\",\\\\\\\"propertyValue\\\\\\\":[\\\\\\\"BlindsWithoutPositioning\\\\\\\",\\\\\\\"BlindsWithPositioning\\\\\\\",\\\\\\\"BlindsWithDriver\\\\\\\",\\\\\\\"BlindsWithDriverImpulse\\\\\\\",\\\\\\\"VenetianBlinds\\\\\\\"]}]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"interface\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"shutterWithSilentMove\\\\\\\"]}]}\\\")) \",\"conditions\":\"{\\n conditions = { {\\n isTrigger = false,\\n operator = \\\"match\\\",\\n property = \\\"cron\\\",\\n type = \\\"date\\\",\\n value = { \\\"*\\\", \\\"*\\\", \\\"*\\\", \\\"*\\\", \\\"1,2,3,4,5,6,7\\\", \\\"*\\\" }\\n }, {\\n conditions = { {\\n conditions = { {\\n isTrigger = true,\\n operator = \\\"==\\\",\\n property = \\\"sunrise\\\",\\n type = \\\"date\\\",\\n value = 0\\n } },\\n operator = \\\"all\\\"\\n } },\\n operator = \\\"any\\\"\\n } },\\n operator = \\\"all\\\"\\n}\"}", "created": 1648646259, "updated": 1676317846, "isRunning": false, "started": 0, "scenarioData": { "type": "morningBlinds", "version": 2, "what": { "action": { "args": [], "name": "open" } }, "when": { "daysOfWeek": [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" ] }, "where": { "wholeHouse": true } }, "categories": [ 3 ], "sortOrder": 1 }, { "id": 2, "name": "Evening Scenario", "type": "scenario", "roomId": 219, "mode": "automatic", "maxRunningInstances": 2, "icon": "evening", "hidden": false, "protectedByPin": false, "stopOnAlarm": true, "restart": true, "enabled": false, "content": "{\"conditions\":\"{\\n conditions = { {\\n isTrigger = false,\\n operator = \\\"match\\\",\\n property = \\\"cron\\\",\\n type = \\\"date\\\",\\n value = { \\\"*\\\", \\\"*\\\", \\\"*\\\", \\\"*\\\", \\\"1,2,3,4,5,6,7\\\", \\\"*\\\" }\\n }, {\\n conditions = { {\\n conditions = { {\\n isTrigger = true,\\n operator = \\\"==\\\",\\n property = \\\"sunset\\\",\\n type = \\\"date\\\",\\n value = 0\\n } },\\n operator = \\\"all\\\"\\n } },\\n operator = \\\"any\\\"\\n } },\\n operator = \\\"all\\\"\\n}\",\"actions\":\"api.post(\\\"/devices/groupAction/close\\\", json.decode(\\\"{\\\\\\\"filters\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"isTypeOf\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"com.fibaro.remoteBaseShutter\\\\\\\",\\\\\\\"com.fibaro.baseShutter\\\\\\\"]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"propertyEquals\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"propertyName\\\\\\\":\\\\\\\"deviceRole\\\\\\\",\\\\\\\"propertyValue\\\\\\\":[\\\\\\\"BlindsWithoutPositioning\\\\\\\",\\\\\\\"BlindsWithPositioning\\\\\\\",\\\\\\\"BlindsWithDriver\\\\\\\",\\\\\\\"BlindsWithDriverImpulse\\\\\\\",\\\\\\\"VenetianBlinds\\\\\\\"]}]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"not\\\\\\\",\\\\\\\"value\\\\\\\":{\\\\\\\"filter\\\\\\\":\\\\\\\"interface\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"shutterWithSilentMove\\\\\\\"]}},{\\\\\\\"filter\\\\\\\":\\\\\\\"visible\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"true\\\\\\\"]}]}\\\")) api.post(\\\"/devices/groupAction/silentClose\\\", json.decode(\\\"{\\\\\\\"filters\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"isTypeOf\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"com.fibaro.remoteBaseShutter\\\\\\\",\\\\\\\"com.fibaro.baseShutter\\\\\\\"]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"or\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"filter\\\\\\\":\\\\\\\"propertyEquals\\\\\\\",\\\\\\\"value\\\\\\\":[{\\\\\\\"propertyName\\\\\\\":\\\\\\\"deviceRole\\\\\\\",\\\\\\\"propertyValue\\\\\\\":[\\\\\\\"BlindsWithoutPositioning\\\\\\\",\\\\\\\"BlindsWithPositioning\\\\\\\",\\\\\\\"BlindsWithDriver\\\\\\\",\\\\\\\"BlindsWithDriverImpulse\\\\\\\",\\\\\\\"VenetianBlinds\\\\\\\"]}]}]},{\\\\\\\"filter\\\\\\\":\\\\\\\"interface\\\\\\\",\\\\\\\"value\\\\\\\":[\\\\\\\"shutterWithSilentMove\\\\\\\"]}]}\\\")) \"}", "created": 1648646338, "updated": 1672822254, "isRunning": false, "started": 0, "scenarioData": { "type": "eveningBlinds", "version": 2, "what": { "action": { "args": [], "name": "close" } }, "when": { "daysOfWeek": [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" ] }, "where": { "wholeHouse": true } }, "categories": [ 3 ], "sortOrder": 2 } ]pyfibaro-0.8.2/tests/fixture/scene.json000066400000000000000000000040331476015233600201630ustar00rootroot00000000000000[ { "id": 2, "name": "Go to bed", "type": "com.fibaro.blockScene", "categories": [ "other" ], "roomID": 5, "iconID": 5, "runConfig": "TRIGGER_AND_MANUAL", "alexaProhibited": true, "autostart": false, "protectedByPIN": false, "killable": true, "killOtherInstances": false, "maxRunningInstances": 1, "runningInstances": 0, "instances": [], "runningManualInstances": 0, "visible": true, "isLua": false, "properties": "", "triggers": { "properties": [ { "id": 17, "name": "value" } ], "globals": [ "GlobalMode" ], "events": [], "weather": [] }, "actions": { "devices": [], "scenes": [], "groups": [] }, "sortOrder": 15 }, { "id": 10, "name": "Automatic light", "type": "com.fibaro.luaScene", "categories": [ "other" ], "roomID": 108, "iconID": 6, "runConfig": "TRIGGER_AND_MANUAL", "alexaProhibited": true, "autostart": false, "protectedByPIN": false, "killable": true, "killOtherInstances": false, "maxRunningInstances": 1, "runningInstances": 0, "instances": [], "runningManualInstances": 0, "visible": true, "isLua": true, "properties": "", "triggers": { "properties": [ { "id": 72, "name": "value" } ], "globals": [], "events": [], "weather": [] }, "actions": { "devices": [], "scenes": [], "groups": [] }, "sortOrder": 57 }, { "name": "invalid scene" } ]pyfibaro-0.8.2/tests/test_fibaro_client.py000066400000000000000000000051441476015233600207220ustar00rootroot00000000000000"""Test fibaro connection.""" import pytest import requests_mock from pyfibaro.fibaro_client import FibaroClient from pyfibaro.fibaro_client import ( FibaroAuthenticationFailed, FibaroConnectFailed ) from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_connect_failed_with_exception() -> None: """Test any exception.""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) with pytest.raises(FibaroConnectFailed): client.connect_with_credentials(TEST_USERNAME, TEST_PASSWORD) def test_connect_failed_with_http_error() -> None: """Test http error.""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}loginStatus", status_code=500) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) with pytest.raises(FibaroConnectFailed): client.connect_with_credentials(TEST_USERNAME, TEST_PASSWORD) def test_invalid_authentication() -> None: """Test invalid password.""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}loginStatus", status_code=403) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) with pytest.raises(FibaroAuthenticationFailed): client.connect_with_credentials(TEST_USERNAME, TEST_PASSWORD) def test_connect_succeed() -> None: """Test connect succeeds.""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) info = client.connect_with_credentials(TEST_USERNAME, TEST_PASSWORD) assert mock.call_count == 2 assert info.raw_data == info_payload def test_frontend_url() -> None: """Test frontend url getter.""" client = FibaroClient(TEST_BASE_URL) assert client.frontend_url() == TEST_BASE_URL.removesuffix("/api/") pyfibaro-0.8.2/tests/test_fibaro_data_helper.py000066400000000000000000000027151476015233600217150ustar00rootroot00000000000000"""Test data helpers.""" from unittest.mock import Mock from pyfibaro.fibaro_data_helper import ( read_devices, read_rooms, find_master_devices, ) from pyfibaro.fibaro_device import DeviceModel from pyfibaro.fibaro_room import RoomModel from .test_utils import load_fixture scene_payload = load_fixture("scene-hc3.json") room_payload = load_fixture("room.json") device_payload = load_fixture("device-hc3.json") device2_payload = load_fixture("device.json") def test_read_rooms() -> None: """Test read rooms""" client = Mock() client.read_rooms.return_value = [RoomModel(room_payload[0])] rooms = read_rooms(client) assert len(rooms) == 1 assert rooms[4] == "Wohnen" def test_read_devices_master_devices() -> None: """Test read devices""" devices = [ DeviceModel(device_payload[0], Mock(), 4), DeviceModel(device2_payload[2], Mock(), 4), DeviceModel(device2_payload[3], Mock(), 4), DeviceModel(device_payload[4], Mock(), 4), ] client = Mock() client.read_devices.return_value = devices devices = find_master_devices(devices) assert len(devices) == 1 def test_read_devices() -> None: """Test read devices""" devices = [ DeviceModel(device2_payload[2], Mock(), 4), DeviceModel(device2_payload[3], Mock(), 4), ] client = Mock() client.read_devices.return_value = devices devices = read_devices(client, True) assert len(devices) == 2 pyfibaro-0.8.2/tests/test_fibaro_device.py000066400000000000000000000256171476015233600207120ustar00rootroot00000000000000"""Test FibaroInfo class.""" from __future__ import annotations from typing import Any import pytest import requests_mock from pyfibaro.fibaro_client import FibaroClient from pyfibaro.fibaro_device import ColorModel, DeviceModel, ValueModel from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture device_payload = load_fixture("device.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_device() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert devices is not None assert len(devices) == 3 assert devices[0].fibaro_id == 1 assert devices[0].name == "zwave" assert devices[0].parent_fibaro_id == 0 assert devices[0].type == "com.fibaro.zwavePrimaryController" assert devices[0].base_type == "" assert devices[0].room_id == 0 assert devices[0].has_unit is False assert devices[0].unit is None assert devices[0].has_armed is False assert devices[0].armed is False assert devices[0].has_interface("invalid_interfce") is False assert devices[2].has_interface("energy") is True assert devices[0].has_battery_level is False assert devices[0].battery_level == 0 assert devices[0].has_endpoint_id is True assert devices[0].endpoint_id == 0 assert devices[0].enabled is True assert devices[0].visible is False assert devices[0].is_plugin is False assert devices[0].value.int_value() == 0 assert devices[0].value_2.has_value is False assert devices[0].color.has_color is False assert devices[0].last_color_set.has_color is False assert devices[0].state.has_value is False assert devices[0].has_brightness is False assert devices[0].brightness == 0 assert devices[0].current_program == 0 assert devices[0].current_program_id == 0 assert devices[0].has_mode is False assert devices[0].mode == 0 assert devices[0].has_supported_modes is False assert devices[0].supported_modes == [] assert devices[0].has_operating_mode is False assert devices[0].operating_mode == 0 assert devices[0].has_supported_operating_modes is False assert devices[0].supported_operating_modes == [] assert devices[0].has_thermostat_mode is False assert devices[0].thermostat_mode is None assert devices[0].has_thermostat_operating_state is False assert devices[0].thermostat_operating_state is None assert devices[0].has_supported_thermostat_modes is False assert devices[0].supported_thermostat_modes == [] assert devices[0].has_heating_thermostat_setpoint is False assert devices[0].heating_thermostat_setpoint == 0 assert devices[0].has_heating_thermostat_setpoint_future is False assert devices[0].heating_thermostat_setpoint_future == 0 assert devices[0].target_level == 0 assert devices[0].has_dead is True assert devices[0].dead is False assert devices[0].has_dead_reason is False assert devices[0].dead_reason is None assert isinstance(devices[0].actions, dict) assert isinstance(devices[0].properties, dict) assert mock.call_count == 3 @pytest.mark.parametrize("test_value", ["true", "True", True, 1, 0.1, "1", "0.1"]) def test_fibaro_value_true(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.bool_value() is True assert value.bool_value(False) is True @pytest.mark.parametrize("test_value", ["false", "False", False, 0, 0.0, "0", "0.0"]) def test_fibaro_value_false(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.bool_value() is False assert value.bool_value(True) is False @pytest.mark.parametrize("test_value", ["1", "1.1", 1, 1.1]) def test_fibaro_value_int(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.int_value() == 1 @pytest.mark.parametrize("test_value", ["1", 1]) def test_fibaro_value_float(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.float_value() == 1 @pytest.mark.parametrize("test_value", ["1", 1]) def test_fibaro_value_str(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.str_value() == "1" @pytest.mark.parametrize("test_value", [{"x": 1}, '{"x":1}']) def test_fibaro_value_dict(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.dict_value() == {"x": 1} @pytest.mark.parametrize("test_value", ["1.1", 1.1]) def test_fibaro_value_float_2(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.has_value is True assert value.float_value() == 1.1 def test_fibaro_value_default() -> None: """Test value model""" value = ValueModel({}, "value") assert value.has_value is False assert value.int_value(1) == 1 assert value.float_value(1.1) == 1.1 assert value.bool_value(True) is True assert value.str_value("test") == "test" assert value.dict_value({"x": 1}) == {"x": 1} @pytest.mark.parametrize("test_value", ["true", "True", True, "false", "False", False]) def test_fibaro_value_is_bool(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.is_bool_value is True @pytest.mark.parametrize("test_value", [0, 1, "any_value"]) def test_fibaro_value_is_bool_negative(test_value: Any) -> None: """Test value model""" value = ValueModel({"value": test_value}, "value") assert value.is_bool_value is False def test_fibaro_no_value() -> None: """Test value model""" value = ValueModel({}, "value") assert value.has_value is False with pytest.raises(TypeError): value.int_value() with pytest.raises(TypeError): value.float_value() with pytest.raises(TypeError): value.bool_value() with pytest.raises(TypeError): value.str_value() with pytest.raises(TypeError): value.dict_value() def test_fibaro_color() -> None: """Test value model""" value = ColorModel({"color": "0,0,0,0"}, "color") assert value.has_color is True assert value.rgbw_color == (0, 0, 0, 0) value = ColorModel({"color": "234,244,255,0"}, "color") assert value.has_color is True assert value.rgbw_color == (234, 244, 255, 0) value = ColorModel({"color": "234,255,0"}, "color") assert value.has_color is False with pytest.raises(TypeError): value.rgbw_color value = ColorModel({"color": "234,255,0"}, "color_xxxx") assert value.has_color is False with pytest.raises(TypeError): value.rgbw_color @pytest.mark.parametrize("test_value", ["1,2,3,4", ["1", "2", "3", "4"], [1, 2, 3, 4]]) def test_fibaro_supported_modes(test_value: Any) -> None: """Test modes""" device = DeviceModel({"properties": {"supportedModes": test_value}}, None, 4) assert device.supported_modes == [1, 2, 3, 4] @pytest.mark.parametrize("test_value", ["1,2,3,4", ["1", "2", "3", "4"], [1, 2, 3, 4]]) def test_fibaro_supported_operating_modes(test_value: Any) -> None: """Test operating modes""" device = DeviceModel( {"properties": {"supportedOperatingModes": test_value}}, None, 4 ) assert device.supported_operating_modes == [1, 2, 3, 4] def test_fibaro_supported_thermostat_modes() -> None: """Test thermostat modes""" device = DeviceModel( {"properties": {"supportedThermostatModes": ["heat", "auto"]}}, None, 4 ) assert device.supported_thermostat_modes == ["heat", "auto"] def test_fibaro_device_turn_on() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/13/action/turnOn") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("turnOn") assert mock.call_count == 4 def test_fibaro_device_invalid_action() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/13/action/XXXX") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("XXXX") assert mock.call_count == 4 def test_fibaro_device_action_with_param() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/13/action/abortUpdate") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("abortUpdate", [True]) assert mock.call_count == 4 pyfibaro-0.8.2/tests/test_fibaro_device_armed.py000066400000000000000000000022541476015233600220520ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture device_payload = load_fixture("device-armed.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_device_armed() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert devices is not None assert len(devices) == 3 assert devices[1].has_armed is True # Invalid value returns False assert devices[1].armed is False assert devices[2].has_armed is True assert devices[2].armed is True pyfibaro-0.8.2/tests/test_fibaro_device_hc3.py000066400000000000000000000076301476015233600214420ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture device_payload = load_fixture("device-hc3.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_device() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert devices is not None assert len(devices) == 4 assert devices[0].fibaro_id == 1 assert devices[0].name == "zwave" assert devices[0].parent_fibaro_id == 0 assert devices[0].type == "com.fibaro.zwavePrimaryController" assert devices[0].base_type == "" assert devices[0].room_id == 219 assert isinstance(devices[0].actions, dict) assert isinstance(devices[0].properties, dict) assert mock.call_count == 3 assert devices[0].has_dead is True assert devices[0].dead is True assert devices[0].has_dead_reason is True assert devices[0].dead_reason == "Connection problem" def test_fibaro_device_turn_on() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/7/action/turnOn") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("turnOn") assert mock.call_count == 4 def test_fibaro_device_invalid_action() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/7/action/XXXX") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("XXXX") assert mock.call_count == 4 def test_fibaro_device_action_with_param() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("POST", f"{TEST_BASE_URL}devices/7/action/abortUpdate") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert mock.call_count == 3 devices[2].execute_action("abortUpdate", [True]) assert mock.call_count == 4 pyfibaro-0.8.2/tests/test_fibaro_device_manager.py000066400000000000000000000031511476015233600223710ustar00rootroot00000000000000"""Test FibaroDeviceManager.""" from unittest.mock import Mock from pyfibaro.fibaro_device import DeviceModel from pyfibaro.fibaro_device_manager import FibaroDeviceManager from .test_utils import load_fixture device_payload = load_fixture("device.json") def test_fibaro_device_manager_get_devices() -> None: """Test fibaro device manager.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] fibaro_client = Mock() fibaro_client.read_devices.return_value = devices multiplexer = FibaroDeviceManager(fibaro_client) devices = multiplexer.get_devices() assert len(devices) == 2 def test_fibaro_device_manager_add_state_listener() -> None: """Test manager add state change listener.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] listener = Mock() fibaro_client = Mock() fibaro_client.read_devices.return_value = devices manager = FibaroDeviceManager(fibaro_client) remove = manager.add_change_listener(13, listener.call_method) remove() manager.close() def test_fibaro_device_manager_add_event_listener() -> None: """Test manager add event listener.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] listener = Mock() fibaro_client = Mock() fibaro_client.read_devices.return_value = devices manager = FibaroDeviceManager(fibaro_client) remove = manager.add_event_listener(28, listener.call_method) remove() pyfibaro-0.8.2/tests/test_fibaro_device_scene_support.py000066400000000000000000000027441476015233600236570ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture device_payload = load_fixture("device-scene-support.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_device_scene_support() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert devices is not None assert len(devices) == 3 assert devices[2].has_central_scene_event is True # Invalid value returns False central_scene_event = devices[2].central_scene_event assert len(central_scene_event) == 4 assert central_scene_event[0].key_id == 1 assert central_scene_event[0].key_event_types == [ "Pressed", "Released", "HeldDown", ] assert devices[1].has_central_scene_event is False assert devices[1].central_scene_event == [] pyfibaro-0.8.2/tests/test_fibaro_device_scene_support_hc3.py000066400000000000000000000030301476015233600244010ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture device_payload = load_fixture("device-scene-support-hc3.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_device_scene_support() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}devices", json=device_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() devices = client.read_devices() assert devices is not None assert len(devices) == 3 assert devices[2].has_central_scene_event is True # Invalid value returns False central_scene_event = devices[2].central_scene_event assert len(central_scene_event) == 2 assert central_scene_event[0].key_id == 1 assert central_scene_event[0].key_event_types == [ "Pressed", "Released", "HeldDown", "Pressed2", "Pressed3", ] assert devices[1].has_central_scene_event is False assert devices[1].central_scene_event == [] pyfibaro-0.8.2/tests/test_fibaro_info.py000066400000000000000000000074201476015233600203760ustar00rootroot00000000000000"""Test FibaroInfo class.""" import pytest import requests_mock from pyfibaro.common.rest_client import RestClient from pyfibaro.fibaro_client import FibaroClient from pyfibaro.fibaro_info import InfoModel from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture info_payload = load_fixture("info.json") def test_extract_info() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = RestClient(TEST_BASE_URL, TEST_USERNAME, TEST_PASSWORD) fibaro_info = InfoModel(client) assert mock.call_count == 1 assert fibaro_info.raw_data == info_payload assert fibaro_info.current_version == "4.630" assert fibaro_info.hc_name == "My Home" assert fibaro_info.serial_number == "HC2-011111" assert fibaro_info.platform == "HC2" assert fibaro_info.mac_address == "00:22:4d:b7:13:24" @pytest.mark.parametrize( "serial_number,api_version", [ ("HC3-111111", 5), ("HC2-111111", 4), ("INVALID", 5)] ) def test_api_version(serial_number: str, api_version: int) -> None: """Test API version""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) test_payload_copy = info_payload.copy() test_payload_copy["serialNumber"] = serial_number mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=test_payload_copy ) client = RestClient(TEST_BASE_URL, TEST_USERNAME, TEST_PASSWORD) fibaro_info = InfoModel(client) assert mock.call_count == 1 assert fibaro_info.api_version == api_version @pytest.mark.parametrize( "serial_number,name", [ ("HC3-111111", "Home Center 3"), ("HC2-111111", "Home Center 2"), ("INVALID", "Hub"), ], ) def test_model_name_resolution(serial_number: str, name: int) -> None: """Test name resolution""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) test_payload_copy = info_payload.copy() test_payload_copy["serialNumber"] = serial_number mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=test_payload_copy ) client = RestClient(TEST_BASE_URL, TEST_USERNAME, TEST_PASSWORD) fibaro_info = InfoModel(client) assert mock.call_count == 1 assert fibaro_info.model_name == name @pytest.mark.parametrize( "serial_number,name", [ ("HC3-111111", "Fibaro"), ("HC2-111111", "Fibaro"), ("ZB", "ZOOZ"), ("INVALID", "Fibaro"), ], ) def test_manufacturer_name_resolution(serial_number: str, name: int) -> None: """Test name resolution""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) test_payload_copy = info_payload.copy() test_payload_copy["serialNumber"] = serial_number mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=test_payload_copy ) client = RestClient(TEST_BASE_URL, TEST_USERNAME, TEST_PASSWORD) fibaro_info = InfoModel(client) assert mock.call_count == 1 assert fibaro_info.manufacturer_name == name def test_fibaro_info() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) assert client.read_info() is not None assert mock.call_count == 1 pyfibaro-0.8.2/tests/test_fibaro_login.py000066400000000000000000000026361476015233600205570ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture def test_fibaro_connect_success() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) logged_in = client.connect() assert logged_in is True assert mock.call_count == 2 def test_fibaro_connect_fail() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) login_payload = load_fixture("login_fail.json") info_payload = load_fixture("info.json") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) logged_in = client.connect() assert logged_in is False assert mock.call_count == 2 pyfibaro-0.8.2/tests/test_fibaro_refresh.py000066400000000000000000000076501476015233600211060ustar00rootroot00000000000000"""Test FibaroStateHandler class.""" import time import pytest import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import (TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture) refresh_payload = load_fixture("refresh.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") class TestRefresh: """Use a test class to get async state.""" callback_result = None def callback_function(self, event) -> None: """The callback handler for this test.""" self.callback_result = event def callback_exception(self, event) -> None: """The callback handler for this test.""" self.callback_result = event raise TypeError() def test_fibaro_refresh(self) -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}refreshStates", json=refresh_payload ) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() client.register_update_handler(self.callback_function) time.sleep(0.1) client.unregister_update_handler() assert self.callback_result is not None assert mock.call_count > 2 def test_fibaro_refresh_fail(self) -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() client.register_update_handler(self.callback_function) time.sleep(1.1) client.unregister_update_handler() assert self.callback_result is None assert mock.call_count > 2 def test_fibaro_refresh_double_register(self) -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() client.register_update_handler(self.callback_function) with pytest.raises(Exception): client.register_update_handler(self.callback_function) client.unregister_update_handler() def test_fibaro_refresh_callback_exception(self) -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}refreshStates", json=refresh_payload ) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() client.register_update_handler(self.callback_exception) time.sleep(0.1) client.unregister_update_handler() assert self.callback_result is not None assert mock.call_count > 2 pyfibaro-0.8.2/tests/test_fibaro_room.py000066400000000000000000000014311476015233600204130ustar00rootroot00000000000000"""Test FibaroInfo class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture def test_fibaro_room() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) room_payload = load_fixture("room.json") mock.register_uri("GET", f"{TEST_BASE_URL}rooms", json=room_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) rooms = client.read_rooms() assert rooms is not None assert len(rooms) == 2 assert rooms[0].fibaro_id == 4 assert rooms[0].name == "Wohnen" assert mock.call_count == 1 pyfibaro-0.8.2/tests/test_fibaro_scene.py000066400000000000000000000104401476015233600205340ustar00rootroot00000000000000"""Test SceneModel class.""" import pytest import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture scene_payload = load_fixture("scene.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") def test_fibaro_scene() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert scenes is not None assert len(scenes) == 2 assert scenes[0].fibaro_id == 2 assert scenes[0].name == "Go to bed" assert scenes[0].room_id == 5 assert scenes[0].visible is True assert mock.call_count == 3 def test_fibaro_scene_start() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/2/action/start") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 scenes[0].start() assert mock.call_count == 4 def test_fibaro_scene_start_with_pin_raise_exception() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/2/action/start") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 with pytest.raises(NotImplementedError): scenes[0].start("1234") assert mock.call_count == 3 def test_fibaro_scene_stop() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/2/action/stop") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 scenes[0].stop() assert mock.call_count == 4 def test_fibaro_scene_stop_with_pin_raises_exception() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/2/action/stop") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 with pytest.raises(NotImplementedError): scenes[0].stop("1234") assert mock.call_count == 3 pyfibaro-0.8.2/tests/test_fibaro_scene_hc3.py000066400000000000000000000052561476015233600213020ustar00rootroot00000000000000"""Test SceneModel class.""" import requests_mock from pyfibaro.fibaro_client import FibaroClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture scene_payload = load_fixture("scene-hc3.json") login_payload = load_fixture("login_success.json") info_payload = load_fixture("info.json") info_payload["serialNumber"] = "HC3-111111" def test_fibaro_scene() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert scenes is not None assert len(scenes) == 2 assert scenes[0].fibaro_id == 1 assert scenes[0].name == "Morning Scenario" assert scenes[0].room_id == 219 assert scenes[0].visible is True assert mock.call_count == 3 def test_fibaro_scene_start() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/1/execute") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 scenes[0].start("1234") assert mock.call_count == 4 def test_fibaro_scene_stop() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri("GET", f"{TEST_BASE_URL}scenes", json=scene_payload) mock.register_uri("POST", f"{TEST_BASE_URL}scenes/1/kill") mock.register_uri("GET", f"{TEST_BASE_URL}loginStatus", json=login_payload) mock.register_uri("GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = FibaroClient(TEST_BASE_URL) client.set_authentication(TEST_USERNAME, TEST_PASSWORD) client.connect() scenes = client.read_scenes() assert mock.call_count == 3 scenes[0].stop() assert mock.call_count == 4 pyfibaro-0.8.2/tests/test_fibaro_state_multiplexer.py000066400000000000000000000061601476015233600232150ustar00rootroot00000000000000"""Test FibaroStateMultiplexer class.""" from unittest.mock import Mock from pyfibaro.fibaro_device import DeviceModel from pyfibaro.fibaro_state_multiplexer import FibaroStateMultiplexer from .test_utils import load_fixture refresh_payload = load_fixture("refresh.json") device_payload = load_fixture("device.json") def test_fibaro_state_multiplexer_start() -> None: """Test state multiplexer start.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] fibaro_client = Mock() fibaro_client.read_devices.return_value = devices multiplexer = FibaroStateMultiplexer(fibaro_client) multiplexer.start() fibaro_client.register_update_handler.assert_called_once() def test_fibaro_state_multiplexer_stop() -> None: """Test state multiplexer stop.""" fibaro_client = Mock() multiplexer = FibaroStateMultiplexer(fibaro_client) multiplexer.stop() fibaro_client.unregister_update_handler.assert_called_once() def test_fibaro_state_multiplexer_state_change_listener() -> None: """Test state multiplexer add state change listener.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] fibaro_client = Mock() fibaro_client.read_devices.return_value = devices multiplexer = FibaroStateMultiplexer(fibaro_client) multiplexer.start() result_mock = Mock() multiplexer.add_change_listener(13, result_mock.call_method) assert len(multiplexer._change_listeners.get(13)) == 1 multiplexer._on_change(refresh_payload) result_mock.call_method.assert_called_once() def test_fibaro_state_multiplexer_get_devices() -> None: """Test state multiplexer add state change listener.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] fibaro_client = Mock() fibaro_client.read_devices.return_value = devices multiplexer = FibaroStateMultiplexer(fibaro_client) multiplexer.start() devices = multiplexer.get_devices() assert len(devices) == 2 def test_fibaro_state_multiplexer_add_remove_state_change_listener() -> None: """Test state multiplexer add state change listener.""" devices = [ DeviceModel(device_payload[2], Mock(), 4), DeviceModel(device_payload[3], Mock(), 4), ] fibaro_client = Mock() fibaro_client.read_devices.return_value = devices multiplexer = FibaroStateMultiplexer(fibaro_client) result_mock = Mock() remove = multiplexer.add_change_listener(13, result_mock.call_method) assert len(multiplexer._change_listeners.get(13)) == 1 remove() assert len(multiplexer._change_listeners.get(13)) == 0 def test_fibaro_state_multiplexer_event_listener() -> None: """Test state multiplexer add event listener.""" fibaro_client = Mock() multiplexer = FibaroStateMultiplexer(fibaro_client) result_mock = Mock() multiplexer.add_event_listener(28, result_mock.call_method) multiplexer._on_change(refresh_payload) result_mock.call_method.assert_called_once() pyfibaro-0.8.2/tests/test_fibaro_state_resolver.py000066400000000000000000000027761476015233600225150ustar00rootroot00000000000000"""Test FibaroStateResolver class.""" from pyfibaro.fibaro_state_resolver import FibaroStateResolver from .test_utils import load_fixture refresh_payload = load_fixture("refresh.json") refresh_extended_payload = load_fixture("refresh_extended.json") def test_fibaro_state_resolver_event() -> None: """Test event resolver""" resolver = FibaroStateResolver(refresh_payload) events = resolver.get_events() assert events is not None assert len(events) == 2 assert events[0].event_type == "DevicePropertyUpdatedEvent" assert events[0].fibaro_id == 28 assert events[0].key_event_type == "" assert events[0].key_id == 0 assert events[1].event_type == "PowerMetricsChangedEvent" assert events[1].fibaro_id is None assert events[1].key_event_type == "" assert events[1].key_id == 0 def test_fibaro_state_resolver_state_update() -> None: """Test event resolver""" resolver = FibaroStateResolver(refresh_payload) changes = resolver.get_state_updates() assert changes is not None assert len(changes) == 2 assert changes[0].fibaro_id == 28 assert changes[0].property_changes == {"value": "232.88"} def test_fibaro_state_resolver_state_update_extended() -> None: """Test event resolver""" resolver = FibaroStateResolver(refresh_extended_payload) changes = resolver.get_state_updates() assert changes is not None assert len(changes) == 1 assert changes[0].fibaro_id == 28 assert changes[0].property_changes == {"value": "232.88"} pyfibaro-0.8.2/tests/test_rest_client.py000066400000000000000000000031351476015233600204330ustar00rootroot00000000000000"""Test rest client.""" import requests_mock from pyfibaro.common.rest_client import RestClient from .test_utils import TEST_BASE_URL, TEST_PASSWORD, TEST_USERNAME, load_fixture info_payload = load_fixture("info.json") def test_get_request() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "GET", f"{TEST_BASE_URL}settings/info", json=info_payload) client = RestClient(TEST_BASE_URL, False, TEST_USERNAME, TEST_PASSWORD) response = client.get("settings/info") assert mock.call_count == 1 assert response == info_payload def test_https_get_request() -> None: """Test get request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) url = "https://192.169.1.40/api/" mock.register_uri("GET", f"{url}settings/info", json=info_payload) client = RestClient(url, True, TEST_USERNAME, TEST_PASSWORD) response = client.get("settings/info") assert mock.call_count == 1 assert response == info_payload def test_post_request() -> None: """Test post request""" with requests_mock.Mocker() as mock: assert isinstance(mock, requests_mock.Mocker) mock.register_uri( "POST", f"{TEST_BASE_URL}settings/info", json=info_payload) client = RestClient(TEST_BASE_URL, False, TEST_USERNAME, TEST_PASSWORD) response = client.post("settings/info", info_payload) assert mock.call_count == 1 assert response == info_payload pyfibaro-0.8.2/tests/test_utils.py000066400000000000000000000005141476015233600172560ustar00rootroot00000000000000"""Helpers for fibaro tests.""" import json from typing import Any TEST_BASE_URL = "http://192.168.1.166/api/" TEST_USERNAME = "admin" TEST_PASSWORD = "admin" def load_fixture(filename: str) -> Any: """Load a json file.""" with open(f"tests/fixture/{filename}", encoding="UTF-8") as file: return json.load(file)