diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..7b55066 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +source = src +omit = + */__init__.py + */debugger.py \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4efc283..f6057b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,11 +35,11 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }} + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements_all.txt') }}-${{ hashFiles('requirements_test_all.txt') }}- + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements_all.txt') }} ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}- - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' @@ -47,7 +47,7 @@ jobs: python -m venv venv . venv/bin/activate pip install -U "pip<20.3" setuptools - pip install -r requirements.txt -r requirements_test.txt + pip install -r requirements_all.txt -r requirements_test_all.txt - name: Restore pre-commit environment from cache id: cache-precommit uses: actions/cache@v2.1.4 @@ -83,8 +83,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -127,8 +127,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -171,8 +171,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -218,8 +218,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -265,8 +265,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -312,8 +312,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -356,8 +356,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -403,8 +403,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -447,8 +447,8 @@ jobs: key: >- ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }} + hashFiles('requirements_all.txt') }}-${{ + hashFiles('requirements_test_all.txt') }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e7771da..3ce5e68 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -23,7 +23,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install flake8 - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements_all.txt ]; then pip install -r requirements_all.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors. diff --git a/.gitignore b/.gitignore index 80496ef..192a913 100644 --- a/.gitignore +++ b/.gitignore @@ -10,11 +10,12 @@ __pycache__/ build dist pyhiveapi.egg-info +*.coverage +venv # due to using tox and pytest .tox .cache -test* custom_tests/* # virtual environment folder diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cdacb76..51a0a50 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,18 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 25.9.0 hooks: - id: black args: - --safe - --quiet - repo: https://github.com/codespell-project/codespell - rev: v2.1.0 + rev: v2.4.1 hooks: - id: codespell args: @@ -21,14 +21,14 @@ repos: - --quiet-level=2 exclude_types: [csv, json] - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 7.3.0 hooks: - id: flake8 additional_dependencies: - flake8-docstrings==1.5.0 - pydocstyle==5.1.1 - repo: https://github.com/PyCQA/bandit - rev: 1.7.4 + rev: 1.8.6 hooks: - id: bandit args: @@ -36,21 +36,21 @@ repos: - --format=custom - --configfile=tests/bandit.yaml - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 6.0.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.26.3 + rev: v1.37.1 hooks: - id: yamllint - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v4.0.0-alpha.8 hooks: - id: prettier stages: [manual] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v6.0.0 hooks: - id: check-executables-have-shebangs stages: [manual] @@ -59,13 +59,10 @@ repos: - id: no-commit-to-branch args: - --branch=master - - repo: local + - repo: https://github.com/pycqa/pylint + rev: v3.3.1 hooks: - id: pylint - name: pylint - entry: pylint - language: system - types: [python] args: [ "-rn", # Only display messages diff --git a/.pylintrc b/.pylintrc index 15a09fa..5006899 100644 --- a/.pylintrc +++ b/.pylintrc @@ -154,7 +154,13 @@ disable=raw-checker-failed, too-many-arguments, too-many-branches, duplicate-code, - import-error + import-error, + R0917, + E1101, + W0603, + W0123, + R0401, + # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/.vscode/settings.json b/.vscode/settings.json index 7688f82..46987f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { - "python.testing.pytestArgs": [], + "python.testing.pytestArgs": ["--disable-warnings"], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "[python]": { + "editor.showUnused": false, + }, + "python.formatting.provider": "black", } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f36eb4a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,114 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Pytest", + "detail": "Execute pytest tests for pyhiveapi/apyhiveapi", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pytest", "--timeout=10", "tests"], + "dependsOn": ["Install all Test Requirements", "Install Package Locally"], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Flake8", + "detail": "Run flake8 checks on the library", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pre-commit", "run", "flake8", "--all-files"], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Pylint", + "detail": "Run pylint checks on the library", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pylint", "src"], + "dependsOn": ["Install all Requirements"], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Code Coverage", + "detail": "Generate local code coverage report.", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pytest", "--cov-report", "html", "--cov=apyhiveapi", "tests/apyhiveapi", "--cov-report", "html", "--cov=pyhiveapi", "--cov-append", "tests/pyhiveapi"], + "dependsOn": ["Install all Test Requirements", "Install Package Locally"], + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Install all Requirements", + "detail": "Install all requirements needed to run pyhiveapi/apyihiveapi ", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pip", "install", "-r", "requirements_all.txt"], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Install all Test Requirements", + "detail": "Install all test requirement for pytest.", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pip", "install", "-r", "requirements_test_all.txt"], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + { + "label": "Install Package Locally", + "detail": "Install pyhiveapi/apyhiveapi locally.", + "type": "shell", + "command": "${command:python.interpreterPath}", + "args": ["-m", "pip", "install", "-e", "."], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always" + }, + "problemMatcher": [] + }, + ], + "inputs": [] +} \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 776f831..d04c8c0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -recursive-include pyhiveapi * -include requirements.txt -include requirements_test.txt +recursive-include src * +include requirements_all.txt +include requirements_test_all.txt recursive-include data * \ No newline at end of file diff --git a/pyhiveapi/apyhiveapi/camera.py b/pyhiveapi/apyhiveapi/camera.py deleted file mode 100644 index 7ccd006..0000000 --- a/pyhiveapi/apyhiveapi/camera.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Hive Camera Module.""" -# pylint: skip-file - - -class HiveCamera: - """Hive camera. - - Returns: - object: Hive camera - """ - - cameraType = "Camera" - - async def getCameraTemperature(self, device: dict): - """Get the camera state. - - Returns: - boolean: True/False if camera is on. - """ - state = None - - try: - data = self.session.data.devices[device["hiveID"]] - state = data["props"]["temperature"] - except KeyError as e: - await self.session.log.error(e) - - return state - - async def getCameraState(self, device: dict): - """Get the camera state. - - Returns: - boolean: True/False if camera is on. - """ - state = None - - try: - data = self.session.data.devices[device["hiveID"]] - state = True if data["state"]["mode"] == "ARMED" else False - except KeyError as e: - await self.session.log.error(e) - - return state - - async def getCameraImageURL(self, device: dict): - """Get the camera image url. - - Returns: - str: image url. - """ - state = None - - try: - state = self.session.data.camera[device["hiveID"]]["cameraImage"][ - "thumbnailUrls" - ][0] - except KeyError as e: - await self.session.log.error(e) - - return state - - async def getCameraRecodringURL(self, device: dict): - """Get the camera recording url. - - Returns: - str: image url. - """ - state = None - - try: - state = self.session.data.camera[device["hiveID"]]["cameraRecording"] - except KeyError as e: - await self.session.log.error(e) - - return state - - async def setCameraOn(self, device: dict, mode: str): - """Set the camera state to on. - - Args: - device (dict): Camera device. - - Returns: - boolean: True/False if successful. - """ - final = False - - if ( - device["hiveID"] in self.session.data.devices - and device["deviceData"]["online"] - ): - await self.session.hiveRefreshTokens() - resp = await self.session.api.setState(mode=mode) - if resp["original"] == 200: - final = True - await self.session.getCamera() - - return final - - async def setCameraOff(self, device: dict, mode: str): - """Set the camera state to on. - - Args: - device (dict): Camera device. - - Returns: - boolean: True/False if successful. - """ - final = False - - if ( - device["hiveID"] in self.session.data.devices - and device["deviceData"]["online"] - ): - await self.session.hiveRefreshTokens() - resp = await self.session.api.setState(mode=mode) - if resp["original"] == 200: - final = True - await self.session.getCamera() - - return final - - -class Camera(HiveCamera): - """Home assistant camera. - - Args: - HiveCamera (object): Class object. - """ - - def __init__(self, session: object = None): - """Initialise camera. - - Args: - session (object, optional): Used to interact with the hive account. Defaults to None. - """ - self.session = session - - async def getCamera(self, device: dict): - """Get camera data. - - Args: - device (dict): Device to update. - - Returns: - dict: Updated device. - """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} - ) - dev_data = {} - - if device["deviceData"]["online"]: - self.session.helper.deviceRecovered(device["device_id"]) - data = self.session.data.devices[device["device_id"]] - dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], - "device_id": device["device_id"], - "device_name": device["device_name"], - "status": { - "temperature": await self.getCameraTemperature(device), - "state": await self.getCameraState(device), - "imageURL": await self.getCameraImageURL(device), - "recordingURL": await self.getCameraRecodringURL(device), - }, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), - "custom": device.get("custom", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] - ), - } - - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device diff --git a/pyhiveapi/apyhiveapi/data/camera.json b/pyhiveapi/apyhiveapi/data/camera.json deleted file mode 100644 index 604ffbe..0000000 --- a/pyhiveapi/apyhiveapi/data/camera.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "cameraImage": { - "parsed": { - "events": [ - { - "thumbnailUrls": [ - "https://test.com/image" - ], - "hasRecording": true - } - ] - } - }, - "camaeraRecording": { - "parsed": "https://test.com/video" - } -} \ No newline at end of file diff --git a/pyhiveapi/apyhiveapi/helper/const.py b/pyhiveapi/apyhiveapi/helper/const.py deleted file mode 100644 index 42e8fe7..0000000 --- a/pyhiveapi/apyhiveapi/helper/const.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Constants for Pyhiveapi.""" -# pylint: skip-file -SYNC_PACKAGE_NAME = "pyhiveapi" -SYNC_PACKAGE_DIR = "/pyhiveapi/" -ASYNC_PACKAGE_NAME = "apyhiveapi" -ASYNC_PACKAGE_DIR = "/apyhiveapi/" -SMS_REQUIRED = "SMS_MFA" - - -# HTTP return codes. -HTTP_OK = 200 -HTTP_CREATED = 201 -HTTP_ACCEPTED = 202 -HTTP_MOVED_PERMANENTLY = 301 -HTTP_BAD_REQUEST = 400 -HTTP_UNAUTHORIZED = 401 -HTTP_FORBIDDEN = 403 -HTTP_NOT_FOUND = 404 -HTTP_METHOD_NOT_ALLOWED = 405 -HTTP_UNPROCESSABLE_ENTITY = 422 -HTTP_TOO_MANY_REQUESTS = 429 -HTTP_INTERNAL_SERVER_ERROR = 500 -HTTP_BAD_GATEWAY = 502 -HTTP_SERVICE_UNAVAILABLE = 503 - - -HIVETOHA = { - "Alarm": {"home": "armed_home", "away": "armed_away", "asleep": "armed_night"}, - "Attribute": {True: "Online", False: "Offline"}, - "Boost": {None: "OFF", False: "OFF"}, - "Heating": {False: "OFF", "ENABLED": True, "DISABLED": False}, - "Hotwater": {"MANUAL": "ON", None: "OFF", False: "OFF"}, - "Hub": { - "Status": {True: 1, False: 0}, - "Smoke": {True: 1, False: 0}, - "Dog": {True: 1, False: 0}, - "Glass": {True: 1, False: 0}, - }, - "Light": {"ON": True, "OFF": False}, - "Sensor": { - "OPEN": True, - "CLOSED": False, - True: "Online", - False: "Offline", - }, - "Switch": {"ON": True, "OFF": False}, -} - -HIVE_TYPES = { - "Hub": ["hub", "sense"], - "Thermo": ["thermostatui", "trv"], - "Heating": ["heating", "trvcontrol"], - "Hotwater": ["hotwater"], - "Light": ["warmwhitelight", "tuneablelight", "colourtuneablelight"], - "Sensor": ["motionsensor", "contactsensor"], - "Switch": ["activeplug"], -} -sensor_commands = { - "SMOKE_CO": "self.session.hub.getSmokeStatus(device)", - "DOG_BARK": "self.session.hub.getDogBarkStatus(device)", - "GLASS_BREAK": "self.session.hub.getGlassBreakStatus(device)", - "Camera_Temp": "self.session.camera.getCameraTemperature(device)", - "Current_Temperature": "self.session.heating.getCurrentTemperature(device)", - "Heating_Current_Temperature": "self.session.heating.getCurrentTemperature(device)", - "Heating_Target_Temperature": "self.session.heating.getTargetTemperature(device)", - "Heating_State": "self.session.heating.getState(device)", - "Heating_Mode": "self.session.heating.getMode(device)", - "Heating_Boost": "self.session.heating.getBoostStatus(device)", - "Hotwater_State": "self.session.hotwater.getState(device)", - "Hotwater_Mode": "self.session.hotwater.getMode(device)", - "Hotwater_Boost": "self.session.hotwater.getBoost(device)", - "Battery": 'self.session.attr.getBattery(device["device_id"])', - "Mode": 'self.session.attr.getMode(device["hiveID"])', - "Availability": "self.online(device)", - "Connectivity": "self.online(device)", - "Power": "self.session.switch.getPowerUsage(device)", -} - -PRODUCTS = { - "sense": [ - 'addList("binary_sensor", p, haName="Glass Detection", hiveType="GLASS_BREAK")', - 'addList("binary_sensor", p, haName="Smoke Detection", hiveType="SMOKE_CO")', - 'addList("binary_sensor", p, haName="Dog Bark Detection", hiveType="DOG_BARK")', - ], - "heating": [ - 'addList("climate", p, temperatureunit=self.data["user"]["temperatureUnit"])', - 'addList("switch", p, haName=" Heat on Demand", hiveType="Heating_Heat_On_Demand", category="config")', - 'addList("sensor", p, haName=" Current Temperature", hiveType="Heating_Current_Temperature", category="diagnostic")', - 'addList("sensor", p, haName=" Target Temperature", hiveType="Heating_Target_Temperature", category="diagnostic")', - 'addList("sensor", p, haName=" State", hiveType="Heating_State", category="diagnostic")', - 'addList("sensor", p, haName=" Mode", hiveType="Heating_Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Boost", hiveType="Heating_Boost", category="diagnostic")', - ], - "trvcontrol": [ - 'addList("climate", p, temperatureunit=self.data["user"]["temperatureUnit"])', - 'addList("sensor", p, haName=" Current Temperature", hiveType="Heating_Current_Temperature", category="diagnostic")', - 'addList("sensor", p, haName=" Target Temperature", hiveType="Heating_Target_Temperature", category="diagnostic")', - 'addList("sensor", p, haName=" State", hiveType="Heating_State", category="diagnostic")', - 'addList("sensor", p, haName=" Mode", hiveType="Heating_Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Boost", hiveType="Heating_Boost", category="diagnostic")', - ], - "hotwater": [ - 'addList("water_heater", p,)', - 'addList("sensor", p, haName="Hotwater State", hiveType="Hotwater_State", category="diagnostic")', - 'addList("sensor", p, haName="Hotwater Mode", hiveType="Hotwater_Mode", category="diagnostic")', - 'addList("sensor", p, haName="Hotwater Boost", hiveType="Hotwater_Boost", category="diagnostic")', - ], - "activeplug": [ - 'addList("switch", p)', - 'addList("sensor", p, haName=" Mode", hiveType="Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Availability", hiveType="Availability", category="diagnostic")', - 'addList("sensor", p, haName=" Power", hiveType="Power", category="diagnostic")', - ], - "warmwhitelight": [ - 'addList("light", p)', - 'addList("sensor", p, haName=" Mode", hiveType="Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - "tuneablelight": [ - 'addList("light", p)', - 'addList("sensor", p, haName=" Mode", hiveType="Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - "colourtuneablelight": [ - 'addList("light", p)', - 'addList("sensor", p, haName=" Mode", hiveType="Mode", category="diagnostic")', - 'addList("sensor", p, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - # "hivecamera": [ - # 'addList("camera", p)', - # 'addList("sensor", p, haName=" Mode", hiveType="Mode", category="diagnostic")', - # 'addList("sensor", p, haName=" Availability", hiveType="Availability", category="diagnostic")', - # 'addList("sensor", p, haName=" Temperature", hiveType="Camera_Temp", category="diagnostic")', - # ], - "motionsensor": [ - 'addList("binary_sensor", p)', - 'addList("sensor", p, haName=" Current Temperature", hiveType="Current_Temperature", category="diagnostic")', - ], - "contactsensor": ['addList("binary_sensor", p)'], -} - -DEVICES = { - "contactsensor": [ - 'addList("sensor", d, haName=" Battery Level", hiveType="Battery", category="diagnostic")', - 'addList("sensor", d, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - "hub": [ - 'addList("binary_sensor", d, haName="Hive Hub Status", hiveType="Connectivity", category="diagnostic")', - ], - "motionsensor": [ - 'addList("sensor", d, haName=" Battery Level", hiveType="Battery", category="diagnostic")', - 'addList("sensor", d, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - "sense": [ - 'addList("binary_sensor", d, haName="Hive Hub Status", hiveType="Connectivity")', - ], - "siren": ['addList("alarm_control_panel", d)'], - "thermostatui": [ - 'addList("sensor", d, haName=" Battery Level", hiveType="Battery", category="diagnostic")', - 'addList("sensor", d, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], - "trv": [ - 'addList("sensor", d, haName=" Battery Level", hiveType="Battery", category="diagnostic")', - 'addList("sensor", d, haName=" Availability", hiveType="Availability", category="diagnostic")', - ], -} - -ACTIONS = ( - 'addList("switch", a, hiveName=a["name"], haName=a["name"], hiveType="action")' -) diff --git a/pyhiveapi/apyhiveapi/helper/logger.py b/pyhiveapi/apyhiveapi/helper/logger.py deleted file mode 100644 index db8fe21..0000000 --- a/pyhiveapi/apyhiveapi/helper/logger.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Custom Logging Module.""" -# pylint: skip-file -import inspect -from datetime import datetime - - -class Logger: - """Custom Logging Code.""" - - def __init__(self, session=None): - """Initialise the logger class.""" - self.session = session - - async def error(self, e="UNKNOWN"): - """Process and unexpected error.""" - self.session.logger.error( - f"An unexpected error has occurred whilst" - f" executing {inspect.stack()[1][3]}" - f" with exception {e.__class__} {e}" - ) - - async def errorCheck(self, n_id, n_type, error_type, **kwargs): - """Error has occurred.""" - message = None - name = self.session.helper.getDeviceName(n_id) - - if error_type is False: - message = "Device offline could not update entity - " + name - if n_id not in self.session.config.errorList: - self.session.logger.warning(message) - self.session.config.errorList.update({n_id: datetime.now()}) - elif error_type == "Failed": - message = "ERROR - No data found for device - " + name - if n_id not in self.session.config.errorList: - self.session.logger.error(message) - self.session.config.errorList.update({n_id: datetime.now()}) diff --git a/pyhiveapi/apyhiveapi/helper/map.py b/pyhiveapi/apyhiveapi/helper/map.py deleted file mode 100644 index afcadd6..0000000 --- a/pyhiveapi/apyhiveapi/helper/map.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Dot notation for dictionary.""" -# pylint: skip-file - - -class Map(dict): - """dot.notation access to dictionary attributes. - - Args: - dict (dict): dictionary to map. - """ - - __getattr__ = dict.get - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ diff --git a/requirements.txt b/requirements_all.txt similarity index 100% rename from requirements.txt rename to requirements_all.txt diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index b654c22..0000000 --- a/requirements_test.txt +++ /dev/null @@ -1 +0,0 @@ -tox \ No newline at end of file diff --git a/requirements_test_all.txt b/requirements_test_all.txt new file mode 100644 index 0000000..6700af6 --- /dev/null +++ b/requirements_test_all.txt @@ -0,0 +1,6 @@ +pyhiveapi +pylint +pytest +pytest-cov +pytest-timeout +tox \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 325cf5f..4ad4efb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,19 +13,22 @@ classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: MIT License - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 + [options] -package_dir = - = pyhiveapi +package_dir = + = src packages = find: -python_requires = >=3.6 +python_requires = >=3.8 [options.packages.find] -where = pyhiveapi +where = src [build-system] requires = ["setuptools>=40.6.2", "wheel", "unasync"] @@ -66,3 +69,9 @@ warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true +[tool:pytest] +minversion = 6.0 +addopts = -ra -q +testpaths = + tests/pyhiveapi + tests/apyhiveapi \ No newline at end of file diff --git a/setup.py b/setup.py index b5c635c..17e7f1c 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """Setup pyhiveapi package.""" -# pylint: skip-file + import os import re @@ -7,23 +7,23 @@ from setuptools import setup -def requirements_from_file(filename="requirements.txt"): +def requirements_from_file(filename="requirements_all.txt"): """Get requirements from file.""" - with open(os.path.join(os.path.dirname(__file__), filename)) as r: + with open(os.path.join(os.path.dirname(__file__), filename), encoding="utf-8") as r: reqs = r.read().strip().split("\n") # Return non empty lines and non comments return [r for r in reqs if re.match(r"^\w+", r)] setup( - version="1.0.1", + version="1.0.4", package_data={"data": ["*.json"]}, include_package_data=True, cmdclass={ "build_py": unasync.cmdclass_build_py( rules=[ unasync.Rule( - "/apyhiveapi/", + "/src/apyhiveapi/", "/pyhiveapi/", additional_replacements={ "apyhiveapi": "pyhiveapi", @@ -31,7 +31,7 @@ def requirements_from_file(filename="requirements.txt"): }, ), unasync.Rule( - "/apyhiveapi/api/", + "/src/apyhiveapi/api/", "/pyhiveapi/api/", additional_replacements={ "apyhiveapi": "pyhiveapi", @@ -41,5 +41,5 @@ def requirements_from_file(filename="requirements.txt"): ) }, install_requires=requirements_from_file(), - extras_require={"dev": requirements_from_file("requirements_test.txt")}, + extras_require={"dev": requirements_from_file("requirements_test_all.txt")}, ) diff --git a/pyhiveapi/apyhiveapi/__init__.py b/src/apyhiveapi/__init__.py similarity index 64% rename from pyhiveapi/apyhiveapi/__init__.py rename to src/apyhiveapi/__init__.py index 7436221..9fe3a7e 100644 --- a/pyhiveapi/apyhiveapi/__init__.py +++ b/src/apyhiveapi/__init__.py @@ -1,11 +1,20 @@ """__init__.py.""" -# pylint: skip-file + +import os + +from .helper.const import SMS_REQUIRED # noqa: F401 +from .hive import Hive # noqa: F401 + if __name__ == "pyhiveapi": from .api.hive_api import HiveApi as API # noqa: F401 from .api.hive_auth import HiveAuth as Auth # noqa: F401 + + PATH = os.path.dirname(os.path.realpath(__file__)) + "/test_data/" + PATH = PATH.replace("/pyhiveapi/", "/apyhiveapi/") else: from .api.hive_async_api import HiveApiAsync as API # noqa: F401 from .api.hive_auth_async import HiveAuthAsync as Auth # noqa: F401 -from .helper.const import SMS_REQUIRED # noqa: F401 -from .hive import Hive # noqa: F401 + PATH = os.path.dirname(os.path.realpath(__file__)) + "/test_data/" + + # noqa: F401 diff --git a/pyhiveapi/apyhiveapi/action.py b/src/apyhiveapi/action.py similarity index 51% rename from pyhiveapi/apyhiveapi/action.py rename to src/apyhiveapi/action.py index c4af98f..96c260b 100644 --- a/pyhiveapi/apyhiveapi/action.py +++ b/src/apyhiveapi/action.py @@ -1,7 +1,6 @@ """Hive Action Module.""" -# pylint: skip-file - +import json class HiveAction: """Hive Action Code. @@ -9,7 +8,7 @@ class HiveAction: object: Return hive action object. """ - actionType = "Actions" + action_type = "Actions" def __init__(self, session: object = None): """Initialise Action. @@ -19,7 +18,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getAction(self, device: dict): + async def get_action(self, device: dict): """Action device to update. Args: @@ -30,28 +29,28 @@ async def getAction(self, device: dict): """ dev_data = {} - if device["hiveID"] in self.data["action"]: + if device["hive_id"] in self.session.data.actions: dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], - "status": {"state": await self.getState(device)}, + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], + "status": {"state": await self.get_state(device)}, "power_usage": None, - "deviceData": {}, + "device_data": {}, "custom": device.get("custom", None), } - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - exists = self.session.data.actions.get("hiveID", False) - if exists is False: - return "REMOVE" - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + exists = self.session.data.actions.get("hive_id", False) + if exists is False: + return "REMOVE" + return device - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get action state. Args: @@ -63,14 +62,14 @@ async def getState(self, device: dict): final = None try: - data = self.session.data.actions[device["hiveID"]] + data = self.session.data.actions[device["hive_id"]] final = data["enabled"] except KeyError as e: await self.session.log.error(e) return final - async def setStatusOn(self, device: dict): + async def set_status_on(self, device: dict): """Set action turn on. Args: @@ -79,23 +78,23 @@ async def setStatusOn(self, device: dict): Returns: boolean: True/False if successful. """ - import json + final = False - if device["hiveID"] in self.session.data.actions: - await self.session.hiveRefreshTokens() - data = self.session.data.actions[device["hiveID"]] + if device["hive_id"] in self.session.data.actions: + await self.session.hive_refresh_tokens() + data = self.session.data.actions[device["hive_id"]] data.update({"enabled": True}) send = json.dumps(data) - resp = await self.session.api.setAction(device["hiveID"], send) + resp = await self.session.api.set_action(device["hive_id"], send) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setStatusOff(self, device: dict): + async def set_status_off(self, device: dict): """Set action to turn off. Args: @@ -104,18 +103,16 @@ async def setStatusOff(self, device: dict): Returns: boolean: True/False if successful. """ - import json - final = False - if device["hiveID"] in self.session.data.actions: - await self.session.hiveRefreshTokens() - data = self.session.data.actions[device["hiveID"]] + if device["hive_id"] in self.session.data.actions: + await self.session.hive_refresh_tokens() + data = self.session.data.actions[device["hive_id"]] data.update({"enabled": False}) send = json.dumps(data) - resp = await self.session.api.setAction(device["hiveID"], send) + resp = await self.session.api.set_action(device["hive_id"], send) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final diff --git a/pyhiveapi/apyhiveapi/alarm.py b/src/apyhiveapi/alarm.py similarity index 54% rename from pyhiveapi/apyhiveapi/alarm.py rename to src/apyhiveapi/alarm.py index d85fdfb..59974fd 100644 --- a/pyhiveapi/apyhiveapi/alarm.py +++ b/src/apyhiveapi/alarm.py @@ -1,5 +1,4 @@ """Hive Alarm Module.""" -# pylint: skip-file class HiveHomeShield: @@ -9,9 +8,9 @@ class HiveHomeShield: object: Hive homeshield """ - alarmType = "Alarm" + alarm_type = "Alarm" - async def getMode(self): + async def get_mode(self): """Get current mode of the alarm. Returns: @@ -27,7 +26,7 @@ async def getMode(self): return state - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get the alarm triggered state. Returns: @@ -36,14 +35,14 @@ async def getState(self, device: dict): state = None try: - data = self.session.data.devices[device["hiveID"]] + data = self.session.data.devices[device["hive_id"]] state = data["state"]["alarmActive"] except KeyError as e: await self.session.log.error(e) return state - async def setMode(self, device: dict, mode: str): + async def set_mode(self, device: dict, mode: str): """Set the alarm mode. Args: @@ -55,14 +54,14 @@ async def setMode(self, device: dict, mode: str): final = False if ( - device["hiveID"] in self.session.data.devices - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.devices + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - resp = await self.session.api.setAlarm(mode=mode) + await self.session.hive_refresh_tokens() + resp = await self.session.api.set_alarm(mode=mode) if resp["original"] == 200: final = True - await self.session.getAlarm() + await self.session.get_alarm() return final @@ -82,7 +81,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getAlarm(self, device: dict): + async def get_alarm(self, device: dict): """Get alarm data. Args: @@ -91,38 +90,38 @@ async def getAlarm(self, device: dict): Returns: dict: Updated device. """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} ) dev_data = {} - if device["deviceData"]["online"]: - self.session.helper.deviceRecovered(device["device_id"]) + if device["device_data"]["online"]: + self.session.helper.device_recovered(device["device_id"]) data = self.session.data.devices[device["device_id"]] dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], "device_id": device["device_id"], "device_name": device["device_name"], "status": { - "state": await self.getState(device), - "mode": await self.getMode(), + "state": await self.get_state(device), + "mode": await self.get_mode(), }, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), "custom": device.get("custom", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] ), } - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device diff --git a/pyhiveapi/apyhiveapi/api/__init__.py b/src/apyhiveapi/api/__init__.py similarity index 100% rename from pyhiveapi/apyhiveapi/api/__init__.py rename to src/apyhiveapi/api/__init__.py diff --git a/pyhiveapi/apyhiveapi/api/hive_api.py b/src/apyhiveapi/api/hive_api.py similarity index 75% rename from pyhiveapi/apyhiveapi/api/hive_api.py rename to src/apyhiveapi/api/hive_api.py index c81e282..da65a57 100644 --- a/pyhiveapi/apyhiveapi/api/hive_api.py +++ b/src/apyhiveapi/api/hive_api.py @@ -1,10 +1,11 @@ """Hive API Module.""" -# pylint: skip-file + import json +from typing import Any, Dict, Optional +from pyquery import PyQuery import requests import urllib3 -from pyquery import PyQuery urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) @@ -12,9 +13,9 @@ class HiveApi: """Hive API Code.""" - def __init__(self, hiveSession=None, websession=None, token=None): + def __init__(self, hive_session=None, websession=None, token=None): """Hive API initialisation.""" - self.cameraBaseUrl = "prod.hcam.bgchtest.info" + self.camera_base_url = "prod.hcam.bgchtest.info" self.urls = { "properties": "https://sso.hivehome.com/", "login": "https://beekeeper.hivehome.com/1.0/cognito/login", @@ -25,8 +26,10 @@ def __init__(self, hiveSession=None, websession=None, token=None): "holiday_mode": "/holiday-mode", "all": "/nodes/all?products=true&devices=true&actions=true", "alarm": "/security-lite?homeId=", - "cameraImages": f"https://event-history-service.{self.cameraBaseUrl}/v1/events/cameras?latest=true&cameraId={{0}}", - "cameraRecordings": f"https://event-history-service.{self.cameraBaseUrl}/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8", + "cameraImages": f"https://event-history-service.{self.camera_base_url}" \ + "/v1/events/cameras?latest=true&cameraId={{0}}", + "cameraRecordings": f"https://event-history-service.{self.camera_base_url}" \ + "/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8", "devices": "/devices", "products": "/products", "actions": "/actions", @@ -37,24 +40,26 @@ def __init__(self, hiveSession=None, websession=None, token=None): "original": "No response to Hive API request", "parsed": "No response to Hive API request", } - self.session = hiveSession + self.session = hive_session self.token = token + self.headers = {} + self.websession = websession - def request(self, type, url, jsc=None, camera=False): + def request(self, request_type, url, jsc=None, camera=False): """Make API request.""" if self.session is not None: if camera: self.headers = { "content-type": "application/json", "Accept": "*/*", - "Authorization": f"Bearer {self.session.tokens.tokenData['token']}", - "x-jwt-token": self.session.tokens.tokenData["token"], + "Authorization": f"Bearer {self.session.tokens.token_data['token']}", + "x-jwt-token": self.session.tokens.token_data["token"], } else: self.headers = { "content-type": "application/json", "Accept": "*/*", - "authorization": self.session.tokens.tokenData["token"], + "authorization": self.session.tokens.token_data["token"], } else: if camera: @@ -71,32 +76,28 @@ def request(self, type, url, jsc=None, camera=False): "authorization": self.token, } - if type == "GET": + if request_type == "GET": return requests.get( url=url, headers=self.headers, data=jsc, timeout=self.timeout ) - if type == "POST": + if request_type == "POST": return requests.post( url=url, headers=self.headers, data=jsc, timeout=self.timeout ) - def refreshTokens(self, tokens={}): + return None + + def refresh_tokens(self, tokens: Dict[str, str] = None) -> Dict[str, Any]: """Get new session tokens - DEPRECATED NOW BY AWS TOKEN MANAGEMENT.""" url = self.urls["refresh"] if self.session is not None: - tokens = self.session.tokens.tokenData - jsc = ( - "{" - + ",".join( - ('"' + str(i) + '": ' '"' + str(t) + '" ' for i, t in tokens.items()) - ) - + "}" - ) + tokens = self.session.tokens.token_data + jsc = json.dumps({str(i): str(t) for i, t in tokens.items()}) try: info = self.request("POST", url, jsc) - data = json.loads(info.text) + data = info.json() if "token" in data and self.session: - self.session.updateTokens(data) + self.session.update_tokens(data) self.urls.update({"base": data["platform"]["endpoint"]}) self.urls.update({"camera": data["platform"]["cameraPlatform"]}) self.json_return.update({"original": info.status_code}) @@ -106,59 +107,59 @@ def refreshTokens(self, tokens={}): return self.json_return - def getLoginInfo(self): + def get_login_info(self) -> Dict[str, str]: """Get login properties to make the login request.""" url = self.urls["properties"] try: data = requests.get(url=url, verify=False, timeout=self.timeout) html = PyQuery(data.content) json_data = json.loads( - '{"' - + (html("script:first").text()) - .replace(",", ', "') - .replace("=", '":') - .replace("window.", "") - + "}" + f'{{"{html("script:first").text().replace(",", ", ").replace( + "=", ":").replace("window.", "")}"}}' ) - loginData = {} - loginData.update({"UPID": json_data["HiveSSOPoolId"]}) - loginData.update({"CLIID": json_data["HiveSSOPublicCognitoClientId"]}) - loginData.update({"REGION": json_data["HiveSSOPoolId"]}) - return loginData + login_data = {} + login_data.update({"UPID": json_data["HiveSSOPoolId"]}) + login_data.update({"CLIID": json_data["HiveSSOPublicCognitoClientId"]}) + login_data.update({"REGION": json_data["HiveSSOPoolId"]}) + return login_data except (OSError, RuntimeError, ZeroDivisionError): self.error() + return {} - def getAll(self): + def get_all(self) -> Dict[str, Any]: """Build and query all endpoint.""" - json_return = {} + json_return: Dict[str, Any] = {} url = self.urls["base"] + self.urls["all"] try: info = self.request("GET", url) - json_return.update({"original": info.status_code}) - json_return.update({"parsed": info.json()}) + json_return["original"] = info.status_code + json_return["parsed"] = info.json() except (OSError, RuntimeError, ZeroDivisionError): self.error() return json_return - def getAlarm(self, homeID=None): + def get_alarm(self, home_id: Optional[str] = None) -> Dict[str, Any]: """Build and query alarm endpoint.""" if self.session is not None: - homeID = self.session.config.homeID - url = self.urls["base"] + self.urls["alarm"] + homeID + home_id = self.session.config.home_id + url = self.urls["base"] + self.urls["alarm"] + home_id + json_return: Dict[str, Any] = {} try: info = self.request("GET", url) - self.json_return.update({"original": info.status_code}) - self.json_return.update({"parsed": info.json()}) + json_return["original"] = info.status_code + json_return["parsed"] = info.json() except (OSError, RuntimeError, ZeroDivisionError): self.error() - return self.json_return + return json_return - def getCameraImage(self, device=None, accessToken=None): + def get_camera_image( + self, device: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: """Build and query camera endpoint.""" - json_return = {} + json_return: Dict[str, Any] = {} url = self.urls["cameraImages"].format(device["props"]["hardwareIdentifier"]) try: info = self.request("GET", url, camera=True) @@ -169,11 +170,13 @@ def getCameraImage(self, device=None, accessToken=None): return json_return - def getCameraRecording(self, device=None, eventId=None): + def get_camera_recording( + self, device: Optional[Dict[str, Any]] = None, event_id: Optional[str] = None + ) -> Dict[str, Any]: """Build and query camera endpoint.""" - json_return = {} - url = self.urls["cameraRecordings"].format( - device["props"]["hardwareIdentifier"], eventId + json_return: Dict[str, Any] = {} + url = self.urls["camera_recordings"].format( + device["props"]["hardwareIdentifier"], event_id ) try: info = self.request("GET", url, camera=True) @@ -184,7 +187,7 @@ def getCameraRecording(self, device=None, eventId=None): return json_return - def getDevices(self): + def get_devices(self): """Call the get devices endpoint.""" url = self.urls["base"] + self.urls["devices"] try: @@ -196,7 +199,7 @@ def getDevices(self): return self.json_return - def getProducts(self): + def get_products(self): """Call the get products endpoint.""" url = self.urls["base"] + self.urls["products"] try: @@ -208,7 +211,7 @@ def getProducts(self): return self.json_return - def getActions(self): + def get_actions(self): """Call the get actions endpoint.""" url = self.urls["base"] + self.urls["actions"] try: @@ -220,7 +223,7 @@ def getActions(self): return self.json_return - def motionSensor(self, sensor, fromepoch, toepoch): + def motion_sensor(self, sensor, fromepoch, toepoch): """Call a way to get motion sensor info.""" url = ( self.urls["base"] @@ -243,7 +246,7 @@ def motionSensor(self, sensor, fromepoch, toepoch): return self.json_return - def getWeather(self, weather_url): + def get_weather(self, weather_url): """Call endpoint to get local weather from Hive API.""" t_url = self.urls["weather"] + weather_url url = t_url.replace(" ", "%20") @@ -256,7 +259,7 @@ def getWeather(self, weather_url): return self.json_return - def setState(self, n_type, n_id, **kwargs): + def set_state(self, n_type, n_id, **kwargs): """Set the state of a Device.""" jsc = ( "{" @@ -277,7 +280,7 @@ def setState(self, n_type, n_id, **kwargs): return self.json_return - def setAction(self, n_id, data): + def set_action(self, n_id, data): """Set the state of a Action.""" jsc = data url = self.urls["base"] + self.urls["actions"] + "/" + n_id diff --git a/pyhiveapi/apyhiveapi/api/hive_async_api.py b/src/apyhiveapi/api/hive_async_api.py similarity index 64% rename from pyhiveapi/apyhiveapi/api/hive_async_api.py rename to src/apyhiveapi/api/hive_async_api.py index e38e72e..a5655e8 100644 --- a/pyhiveapi/apyhiveapi/api/hive_async_api.py +++ b/src/apyhiveapi/api/hive_async_api.py @@ -1,5 +1,5 @@ """Hive API Module.""" -# pylint: skip-file + import json from typing import Optional @@ -10,6 +10,7 @@ from ..helper.const import HTTP_UNAUTHORIZED from ..helper.hive_exceptions import FileInUse, HiveApiError, NoApiToken +##from ..session import HiveSession urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) @@ -17,23 +18,29 @@ class HiveApiAsync: """Hive API Code.""" - def __init__(self, hiveSession=None, websession: Optional[ClientSession] = None): + def __init__( + self, + hive_session = None, + websession: Optional[ClientSession] = None, + ) -> None: """Hive API initialisation.""" - self.baseUrl = "https://beekeeper.hivehome.com/1.0" - self.cameraBaseUrl = "prod.hcam.bgchtest.info" + self.base_url = "https://beekeeper.hivehome.com/1.0" + self.camera_base_url = "prod.hcam.bgchtest.info" self.urls = { "properties": "https://sso.hivehome.com/", - "login": f"{self.baseUrl}/cognito/login", - "refresh": f"{self.baseUrl}/cognito/refresh-token", - "holiday_mode": f"{self.baseUrl}/holiday-mode", - "all": f"{self.baseUrl}/nodes/all?products=true&devices=true&actions=true", - "alarm": f"{self.baseUrl}/security-lite?homeId=", - "cameraImages": f"https://event-history-service.{self.cameraBaseUrl}/v1/events/cameras?latest=true&cameraId={{0}}", - "cameraRecordings": f"https://event-history-service.{self.cameraBaseUrl}/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8", - "devices": f"{self.baseUrl}/devices", - "products": f"{self.baseUrl}/products", - "actions": f"{self.baseUrl}/actions", - "nodes": f"{self.baseUrl}/nodes/{{0}}/{{1}}", + "login": f"{self.base_url}/cognito/login", + "refresh": f"{self.base_url}/cognito/refresh-token", + "holiday_mode": f"{self.base_url}/holiday-mode", + "all": f"{self.base_url}/nodes/all?products=true&devices=true&actions=true", + "alarm": f"{self.base_url}/security-lite?homeId=", + "camera_images": f"https://event-history-service.{self.camera_base_url}" \ + "/v1/events/cameras?latest=true&cameraId={{0}}", + "camera_recordings": f"https://event-history-service.{self.camera_base_url}" \ + "/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8", + "devices": f"{self.base_url}/devices", + "products": f"{self.base_url}/products", + "actions": f"{self.base_url}/actions", + "nodes": f"{self.base_url}/nodes/{{0}}/{{1}}", "long_lived": "https://api.prod.bgchprod.info/omnia/accessTokens", "weather": "https://weather.prod.bgchprod.info/weather", } @@ -42,8 +49,8 @@ def __init__(self, hiveSession=None, websession: Optional[ClientSession] = None) "original": "No response to Hive API request", "parsed": "No response to Hive API request", } - self.session = hiveSession - self.websession = ClientSession() if websession is None else websession + self.session = hive_session + self.websession = websession async def request( self, method: str, url: str, camera: bool = False, **kwargs @@ -51,25 +58,25 @@ async def request( """Make a request.""" data = kwargs.get("data", None) + headers = {} try: + token = self.session.tokens.token_data["token"] if camera: headers = { "content-type": "application/json", "Accept": "*/*", - "Authorization": f"Bearer {self.session.tokens.tokenData['token']}", - "x-jwt-token": self.session.tokens.tokenData["token"], + "Authorization": f"Bearer {token}", + "x-jwt-token": token, } else: headers = { "content-type": "application/json", "Accept": "*/*", - "authorization": self.session.tokens.tokenData["token"], + "authorization": token, } except KeyError: - if "sso" in url: - pass - else: - raise NoApiToken + if "sso" not in url: + raise NoApiToken from None async with self.websession.request( method, url, headers=headers, data=data @@ -80,18 +87,16 @@ async def request( if resp.status == HTTP_UNAUTHORIZED: self.session.logger.error( - f"Hive token has expired when calling {url} - " - f"HTTP status is - {resp.status}" + "Hive token has expired when calling %s - HTTP status is - %d", url, resp.status ) - elif url is not None and resp.status is not None: + else: self.session.logger.error( - f"Something has gone wrong calling {url} - " - f"HTTP status is - {resp.status}" + "Something has gone wrong calling %s - HTTP status is - %d", url, resp.status ) raise HiveApiError - def getLoginInfo(self): + def get_login_info(self): """Get login properties to make the login request.""" url = "https://sso.hivehome.com/" @@ -106,40 +111,34 @@ def getLoginInfo(self): + "}" ) - loginData = {} - loginData.update({"UPID": json_data["HiveSSOPoolId"]}) - loginData.update({"CLIID": json_data["HiveSSOPublicCognitoClientId"]}) - loginData.update({"REGION": json_data["HiveSSOPoolId"]}) - return loginData - - async def refreshTokens(self): - """Refresh tokens - DEPRECATED NOW BY AWS TOKEN MANAGEMENT.""" - url = self.urls["refresh"] - if self.session is not None: - tokens = self.session.tokens.tokenData - jsc = ( - "{" - + ",".join( - ('"' + str(i) + '": ' '"' + str(t) + '" ' for i, t in tokens.items()) - ) - + "}" - ) - try: - await self.request("post", url, data=jsc) - - if self.json_return["original"] == 200: - info = self.json_return["parsed"] - if "token" in info: - await self.session.updateTokens(info) - self.baseUrl = info["platform"]["endpoint"] - self.cameraBaseUrl = info["platform"]["cameraPlatform"] - return True - except (ConnectionError, OSError, RuntimeError, ZeroDivisionError): - await self.error() - - return self.json_return - - async def getAll(self): + login_data = {} + login_data.update({"UPID": json_data["HiveSSOPoolId"]}) + login_data.update({"CLIID": json_data["HiveSSOPublicCognitoClientId"]}) + login_data.update({"REGION": json_data["HiveSSOPoolId"]}) + return login_data + + # async def refresh_tokens(self) -> bool: + # """Refresh tokens - DEPRECATED NOW BY AWS TOKEN MANAGEMENT.""" + # url = self.urls["refresh"] + # if self.session is None: + # raise NoSessionError from None + # tokens = self.session.tokens.token_data + # jsc = json.dumps({str(i): str(t) for i, t in tokens.items()}) + # try: + # await self.request("POST", url, data=jsc) + # if self.json_return["original"] == 200: + # info = self.json_return["parsed"] + # if "token" in info: + # await self.session.update_tokens(info) + # self.base_url = info["platform"]["endpoint"] + # self.camera_base_url = info["platform"]["cameraPlatform"] + # return True + # except (ConnectionError, OSError, RuntimeError, ZeroDivisionError): + # await self.error() + + # return self.json_return + + async def get_all(self): """Build and query all endpoint.""" json_return = {} url = self.urls["all"] @@ -152,10 +151,10 @@ async def getAll(self): return json_return - async def getAlarm(self): + async def get_alarm(self): """Build and query alarm endpoint.""" json_return = {} - url = self.urls["alarm"] + self.session.config.homeID + url = self.urls["alarm"] + self.session.config.home_id try: resp = await self.request("get", url) json_return.update({"original": resp.status}) @@ -165,10 +164,10 @@ async def getAlarm(self): return json_return - async def getCameraImage(self, device): + async def get_camera_image(self, device): """Build and query alarm endpoint.""" json_return = {} - url = self.urls["cameraImages"].format(device["props"]["hardwareIdentifier"]) + url = self.urls["camera_images"].format(device["props"]["hardwareIdentifier"]) try: resp = await self.request("get", url, True) json_return.update({"original": resp.status}) @@ -178,23 +177,23 @@ async def getCameraImage(self, device): return json_return - async def getCameraRecording(self, device, eventId): + async def get_camera_recording(self, device, event_id): """Build and query alarm endpoint.""" json_return = {} - url = self.urls["cameraRecordings"].format( - device["props"]["hardwareIdentifier"], eventId + url = self.urls["camera_recordings"].format( + device["props"]["hardwareIdentifier"], event_id ) try: resp = await self.request("get", url, True) - recUrl = await resp.text() + rec_url = await resp.text() json_return.update({"original": resp.status}) - json_return.update({"parsed": recUrl.split("\n")[3]}) + json_return.update({"parsed": rec_url.split("\n")[3]}) except (OSError, RuntimeError, ZeroDivisionError): await self.error() return json_return - async def getDevices(self): + async def get_devices(self): """Call the get devices endpoint.""" json_return = {} url = self.urls["devices"] @@ -207,7 +206,7 @@ async def getDevices(self): return json_return - async def getProducts(self): + async def get_products(self): """Call the get products endpoint.""" json_return = {} url = self.urls["products"] @@ -220,7 +219,7 @@ async def getProducts(self): return json_return - async def getActions(self): + async def get_actions(self): """Call the get actions endpoint.""" json_return = {} url = self.urls["actions"] @@ -233,7 +232,7 @@ async def getActions(self): return json_return - async def motionSensor(self, sensor, fromepoch, toepoch): + async def motion_sensor(self, sensor, fromepoch, toepoch): """Call a way to get motion sensor info.""" json_return = {} url = ( @@ -257,7 +256,7 @@ async def motionSensor(self, sensor, fromepoch, toepoch): return json_return - async def getWeather(self, weather_url): + async def get_weather(self, weather_url): """Call endpoint to get local weather from Hive API.""" json_return = {} t_url = self.urls["weather"] + weather_url @@ -271,7 +270,7 @@ async def getWeather(self, weather_url): return json_return - async def setState(self, n_type, n_id, **kwargs): + async def set_state(self, n_type, n_id, **kwargs): """Set the state of a Device.""" json_return = {} jsc = ( @@ -284,19 +283,19 @@ async def setState(self, n_type, n_id, **kwargs): url = self.urls["nodes"].format(n_type, n_id) try: - await self.isFileBeingUsed() + await self.is_file_being_used() resp = await self.request("post", url, data=jsc) json_return["original"] = resp.status json_return["parsed"] = await resp.json(content_type=None) except (FileInUse, OSError, RuntimeError, ConnectionError) as e: if e.__class__.__name__ == "FileInUse": return {"original": "file"} - else: - await self.error() + + await self.error() return json_return - async def setAlarm(self, **kwargs): + async def set_alarm(self, **kwargs): """Set the state of the alarm.""" json_return = {} jsc = ( @@ -307,32 +306,32 @@ async def setAlarm(self, **kwargs): + "}" ) - url = f"{self.urls['alarm']}{self.session.config.homeID}" + url = f"{self.urls['alarm']}{self.session.config.home_id}" try: - await self.isFileBeingUsed() + await self.is_file_being_used() resp = await self.request("post", url, data=jsc) json_return["original"] = resp.status json_return["parsed"] = await resp.json(content_type=None) except (FileInUse, OSError, RuntimeError, ConnectionError) as e: if e.__class__.__name__ == "FileInUse": return {"original": "file"} - else: - await self.error() + + await self.error() return json_return - async def setAction(self, n_id, data): + async def set_action(self, n_id, data): """Set the state of a Action.""" jsc = data url = self.urls["actions"] + "/" + n_id try: - await self.isFileBeingUsed() + await self.is_file_being_used() await self.request("put", url, data=jsc) except (FileInUse, OSError, RuntimeError, ConnectionError) as e: if e.__class__.__name__ == "FileInUse": return {"original": "file"} - else: - await self.error() + + await self.error() return self.json_return @@ -340,7 +339,20 @@ async def error(self): """An error has occurred iteracting with the Hive API.""" raise web_exceptions.HTTPError - async def isFileBeingUsed(self): + async def is_file_being_used(self): """Check if running in file mode.""" if self.session.config.file: raise FileInUse() + + async def close(self): + """Close the aiohttp session.""" + if self.websession and not self.websession.closed: + await self.websession.close() + + async def __aenter__(self): + """Async enter.""" + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async exit.""" + await self.close() diff --git a/pyhiveapi/apyhiveapi/api/hive_auth.py b/src/apyhiveapi/api/hive_auth.py similarity index 99% rename from pyhiveapi/apyhiveapi/api/hive_auth.py rename to src/apyhiveapi/api/hive_auth.py index e4157ba..96e7abb 100644 --- a/pyhiveapi/apyhiveapi/api/hive_auth.py +++ b/src/apyhiveapi/api/hive_auth.py @@ -113,7 +113,7 @@ def __init__( self.use_file = bool(self.username == "use@file.com") self.file_response = {"AuthenticationResult": {"AccessToken": "file"}} self.api = HiveApi() - self.data = self.api.getLoginInfo() + self.data = self.api.get_login_info() self.__pool_id = self.data.get("UPID") self.__client_id = self.data.get("CLIID") self.__region = self.data.get("REGION").split("_")[0] diff --git a/pyhiveapi/apyhiveapi/api/hive_auth_async.py b/src/apyhiveapi/api/hive_auth_async.py similarity index 98% rename from pyhiveapi/apyhiveapi/api/hive_auth_async.py rename to src/apyhiveapi/api/hive_auth_async.py index 33df18d..0645b0b 100644 --- a/pyhiveapi/apyhiveapi/api/hive_auth_async.py +++ b/src/apyhiveapi/api/hive_auth_async.py @@ -17,6 +17,7 @@ from ..helper.hive_exceptions import ( HiveApiError, + HiveFailedToRefreshTokens, HiveInvalid2FACode, HiveInvalidDeviceAuthentication, HiveInvalidPassword, @@ -57,6 +58,7 @@ class HiveAuthAsync: SMS_MFA_CHALLENGE = "SMS_MFA" DEVICE_VERIFIER_CHALLENGE = "DEVICE_SRP_AUTH" + # pylint: disable=too-many-arguments def __init__( self, username: str, @@ -100,7 +102,7 @@ def __init__( async def async_init(self): """Initialise async variables.""" - self.data = await self.loop.run_in_executor(None, self.api.getLoginInfo) + self.data = await self.loop.run_in_executor(None, self.api.get_login_info) self.__pool_id = self.data.get("UPID") self.__client_id = self.data.get("CLIID") self.__region = self.data.get("REGION").split("_")[0] @@ -218,7 +220,7 @@ async def generate_hash_device(self, device_group_key, device_key): self.device_password = device_password return device_secret_verifier_config - async def get_device_authentication_key( + async def get_device_authentication_key( # pylint: disable=too-many-arguments self, device_group_key, device_key, device_password, server_b_value, salt ): """Get device authentication key.""" @@ -559,6 +561,12 @@ async def refresh_token(self, token): "NotAuthorizedException", "CodeMismatchException", ): + error_message = err.response.get("Error", {}).get("Message", "") + if any( + msg in error_message + for msg in ["Refresh Token has expired", "Invalid Refresh Token"] + ): + raise HiveFailedToRefreshTokens from err raise HiveInvalid2FACode from err except botocore.exceptions.EndpointConnectionError as err: if err.__class__.__name__ == "EndpointConnectionError": diff --git a/src/apyhiveapi/camera.py b/src/apyhiveapi/camera.py new file mode 100644 index 0000000..42e2c0f --- /dev/null +++ b/src/apyhiveapi/camera.py @@ -0,0 +1,185 @@ +"""Hive Camera Module.""" + + +class HiveCamera: + """Hive camera. + + Returns: + object: Hive camera + """ + + camera_type = "Camera" + + async def get_camera_temperature(self, device: dict): + """Get the camera state. + + Returns: + boolean: True/False if camera is on. + """ + state = None + + try: + data = self.session.data.devices[device["hive_id"]] + state = data["props"]["temperature"] + except KeyError as e: + await self.session.log.error(e) + + return state + + async def get_camera_state(self, device: dict) -> bool: + """Get the camera state. + + Returns: + bool: True/False if camera is on. + """ + state = False + + try: + data = self.session.data.devices[device["hive_id"]] + state = data["state"]["mode"] == "ARMED" + except KeyError as e: + await self.session.log.error(e) + + return state + + async def get_camera_image_url(self, device: dict): + """Get the camera image url. + + Returns: + str: image url. + """ + state = None + + try: + state = self.session.data.camera[device["hive_id"]]["camera_image"][ + "thumbnailUrls" + ][0] + except KeyError as e: + await self.session.log.error(e) + + return state + + async def get_camera_recording_url(self, device: dict): + """Get the camera recording url. + + Returns: + str: image url. + """ + state = None + + try: + state = self.session.data.camera[device["hive_id"]]["camera_recording"] + except KeyError as e: + await self.session.log.error(e) + + return state + + async def set_camera_on(self, device: dict, mode: str): + """Set the camera state to on. + + Args: + device (dict): Camera device. + + Returns: + boolean: True/False if successful. + """ + final = False + + if ( + device["hive_id"] in self.session.data.devices + and device["device_data"]["online"] + ): + await self.session.hive_refresh_tokens() + resp = await self.session.api.set_state(mode=mode) + if resp["original"] == 200: + final = True + await self.session.get_camera() + + return final + + async def set_camera_off(self, device: dict, mode: str): + """Set the camera state to on. + + Args: + device (dict): Camera device. + + Returns: + boolean: True/False if successful. + """ + final = False + + if ( + device["hive_id"] in self.session.data.devices + and device["device_data"]["online"] + ): + await self.session.hive_refresh_tokens() + resp = await self.session.api.set_state(mode=mode) + if resp["original"] == 200: + final = True + await self.session.get_camera() + + return final + + +class Camera(HiveCamera): + """Home assistant camera. + + Args: + HiveCamera (object): Class object. + """ + + def __init__(self, session: object = None): + """Initialise camera. + + Args: + session (object, optional): Used to interact with the hive account. Defaults to None. + """ + self.session = session + + async def get_camera(self, device: dict): + """Get camera data. + + Args: + device (dict): Device to update. + + Returns: + dict: Updated device. + """ + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} + ) + dev_data = {} + + if device["device_data"]["online"]: + self.session.helper.device_recovered(device["device_id"]) + data = self.session.data.devices[device["device_id"]] + dev_data = { + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], + "device_id": device["device_id"], + "device_name": device["device_name"], + "status": { + "temperature": await self.get_camera_temperature(device), + "state": await self.get_camera_state(device), + "image_url": await self.get_camera_image_url(device), + "recording_url": await self.get_camera_recording_url(device), + }, + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), + "custom": device.get("custom", None), + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] + ), + } + + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device diff --git a/pyhiveapi/apyhiveapi/device_attributes.py b/src/apyhiveapi/device_attributes.py similarity index 79% rename from pyhiveapi/apyhiveapi/device_attributes.py rename to src/apyhiveapi/device_attributes.py index 7821181..b4a1053 100644 --- a/pyhiveapi/apyhiveapi/device_attributes.py +++ b/src/apyhiveapi/device_attributes.py @@ -1,5 +1,5 @@ """Hive Device Attribute Module.""" -# pylint: skip-file + from .helper.const import HIVETOHA from .helper.logger import Logger @@ -15,9 +15,9 @@ def __init__(self, session: object = None): """ self.session = session self.session.log = Logger() - self.type = "Attribute" + self.data_type = "Attribute" - async def stateAttributes(self, n_id: str, _type: str): + async def state_attributes(self, n_id: str, _type: str): """Get HA State Attributes. Args: @@ -30,16 +30,16 @@ async def stateAttributes(self, n_id: str, _type: str): attr = {} if n_id in self.session.data.products or n_id in self.session.data.devices: - attr.update({"available": (await self.onlineOffline(n_id))}) + attr.update({"available": (await self.online_offline(n_id))}) if n_id in self.session.config.battery: - battery = await self.getBattery(n_id) + battery = await self.get_battery(n_id) if battery is not None: attr.update({"battery": str(battery) + "%"}) if n_id in self.session.config.mode: - attr.update({"mode": (await self.getMode(n_id))}) + attr.update({"mode": (await self.get_mode(n_id))}) return attr - async def onlineOffline(self, n_id: str): + async def online_offline(self, n_id: str): """Check if device is online. Args: @@ -58,7 +58,7 @@ async def onlineOffline(self, n_id: str): return state - async def getMode(self, n_id: str): + async def get_mode(self, n_id: str): """Get sensor mode. Args: @@ -73,13 +73,13 @@ async def getMode(self, n_id: str): try: data = self.session.data.products[n_id] state = data["state"]["mode"] - final = HIVETOHA[self.type].get(state, state) + final = HIVETOHA[self.data_type].get(state, state) except KeyError as e: await self.session.log.error(e) return final - async def getBattery(self, n_id: str): + async def get_battery(self, n_id: str): """Get device battery level. Args: @@ -95,7 +95,7 @@ async def getBattery(self, n_id: str): data = self.session.data.devices[n_id] state = data["props"]["battery"] final = state - await self.session.log.errorCheck(n_id, self.type, state) + await self.session.log.error_check(n_id, state) except KeyError as e: await self.session.log.error(e) diff --git a/pyhiveapi/apyhiveapi/heating.py b/src/apyhiveapi/heating.py similarity index 55% rename from pyhiveapi/apyhiveapi/heating.py rename to src/apyhiveapi/heating.py index 95b67c0..697deba 100644 --- a/pyhiveapi/apyhiveapi/heating.py +++ b/src/apyhiveapi/heating.py @@ -1,7 +1,8 @@ """Hive Heating Module.""" -# pylint: skip-file -from .helper.const import HIVETOHA +from datetime import datetime + +from .helper.const import HIVETOHA class HiveHeating: """Hive Heating Code. @@ -12,7 +13,7 @@ class HiveHeating: heatingType = "Heating" - async def getMinTemperature(self, device: dict): + async def get_min_temperature(self, device: dict): """Get heating minimum target temperature. Args: @@ -21,11 +22,11 @@ async def getMinTemperature(self, device: dict): Returns: int: Minimum temperature """ - if device["hiveType"] == "nathermostat": - return self.session.data.products[device["hiveID"]]["props"]["minHeat"] + if device["hive_type"] == "nathermostat": + return self.session.data.products[device["hive_id"]]["props"]["minHeat"] return 5 - async def getMaxTemperature(self, device: dict): + async def get_max_temperature(self, device: dict): """Get heating maximum target temperature. Args: @@ -34,11 +35,11 @@ async def getMaxTemperature(self, device: dict): Returns: int: Maximum temperature """ - if device["hiveType"] == "nathermostat": - return self.session.data.products[device["hiveID"]]["props"]["maxHeat"] + if device["hive_type"] == "nathermostat": + return self.session.data.products[device["hive_id"]]["props"]["maxHeat"] return 32 - async def getCurrentTemperature(self, device: dict): + async def get_current_temperature(self, device: dict): """Get heating current temperature. Args: @@ -47,38 +48,37 @@ async def getCurrentTemperature(self, device: dict): Returns: float: current temperature """ - from datetime import datetime f_state = None state = None final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["temperature"] - if device["hiveID"] in self.session.data.minMax: - if self.session.data.minMax[device["hiveID"]]["TodayDate"] == str( + if device["hive_id"] in self.session.data.min_max: + if self.session.data.min_max[device["hive_id"]]["TodayDate"] == str( datetime.date(datetime.now()) ): - if state < self.session.data.minMax[device["hiveID"]]["TodayMin"]: - self.session.data.minMax[device["hiveID"]]["TodayMin"] = state + if state < self.session.data.min_max[device["hive_id"]]["TodayMin"]: + self.session.data.min_max[device["hive_id"]]["TodayMin"] = state - if state > self.session.data.minMax[device["hiveID"]]["TodayMax"]: - self.session.data.minMax[device["hiveID"]]["TodayMax"] = state + if state > self.session.data.min_max[device["hive_id"]]["TodayMax"]: + self.session.data.min_max[device["hive_id"]]["TodayMax"] = state else: data = { "TodayMin": state, "TodayMax": state, "TodayDate": str(datetime.date(datetime.now())), } - self.session.data.minMax[device["hiveID"]].update(data) + self.session.data.min_max[device["hive_id"]].update(data) - if state < self.session.data.minMax[device["hiveID"]]["RestartMin"]: - self.session.data.minMax[device["hiveID"]]["RestartMin"] = state + if state < self.session.data.min_max[device["hive_id"]]["RestartMin"]: + self.session.data.min_max[device["hive_id"]]["RestartMin"] = state - if state > self.session.data.minMax[device["hiveID"]]["RestartMax"]: - self.session.data.minMax[device["hiveID"]]["RestartMax"] = state + if state > self.session.data.min_max[device["hive_id"]]["RestartMax"]: + self.session.data.min_max[device["hive_id"]]["RestartMax"] = state else: data = { "TodayMin": state, @@ -87,7 +87,7 @@ async def getCurrentTemperature(self, device: dict): "RestartMin": state, "RestartMax": state, } - self.session.data.minMax[device["hiveID"]] = data + self.session.data.min_max[device["hive_id"]] = data f_state = round(float(state), 1) final = f_state @@ -96,7 +96,7 @@ async def getCurrentTemperature(self, device: dict): return final - async def getTargetTemperature(self, device: dict): + async def get_target_temperature(self, device: dict): """Get heating target temperature. Args: @@ -108,7 +108,7 @@ async def getTargetTemperature(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = float(data["state"].get("target", None)) state = float(data["state"].get("heat", state)) except (KeyError, TypeError) as e: @@ -116,7 +116,7 @@ async def getTargetTemperature(self, device: dict): return state - async def getMode(self, device: dict): + async def get_mode(self, device: dict): """Get heating current mode. Args: @@ -129,7 +129,7 @@ async def getMode(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["mode"] if state == "BOOST": state = data["props"]["previous"]["mode"] @@ -139,7 +139,7 @@ async def getMode(self, device: dict): return final - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get heating current state. Args: @@ -152,8 +152,8 @@ async def getState(self, device: dict): final = None try: - current_temp = await self.getCurrentTemperature(device) - target_temp = await self.getTargetTemperature(device) + current_temp = await self.get_current_temperature(device) + target_temp = await self.get_target_temperature(device) if current_temp < target_temp: state = "ON" else: @@ -164,7 +164,7 @@ async def getState(self, device: dict): return final - async def getCurrentOperation(self, device: dict): + async def get_current_operation(self, device: dict): """Get heating current operation. Args: @@ -176,14 +176,14 @@ async def getCurrentOperation(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["working"] except KeyError as e: await self.session.log.error(e) return state - async def getBoostStatus(self, device: dict): + async def get_boost_status(self, device: dict): """Get heating boost current status. Args: @@ -195,14 +195,14 @@ async def getBoostStatus(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = HIVETOHA["Boost"].get(data["state"].get("boost", False), "ON") except KeyError as e: await self.session.log.error(e) return state - async def getBoostTime(self, device: dict): + async def get_boost_time(self, device: dict): """Get heating boost time remaining. Args: @@ -211,11 +211,11 @@ async def getBoostTime(self, device: dict): Returns: str: Boost time. """ - if await self.getBoostStatus(device) == "ON": + if await self.get_boost_status(device) == "ON": state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["boost"] except KeyError as e: await self.session.log.error(e) @@ -223,7 +223,7 @@ async def getBoostTime(self, device: dict): return state return None - async def getHeatOnDemand(self, device): + async def get_heat_on_demand(self, device): """Get heat on demand status. Args: @@ -235,7 +235,7 @@ async def getHeatOnDemand(self, device): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["autoBoost"]["active"] except KeyError as e: await self.session.log.error(e) @@ -243,7 +243,7 @@ async def getHeatOnDemand(self, device): return state @staticmethod - async def getOperationModes(): + async def get_operation_modes(): """Get heating list of possible modes. Returns: @@ -251,7 +251,7 @@ async def getOperationModes(): """ return ["SCHEDULE", "MANUAL", "OFF"] - async def setTargetTemperature(self, device: dict, new_temp: str): + async def set_target_temperature(self, device: dict, new_temp: str): """Set heating target temperature. Args: @@ -261,26 +261,26 @@ async def setTargetTemperature(self, device: dict, new_temp: str): Returns: boolean: True/False if successful """ - await self.session.hiveRefreshTokens() + await self.session.hive_refresh_tokens() final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], target=new_temp + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], target=new_temp ) if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices(device["hive_id"]) final = True return final - async def setMode(self, device: dict, new_mode: str): + async def set_mode(self, device: dict, new_mode: str): """Set heating mode. Args: @@ -290,25 +290,25 @@ async def setMode(self, device: dict, new_mode: str): Returns: boolean: True/False if successful """ - await self.session.hiveRefreshTokens() + await self.session.hive_refresh_tokens() final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], mode=new_mode + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], mode=new_mode ) if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices(device["hive_id"]) final = True return final - async def setBoostOn(self, device: dict, mins: str, temp: float): + async def set_boost_on(self, device: dict, mins: str, temp: float): """Turn heating boost on. Args: @@ -319,32 +319,32 @@ async def setBoostOn(self, device: dict, mins: str, temp: float): Returns: boolean: True/False if successful """ - if int(mins) > 0 and int(temp) >= await self.getMinTemperature(device): - if int(temp) <= await self.getMaxTemperature(device): - await self.session.hiveRefreshTokens() + if int(mins) > 0 and int(temp) >= await self.get_min_temperature(device): + if int(temp) <= await self.get_max_temperature(device): + await self.session.hive_refresh_tokens() final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], mode="BOOST", boost=mins, target=temp, ) if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices(device["hive_id"]) final = True return final return None - async def setBoostOff(self, device: dict): + async def set_boost_off(self, device: dict): """Turn heating boost off. Args: @@ -356,33 +356,33 @@ async def setBoostOff(self, device: dict): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - await self.session.getDevices(device["hiveID"]) - if await self.getBoostStatus(device) == "ON": + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + await self.session.get_devices(device["hive_id"]) + if await self.get_boost_status(device) == "ON": prev_mode = data["props"]["previous"]["mode"] - if prev_mode == "MANUAL" or prev_mode == "OFF": + if prev_mode in ("MANUAL", "OFF"): pre_temp = data["props"]["previous"].get("target", 7) - resp = await self.session.api.setState( + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], mode=prev_mode, target=pre_temp, ) else: - resp = await self.session.api.setState( - data["type"], device["hiveID"], mode=prev_mode + resp = await self.session.api.set_state( + data["type"], device["hive_id"], mode=prev_mode ) if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices(device["hive_id"]) final = True return final - async def setHeatOnDemand(self, device: dict, state: str): + async def set_heat_on_demand(self, device: dict, state: str): """Enable or disable Heat on Demand for a Thermostat. Args: @@ -395,17 +395,17 @@ async def setHeatOnDemand(self, device: dict, state: str): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - data = self.session.data.products[device["hiveID"]] - await self.session.hiveRefreshTokens() - resp = await self.session.api.setState( - data["type"], device["hiveID"], autoBoost=state + data = self.session.data.products[device["hive_id"]] + await self.session.hive_refresh_tokens() + resp = await self.session.api.set_state( + data["type"], device["hive_id"], autoBoost=state ) if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices(device["hive_id"]) final = True return final @@ -426,7 +426,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getClimate(self, device: dict): + async def get_climate(self, device: dict): """Get heating data. Args: @@ -435,48 +435,48 @@ async def getClimate(self, device: dict): Returns: dict: Updated device. """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} ) - if device["deviceData"]["online"]: + if device["device_data"]["online"]: dev_data = {} - self.session.helper.deviceRecovered(device["device_id"]) + self.session.helper.device_recovered(device["device_id"]) data = self.session.data.devices[device["device_id"]] dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], "device_id": device["device_id"], "device_name": device["device_name"], "temperatureunit": device["temperatureunit"], - "min_temp": await self.getMinTemperature(device), - "max_temp": await self.getMaxTemperature(device), + "min_temp": await self.get_min_temperature(device), + "max_temp": await self.get_max_temperature(device), "status": { - "current_temperature": await self.getCurrentTemperature(device), - "target_temperature": await self.getTargetTemperature(device), - "action": await self.getCurrentOperation(device), - "mode": await self.getMode(device), - "boost": await self.getBoostStatus(device), + "current_temperature": await self.get_current_temperature(device), + "target_temperature": await self.get_target_temperature(device), + "action": await self.get_current_operation(device), + "mode": await self.get_mode(device), + "boost": await self.get_boost_status(device), }, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), "custom": device.get("custom", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] ), } - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device - async def getScheduleNowNextLater(self, device: dict): + async def get_schedule_now_next_later(self, device: dict): """Hive get heating schedule now, next and later. Args: @@ -485,20 +485,20 @@ async def getScheduleNowNextLater(self, device: dict): Returns: dict: Schedule now, next and later """ - online = await self.session.attr.onlineOffline(device["device_id"]) - current_mode = await self.getMode(device) + online = await self.session.attr.online_offline(device["device_id"]) + current_mode = await self.get_mode(device) state = None try: if online and current_mode == "SCHEDULE": - data = self.session.data.products[device["hiveID"]] - state = self.session.helper.getScheduleNNL(data["state"]["schedule"]) + data = self.session.data.products[device["hive_id"]] + state = self.session.helper.get_schedule_nnl(data["state"]["schedule"]) except KeyError as e: await self.session.log.error(e) return state - async def minmaxTemperature(self, device: dict): + async def min_max_temperature(self, device: dict): """Min/Max Temp. Args: @@ -511,7 +511,7 @@ async def minmaxTemperature(self, device: dict): final = None try: - state = self.session.data.minMax[device["hiveID"]] + state = self.session.data.min_max[device["hive_id"]] final = state except KeyError as e: await self.session.log.error(e) diff --git a/pyhiveapi/apyhiveapi/helper/__init__.py b/src/apyhiveapi/helper/__init__.py similarity index 85% rename from pyhiveapi/apyhiveapi/helper/__init__.py rename to src/apyhiveapi/helper/__init__.py index f86f509..1e9a4c2 100644 --- a/pyhiveapi/apyhiveapi/helper/__init__.py +++ b/src/apyhiveapi/helper/__init__.py @@ -1,4 +1,4 @@ """__init__.py file.""" -# pylint: skip-file + from .hive_helper import HiveHelper # noqa: F401 from .logger import Logger # noqa: F401 diff --git a/src/apyhiveapi/helper/const.py b/src/apyhiveapi/helper/const.py new file mode 100644 index 0000000..79c25c5 --- /dev/null +++ b/src/apyhiveapi/helper/const.py @@ -0,0 +1,172 @@ +# pylint: skip-file +"""Constants for Pyhiveapi.""" + +SYNC_PACKAGE_NAME = "pyhiveapi" +SYNC_PACKAGE_DIR = "/pyhiveapi/" +ASYNC_PACKAGE_NAME = "apyhiveapi" +ASYNC_PACKAGE_DIR = "/apyhiveapi/" +SMS_REQUIRED = "SMS_MFA" + + +# HTTP return codes. +HTTP_OK = 200 +HTTP_CREATED = 201 +HTTP_ACCEPTED = 202 +HTTP_MOVED_PERMANENTLY = 301 +HTTP_BAD_REQUEST = 400 +HTTP_UNAUTHORIZED = 401 +HTTP_FORBIDDEN = 403 +HTTP_NOT_FOUND = 404 +HTTP_METHOD_NOT_ALLOWED = 405 +HTTP_UNPROCESSABLE_ENTITY = 422 +HTTP_TOO_MANY_REQUESTS = 429 +HTTP_INTERNAL_SERVER_ERROR = 500 +HTTP_BAD_GATEWAY = 502 +HTTP_SERVICE_UNAVAILABLE = 503 + + +HIVETOHA = { + "Alarm": {"home": "armed_home", "away": "armed_away", "asleep": "armed_night"}, + "Attribute": {True: "Online", False: "Offline"}, + "Boost": {None: "OFF", False: "OFF"}, + "Heating": {False: "OFF", "ENABLED": True, "DISABLED": False}, + "Hotwater": {"MANUAL": "ON", None: "OFF", False: "OFF"}, + "Hub": { + "Status": {True: 1, False: 0}, + "Smoke": {True: 1, False: 0}, + "Dog": {True: 1, False: 0}, + "Glass": {True: 1, False: 0}, + }, + "Light": {"ON": True, "OFF": False}, + "Sensor": { + "OPEN": True, + "CLOSED": False, + True: "Online", + False: "Offline", + }, + "Switch": {"ON": True, "OFF": False}, +} + +HIVE_TYPES = { + "Hub": ["hub", "sense"], + "Thermo": ["thermostatui", "trv"], + "Heating": ["heating", "trvcontrol"], + "Hotwater": ["hotwater"], + "Light": ["warmwhitelight", "tuneablelight", "colourtuneablelight"], + "Sensor": ["motionsensor", "contactsensor"], + "Switch": ["activeplug"], +} +sensor_commands = { + "smoke_co": "self.session.hub.get_smoke_status(device)", + "dog_bark": "self.session.hub.get_dog_bark_status(device)", + "glass_break": "self.session.hub.get_glass_break_status(device)", + "camera_temp": "self.session.camera.get_camera_temperature(device)", + "current_temperature": "self.session.heating.get_current_temperature(device)", + "heating_current_temperature": "self.session.heating.get_current_temperature(device)", + "heating_target_temperature": "self.session.heating.get_target_temperature(device)", + "heating_state": "self.session.heating.get_state(device)", + "heating_mode": "self.session.heating.get_mode(device)", + "heating_boost": "self.session.heating.get_boost_status(device)", + "hotwater_state": "self.session.hotwater.get_state(device)", + "hotwater_mode": "self.session.hotwater.get_mode(device)", + "hotwater_boost": "self.session.hotwater.get_boost(device)", + "battery": 'self.session.attr.get_battery(device["device_id"])', + "mode": 'self.session.attr.get_mode(device["hive_id"])', + "availability": "self.online(device)", + "connectivity": "self.online(device)", + "power": "self.session.switch.get_power_usage(device)", +} + +PRODUCTS = { + "sense": [ + 'add_list("binary_sensor", p, ha_name="Glass Detection", hive_type="GLASS_BREAK")', + 'add_list("binary_sensor", p, ha_name="Smoke Detection", hive_type="SMOKE_CO")', + 'add_list("binary_sensor", p, ha_name="Dog Bark Detection", hive_type="DOG_BARK")', + ], + "heating": [ + 'add_list("climate", p, temperature_unit=self.data["user"]["temperature_unit"])', + 'add_list("switch", p, ha_name=" Heat on Demand", hive_type="heating_heat_on_demand", category="config")', + 'add_list("sensor", p, ha_name=" Current Temperature", hive_type="heating_current_temperature", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Target Temperature", hive_type="heating_target_temperature", category="diagnostic")', + 'add_list("sensor", p, ha_name=" State", hive_type="heating_state", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Mode", hive_type="heating_mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Boost", hive_type="heating_boost", category="diagnostic")', + ], + "trvcontrol": [ + 'add_list("climate", p, temperature_unit=self.data["user"]["temperature_unit"])', + 'add_list("sensor", p, ha_name=" Current Temperature", hive_type="heating_current_temperature", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Target Temperature", hive_type="heating_target_temperature", category="diagnostic")', + 'add_list("sensor", p, ha_name=" State", hive_type="heating_state", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Mode", hive_type="heating_mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Boost", hive_type="heating_boost", category="diagnostic")', + ], + "hotwater": [ + 'add_list("water_heater", p,)', + 'add_list("sensor", p, ha_name="Hotwater State", hive_type="hotwater_state", category="diagnostic")', + 'add_list("sensor", p, ha_name="Hotwater Mode", hive_type="hotwater_mode", category="diagnostic")', + 'add_list("sensor", p, ha_name="Hotwater Boost", hive_type="hotwater_boost", category="diagnostic")', + ], + "activeplug": [ + 'add_list("switch", p)', + 'add_list("sensor", p, ha_name=" Mode", hive_type="mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Availability", hive_type="availability", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Power", hive_type="power", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Energy", hive_type="energy", category="diagnostic")', + ], + "warmwhitelight": [ + 'add_list("light", p)', + 'add_list("sensor", p, ha_name=" Mode", hive_type="mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + "tuneablelight": [ + 'add_list("light", p)', + 'add_list("sensor", p, ha_name=" Mode", hive_type="mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + "colourtuneablelight": [ + 'add_list("light", p)', + 'add_list("sensor", p, ha_name=" Mode", hive_type="mode", category="diagnostic")', + 'add_list("sensor", p, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + # "hivecamera": [ + # 'add_list("camera", p)', + # 'add_list("sensor", p, ha_name=" Mode", hive_type="mode", category="diagnostic")', + # 'add_list("sensor", p, ha_name=" Availability", hive_type="availability", category="diagnostic")', + # 'add_list("sensor", p, ha_name=" Temperature", hive_type="camera_temp", category="diagnostic")', + # ], + "motionsensor": [ + 'add_list("binary_sensor", p)', + 'add_list("sensor", p, ha_name=" Current Temperature", hive_type="current_temperature", category="diagnostic")', + ], + "contactsensor": ['add_list("binary_sensor", p)'], +} + +DEVICES = { + "contactsensor": [ + 'add_list("sensor", d, ha_name=" Battery Level", hive_type="battery", category="diagnostic")', + 'add_list("sensor", d, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + "hub": [ + 'add_list("binary_sensor", d, ha_name="Hive Hub Status", hive_type="connectivity", category="diagnostic")', + ], + "motionsensor": [ + 'add_list("sensor", d, ha_name=" Battery Level", hive_type="battery", category="diagnostic")', + 'add_list("sensor", d, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + "sense": [ + 'add_list("binary_sensor", d, ha_name="Hive Hub Status", hive_type="connectivity")', + ], + "siren": ['add_list("alarm_control_panel", d)'], + "thermostatui": [ + 'add_list("sensor", d, ha_name=" Battery Level", hive_type="battery", category="diagnostic")', + 'add_list("sensor", d, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], + "trv": [ + 'add_list("sensor", d, ha_name=" Battery Level", hive_type="battery", category="diagnostic")', + 'add_list("sensor", d, ha_name=" Availability", hive_type="availability", category="diagnostic")', + ], +} + +ACTIONS = ( + 'add_list("switch", a, hive_name=a["name"], ha_name=a["name"], hive_type="action")' +) \ No newline at end of file diff --git a/pyhiveapi/apyhiveapi/helper/debugger.py b/src/apyhiveapi/helper/debugger.py similarity index 66% rename from pyhiveapi/apyhiveapi/helper/debugger.py rename to src/apyhiveapi/helper/debugger.py index 526d977..b7ee496 100644 --- a/pyhiveapi/apyhiveapi/helper/debugger.py +++ b/src/apyhiveapi/helper/debugger.py @@ -1,45 +1,57 @@ """Debugger file.""" -# pylint: skip-file + import logging +import sys class DebugContext: """Debug context to trace any function calls inside the context.""" - def __init__(self, name, enabled): + def __init__(self, name: str, enabled: bool): """Initialise debugger.""" self.name = name self.enabled = enabled self.logging = logging.getLogger(__name__) - self.debugOutFolder = "" - self.debugOutFile = "" - self.debugEnabled = False - self.debugList = [] + self.debug_out_folder = "" + self.debug_out_file = "" + self.debug_enabled = False + self.debug_list = [] - def __enter__(self): + def __enter__(self) -> "DebugContext": """Set trace calls on entering debugger.""" print("Entering Debug Decorated func") # Set the trace function to the trace_calls function # So all events are now traced - self.traceCalls + sys.settrace(self.trace_calls) + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + """Set trace calls on exiting debugger.""" + print("Exiting Debug Decorated func") + sys.settrace(None) - def traceCalls(self, frame, event, arg): + def trace_calls(self, frame, event, arg): """Trace calls be made.""" # We want to only trace our call to the decorated function + if arg is not None: + pass + if event != "call": - return - elif frame.f_code.co_name != self.name: - return + return None + if frame.f_code.co_name != self.name: + return None # return the trace function to use when you go into that # function call - return self.traceLines + return self.trace_lines - def traceLines(self, frame, event, arg): + def trace_lines(self, frame, event, arg): """Print out lines for function.""" # If you want to print local variables each line # keep the check for the event 'line' # If you want to print local variables only on return # check only for the 'return' event + if arg is not None: + pass if event not in ["line", "return"]: return co = frame.f_code diff --git a/pyhiveapi/apyhiveapi/helper/hive_exceptions.py b/src/apyhiveapi/helper/hive_exceptions.py similarity index 91% rename from pyhiveapi/apyhiveapi/helper/hive_exceptions.py rename to src/apyhiveapi/helper/hive_exceptions.py index 67bd0ae..8f6309e 100644 --- a/pyhiveapi/apyhiveapi/helper/hive_exceptions.py +++ b/src/apyhiveapi/helper/hive_exceptions.py @@ -1,5 +1,5 @@ """Hive exception class.""" -# pylint: skip-file + class FileInUse(Exception): @@ -80,3 +80,10 @@ class HiveFailedToRefreshTokens(Exception): Args: Exception (object): Exception object to invoke """ + +class NoSessionError(Exception): + """No session error. + + Args: + Exception (object): Exception object to invoke + """ diff --git a/pyhiveapi/apyhiveapi/helper/hive_helper.py b/src/apyhiveapi/helper/hive_helper.py similarity index 62% rename from pyhiveapi/apyhiveapi/helper/hive_helper.py rename to src/apyhiveapi/helper/hive_helper.py index 7943cd7..3000632 100644 --- a/pyhiveapi/apyhiveapi/helper/hive_helper.py +++ b/src/apyhiveapi/helper/hive_helper.py @@ -1,7 +1,9 @@ """Helper class for pyhiveapi.""" -# pylint: skip-file + import datetime import operator +import timedelta + from .const import HIVE_TYPES @@ -17,7 +19,7 @@ def __init__(self, session: object = None): """ self.session = session - def getDeviceName(self, n_id: str): + def get_device_name(self, n_id: str): """Resolve a id into a name. Args: @@ -38,24 +40,24 @@ def getDeviceName(self, n_id: str): if product_name: return product_name - elif device_name: + if device_name: return device_name - elif n_id == "No_ID": + if n_id == "No_ID": return "Hive" - else: - return n_id - def deviceRecovered(self, n_id: str): + return n_id + + def device_recovered(self, n_id: str): """Register that a device has recovered from being offline. Args: n_id (str): ID of the device. """ - # name = HiveHelper.getDeviceName(n_id) - if n_id in self.session.config.errorList: - self.session.config.errorList.pop(n_id) + # name = HiveHelper.get_device_name(n_id) + if n_id in self.session.config.error_list: + self.session.config.error_list.pop(n_id) - def getDeviceFromID(self, n_id: str): + def get_device_from_id(self, n_id: str): """Get product/device data from ID. Args: @@ -72,7 +74,7 @@ def getDeviceFromID(self, n_id: str): return data - def getDeviceData(self, product: dict): + def get_device_data(self, product: dict): """Get device from product data. Args: @@ -82,34 +84,35 @@ def getDeviceData(self, product: dict): [type]: Device data. """ device = product - type = product["type"] - if type in ("heating", "hotwater"): - for aDevice in self.session.data.devices: - if self.session.data.devices[aDevice]["type"] in HIVE_TYPES["Thermo"]: + device_type = product["type"] + if device_type in ("heating", "hotwater"): + for a_device in self.session.data.devices: + if self.session.data.devices[a_device]["type"] in HIVE_TYPES["Thermo"]: try: if ( product["props"]["zone"] - == self.session.data.devices[aDevice]["props"]["zone"] + == self.session.data.devices[a_device]["props"]["zone"] ): - device = self.session.data.devices[aDevice] + device = self.session.data.devices[a_device] except KeyError: pass - elif type == "trvcontrol": + elif device_type == "trvcontrol": trv_present = len(product["props"]["trvs"]) > 0 if trv_present: device = self.session.data.devices[product["props"]["trvs"][0]] else: raise KeyError - elif type == "warmwhitelight" and product["props"]["model"] == "SIREN001": + elif device_type == "warmwhitelight" and product["props"]["model"] == "SIREN001": device = self.session.data.devices[product["parent"]] - elif type == "sense": + elif device_type == "sense": device = self.session.data.devices[product["parent"]] else: device = self.session.data.devices[product["id"]] return device - def convertMinutesToTime(self, minutes_to_convert: str): + @staticmethod + def convert_minutes_to_time(minutes_to_convert: str) -> timedelta: """Convert minutes string to datetime. Args: @@ -125,18 +128,20 @@ def convertMinutesToTime(self, minutes_to_convert: str): converted_time_string = converted_time.strftime("%H:%M") return converted_time_string - def getScheduleNNL(self, hive_api_schedule: list): - """Get the schedule now, next and later of a given nodes schedule. + @staticmethod + def get_schedule_nnl(hive_api_schedule: list) -> dict: + """Get the schedule now, next, and later of a given node's schedule. Args: hive_api_schedule (list): Schedule to parse. Returns: - dict: Now, Next and later values. + dict: Now, Next, and later values. """ + # pylint: disable=too-many-locals, too-many-branches schedule_now_and_next = {} - date_time_now = datetime.datetime.now() - date_time_now_day_int = date_time_now.today().weekday() + now = datetime.datetime.now() + day_int = now.weekday() days_t = ( "monday", @@ -148,50 +153,46 @@ def getScheduleNNL(self, hive_api_schedule: list): "sunday", ) - days_rolling_list = list(days_t[date_time_now_day_int:] + days_t)[:7] + days_rolling_list = list(days_t[day_int:] + days_t[:day_int]) full_schedule_list = [] - for day_index in range(0, len(days_rolling_list)): - current_day_schedule = hive_api_schedule[days_rolling_list[day_index]] + for day_index, day_name in enumerate(days_rolling_list): + current_day_schedule = hive_api_schedule.get(day_name, []) current_day_schedule_sorted = sorted( current_day_schedule, key=operator.itemgetter("start"), reverse=False, ) - for current_slot in range(0, len(current_day_schedule_sorted)): - current_slot_custom = current_day_schedule_sorted[current_slot] - - slot_date = datetime.datetime.now() + datetime.timedelta(days=day_index) - slot_time = self.convertMinutesToTime(current_slot_custom["start"]) - slot_time_date_s = slot_date.strftime("%d-%m-%Y") + " " + slot_time + for current_slot_custom in current_day_schedule_sorted: + slot_date = now + datetime.timedelta(days=day_index) + slot_time = HiveHelper.convert_minutes_to_time(current_slot_custom["start"]) + slot_time_date_s = f"{slot_date.strftime('%d-%m-%Y')} {slot_time}" slot_time_date_dt = datetime.datetime.strptime( slot_time_date_s, "%d-%m-%Y %H:%M" ) - if slot_time_date_dt <= date_time_now: - slot_time_date_dt = slot_time_date_dt + datetime.timedelta(days=7) + if slot_time_date_dt <= now: + slot_time_date_dt += datetime.timedelta(days=7) current_slot_custom["Start_DateTime"] = slot_time_date_dt full_schedule_list.append(current_slot_custom) - fsl_sorted = sorted( - full_schedule_list, - key=operator.itemgetter("Start_DateTime"), - reverse=False, - ) + full_schedule_list.sort(key=operator.itemgetter("Start_DateTime")) - schedule_now = fsl_sorted[-1] - schedule_next = fsl_sorted[0] - schedule_later = fsl_sorted[1] + if len(full_schedule_list) < 3: + raise ValueError("Schedule list must contain at least three entries.") - schedule_now["Start_DateTime"] = schedule_now[ - "Start_DateTime" - ] - datetime.timedelta(days=7) + schedule_now, schedule_next, schedule_later = ( + full_schedule_list[-1], + full_schedule_list[0], + full_schedule_list[1], + ) + schedule_now["Start_DateTime"] -= datetime.timedelta(days=7) schedule_now["End_DateTime"] = schedule_next["Start_DateTime"] schedule_next["End_DateTime"] = schedule_later["Start_DateTime"] - schedule_later["End_DateTime"] = fsl_sorted[2]["Start_DateTime"] + schedule_later["End_DateTime"] = full_schedule_list[2]["Start_DateTime"] schedule_now_and_next["now"] = schedule_now schedule_now_and_next["next"] = schedule_next @@ -199,7 +200,7 @@ def getScheduleNNL(self, hive_api_schedule: list): return schedule_now_and_next - def getHeatOnDemandDevice(self, device: dict): + def get_heat_on_demand_device(self, device: dict): """Use TRV device to get the linked thermostat device. Args: @@ -208,6 +209,6 @@ def getHeatOnDemandDevice(self, device: dict): Returns: [dictionary]: [Gets the thermostat device linked to TRV.] """ - trv = self.session.data.products.get(device["HiveID"]) + trv = self.session.data.products.get(device["hive_id"]) thermostat = self.session.data.products.get(trv["state"]["zone"]) return thermostat diff --git a/pyhiveapi/apyhiveapi/helper/hivedataclasses.py b/src/apyhiveapi/helper/hivedataclasses.py similarity index 58% rename from pyhiveapi/apyhiveapi/helper/hivedataclasses.py rename to src/apyhiveapi/helper/hivedataclasses.py index ca9c8fd..c457857 100644 --- a/pyhiveapi/apyhiveapi/helper/hivedataclasses.py +++ b/src/apyhiveapi/helper/hivedataclasses.py @@ -1,5 +1,4 @@ """Device data class.""" -# pylint: skip-file from dataclasses import dataclass @@ -8,14 +7,14 @@ class Device: """Class for keeping track of an device.""" - hiveID: str - hiveName: str - hiveType: str - haType: str - deviceData: dict + hive_id: str + hive_name: str + hive_type: str + ha_type: str + device_data: dict status: dict data: dict - parentDevice: str - isGroup: bool + parent_device: str + is_group: bool device_id: str device_name: str diff --git a/src/apyhiveapi/helper/logger.py b/src/apyhiveapi/helper/logger.py new file mode 100644 index 0000000..cff56f8 --- /dev/null +++ b/src/apyhiveapi/helper/logger.py @@ -0,0 +1,38 @@ +"""Custom Logging Module.""" + +import inspect +from datetime import datetime + + +class Logger: + """Custom Logging Code.""" + + def __init__(self, session=None): + """Initialise the logger class.""" + self.session = session + + async def error(self, e: Exception = Exception("UNKNOWN")) -> None: + """Process an unexpected error.""" + self.session.logger.error( + "An unexpected error has occurred whilst " + "executing %s with exception %s %s", + inspect.stack()[1][3], + e.__class__, + e, + ) + + async def error_check(self, n_id: str, error_type: bool) -> None: + """Error has occurred.""" + message = None + name = self.session.helper.get_device_name(n_id) + + if error_type is False: + message = "Device offline could not update entity - " + name + if n_id not in self.session.config.error_list: + self.session.logger.warning(message) + self.session.config.error_list.update({n_id: datetime.now()}) + elif error_type == "Failed": + message = "ERROR - No data found for device - " + name + if n_id not in self.session.config.error_list: + self.session.logger.error(message) + self.session.config.error_list.update({n_id: datetime.now()}) diff --git a/src/apyhiveapi/helper/map.py b/src/apyhiveapi/helper/map.py new file mode 100644 index 0000000..67d9816 --- /dev/null +++ b/src/apyhiveapi/helper/map.py @@ -0,0 +1,39 @@ +"""Dot notation for dictionary.""" + +from typing import Any + + +class Map(dict): + """dot.notation access to dictionary attributes. + + Args: + dict (dict): dictionary to map. + """ + + def __getattr__(self, item: str) -> Any: + """Get item from dictionary. + + Args: + item (str): key to get. + + Returns: + Any: Value of the key. + """ + return self.get(item) # type: ignore + + def __setattr__(self, key: str, value: Any) -> None: + """Set value to dictionary. + + Args: + key (str): key to set. + value (Any): Value to set. + """ + self[key] = value + + def __delattr__(self, key: str) -> None: + """Delete item from dictionary. + + Args: + key (str): Item to delete. + """ + self.pop(key, None) diff --git a/pyhiveapi/apyhiveapi/hive.py b/src/apyhiveapi/hive.py similarity index 87% rename from pyhiveapi/apyhiveapi/hive.py rename to src/apyhiveapi/hive.py index eb7c560..2588fe0 100644 --- a/pyhiveapi/apyhiveapi/hive.py +++ b/src/apyhiveapi/hive.py @@ -1,8 +1,8 @@ """Start Hive Session.""" -# pylint: skip-file + +import os import sys import traceback -from os.path import expanduser from typing import Optional from aiohttp import ClientSession @@ -20,7 +20,7 @@ from .session import HiveSession debug = [] -home = expanduser("~") +home = os.path.expanduser("~") logger.add( home + "/pyhiveapi_debug.log", filter=lambda record: record["level"].name == "DEBUG" ) @@ -40,6 +40,9 @@ def exception_handler(exctype, value, tb): value ([type]): [description] tb ([type]): [description] """ + with open(os.devnull, "w", encoding="utf-8") as devnull: + print(f"{exctype.__name__}: {value}", file=devnull) + last = len(traceback.extract_tb(tb)) - 1 logger.error( f"-> \n" @@ -85,14 +88,14 @@ def trace_debug(frame, event, arg): elif event == "return": logger.debug(f"returning {arg}") - return trace_debug + return trace_debug class Hive(HiveSession): """Hive Class. Args: - HiveSession (object): Interact with Hive Account + hive_session (object): Interact with Hive Account """ def __init__( @@ -104,7 +107,8 @@ def __init__( """Generate a Hive session. Args: - websession (Optional[ClientSession], optional): This is a websession that can be used for the api. Defaults to None. + websession (Optional[ClientSession], optional): + This is a websession that can be used for the api. Defaults to None. username (str, optional): This is the Hive username used for login. Defaults to None. password (str, optional): This is the Hive password used for login. Defaults to None. """ @@ -123,17 +127,15 @@ def __init__( if debug: sys.settrace(trace_debug) - def setDebugging(self, debugger: list): + def set_debugging(self, debugger: list) -> None: """Set function to debug. Args: debugger (list): a list of functions to debug - - Returns: - object: Returns traceback object. """ global debug debug = debugger if debug: - return sys.settrace(trace_debug) - return sys.settrace(None) + sys.settrace(trace_debug) + else: + sys.settrace(None) diff --git a/pyhiveapi/apyhiveapi/hotwater.py b/src/apyhiveapi/hotwater.py similarity index 53% rename from pyhiveapi/apyhiveapi/hotwater.py rename to src/apyhiveapi/hotwater.py index ae83d82..1ce755f 100644 --- a/pyhiveapi/apyhiveapi/hotwater.py +++ b/src/apyhiveapi/hotwater.py @@ -1,275 +1,275 @@ -"""Hive Hotwater Module.""" -# pylint: skip-file - -from .helper.const import HIVETOHA - - -class HiveHotwater: - """Hive Hotwater Code. - - Returns: - object: Hotwater Object. - """ - - hotwaterType = "Hotwater" - - async def getMode(self, device: dict): - """Get hotwater current mode. - - Args: - device (dict): Device to get the mode for. - - Returns: - str: Return mode. - """ - state = None - final = None - - try: - data = self.session.data.products[device["hiveID"]] - state = data["state"]["mode"] - if state == "BOOST": - state = data["props"]["previous"]["mode"] - final = HIVETOHA[self.hotwaterType].get(state, state) - except KeyError as e: - await self.session.log.error(e) - - return final - - @staticmethod - async def getOperationModes(): - """Get heating list of possible modes. - - Returns: - list: Return list of operation modes. - """ - return ["SCHEDULE", "ON", "OFF"] - - async def getBoost(self, device: dict): - """Get hot water current boost status. - - Args: - device (dict): Device to get boost status for - - Returns: - str: Return boost status. - """ - state = None - final = None - - try: - data = self.session.data.products[device["hiveID"]] - state = data["state"]["boost"] - final = HIVETOHA["Boost"].get(state, "ON") - except KeyError as e: - await self.session.log.error(e) - - return final - - async def getBoostTime(self, device: dict): - """Get hotwater boost time remaining. - - Args: - device (dict): Device to get boost time for. - - Returns: - str: Return time remaining on the boost. - """ - state = None - if await self.getBoost(device) == "ON": - try: - data = self.session.data.products[device["hiveID"]] - state = data["state"]["boost"] - except KeyError as e: - await self.session.log.error(e) - - return state - - async def getState(self, device: dict): - """Get hot water current state. - - Args: - device (dict): Device to get the state for. - - Returns: - str: return state of device. - """ - state = None - final = None - - try: - data = self.session.data.products[device["hiveID"]] - state = data["state"]["status"] - mode_current = await self.getMode(device) - if mode_current == "SCHEDULE": - if await self.getBoost(device) == "ON": - state = "ON" - else: - snan = self.session.helper.getScheduleNNL(data["state"]["schedule"]) - state = snan["now"]["value"]["status"] - - final = HIVETOHA[self.hotwaterType].get(state, state) - except KeyError as e: - await self.session.log.error(e) - - return final - - async def setMode(self, device: dict, new_mode: str): - """Set hot water mode. - - Args: - device (dict): device to update mode. - new_mode (str): Mode to set the device to. - - Returns: - boolean: return True/False if boost was successful. - """ - final = False - - if device["hiveID"] in self.session.data.products: - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], mode=new_mode - ) - if resp["original"] == 200: - final = True - await self.session.getDevices(device["hiveID"]) - - return final - - async def setBoostOn(self, device: dict, mins: int): - """Turn hot water boost on. - - Args: - device (dict): Deice to boost. - mins (int): Number of minutes to boost it for. - - Returns: - boolean: return True/False if boost was successful. - """ - final = False - - if ( - int(mins) > 0 - and device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] - ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], mode="BOOST", boost=mins - ) - if resp["original"] == 200: - final = True - await self.session.getDevices(device["hiveID"]) - - return final - - async def setBoostOff(self, device: dict): - """Turn hot water boost off. - - Args: - device (dict): device to set boost off - - Returns: - boolean: return True/False if boost was successful. - """ - final = False - - if ( - device["hiveID"] in self.session.data.products - and await self.getBoost(device) == "ON" - and device["deviceData"]["online"] - ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - prev_mode = data["props"]["previous"]["mode"] - resp = await self.session.api.setState( - data["type"], device["hiveID"], mode=prev_mode - ) - if resp["original"] == 200: - await self.session.getDevices(device["hiveID"]) - final = True - - return final - - -class WaterHeater(HiveHotwater): - """Water heater class. - - Args: - Hotwater (object): Hotwater class. - """ - - def __init__(self, session: object = None): - """Initialise water heater. - - Args: - session (object, optional): Session to interact with account. Defaults to None. - """ - self.session = session - - async def getWaterHeater(self, device: dict): - """Update water heater device. - - Args: - device (dict): device to update. - - Returns: - dict: Updated device. - """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} - ) - - if device["deviceData"]["online"]: - - dev_data = {} - self.session.helper.deviceRecovered(device["device_id"]) - data = self.session.data.devices[device["device_id"]] - dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], - "device_id": device["device_id"], - "device_name": device["device_name"], - "status": {"current_operation": await self.getMode(device)}, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), - "custom": device.get("custom", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] - ), - } - - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device - - async def getScheduleNowNextLater(self, device: dict): - """Hive get hotwater schedule now, next and later. - - Args: - device (dict): device to get schedule for. - - Returns: - dict: return now, next and later schedule. - """ - state = None - - try: - mode_current = await self.getMode(device) - if mode_current == "SCHEDULE": - data = self.session.data.products[device["hiveID"]] - state = self.session.helper.getScheduleNNL(data["state"]["schedule"]) - except KeyError as e: - await self.session.log.error(e) - - return state +"""Hive Hotwater Module.""" + + +from .helper.const import HIVETOHA + + +class HiveHotwater: + """Hive Hotwater Code. + + Returns: + object: Hotwater Object. + """ + + hot_water_type = "Hotwater" + + async def get_mode(self, device: dict): + """Get hotwater current mode. + + Args: + device (dict): Device to get the mode for. + + Returns: + str: Return mode. + """ + state = None + final = None + + try: + data = self.session.data.products[device["hive_id"]] + state = data["state"]["mode"] + if state == "BOOST": + state = data["props"]["previous"]["mode"] + final = HIVETOHA[self.hot_water_type].get(state, state) + except KeyError as e: + await self.session.log.error(e) + + return final + + @staticmethod + async def get_operation_modes(): + """Get heating list of possible modes. + + Returns: + list: Return list of operation modes. + """ + return ["SCHEDULE", "ON", "OFF"] + + async def get_boost(self, device: dict): + """Get hot water current boost status. + + Args: + device (dict): Device to get boost status for + + Returns: + str: Return boost status. + """ + state = None + final = None + + try: + data = self.session.data.products[device["hive_id"]] + state = data["state"]["boost"] + final = HIVETOHA["Boost"].get(state, "ON") + except KeyError as e: + await self.session.log.error(e) + + return final + + async def get_boost_time(self, device: dict): + """Get hotwater boost time remaining. + + Args: + device (dict): Device to get boost time for. + + Returns: + str: Return time remaining on the boost. + """ + state = None + if await self.get_boost(device) == "ON": + try: + data = self.session.data.products[device["hive_id"]] + state = data["state"]["boost"] + except KeyError as e: + await self.session.log.error(e) + + return state + + async def get_state(self, device: dict): + """Get hot water current state. + + Args: + device (dict): Device to get the state for. + + Returns: + str: return state of device. + """ + state = None + final = None + + try: + data = self.session.data.products[device["hive_id"]] + state = data["state"]["status"] + mode_current = await self.get_mode(device) + if mode_current == "SCHEDULE": + if await self.get_boost(device) == "ON": + state = "ON" + else: + snan = self.session.helper.get_schedule_nnl(data["state"]["schedule"]) + state = snan["now"]["value"]["status"] + + final = HIVETOHA[self.hot_water_type].get(state, state) + except KeyError as e: + await self.session.log.error(e) + + return final + + async def set_mode(self, device: dict, new_mode: str): + """Set hot water mode. + + Args: + device (dict): device to update mode. + new_mode (str): Mode to set the device to. + + Returns: + boolean: return True/False if boost was successful. + """ + final = False + + if device["hive_id"] in self.session.data.products: + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], mode=new_mode + ) + if resp["original"] == 200: + final = True + await self.session.get_devices() + + return final + + async def set_boost_on(self, device: dict, mins: int): + """Turn hot water boost on. + + Args: + device (dict): Deice to boost. + mins (int): Number of minutes to boost it for. + + Returns: + boolean: return True/False if boost was successful. + """ + final = False + + if ( + int(mins) > 0 + and device["hive_id"] in self.session.data.products + and device["device_data"]["online"] + ): + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], mode="BOOST", boost=mins + ) + if resp["original"] == 200: + final = True + await self.session.get_devices() + + return final + + async def set_boost_off(self, device: dict): + """Turn hot water boost off. + + Args: + device (dict): device to set boost off + + Returns: + boolean: return True/False if boost was successful. + """ + final = False + + if ( + device["hive_id"] in self.session.data.products + and await self.get_boost(device) == "ON" + and device["device_data"]["online"] + ): + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + prev_mode = data["props"]["previous"]["mode"] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], mode=prev_mode + ) + if resp["original"] == 200: + await self.session.get_devices() + final = True + + return final + + +class WaterHeater(HiveHotwater): + """Water heater class. + + Args: + Hotwater (object): Hotwater class. + """ + + def __init__(self, session: object = None): + """Initialise water heater. + + Args: + session (object, optional): Session to interact with account. Defaults to None. + """ + self.session = session + + async def get_water_heater(self, device: dict): + """Update water heater device. + + Args: + device (dict): device to update. + + Returns: + dict: Updated device. + """ + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} + ) + + if device["device_data"]["online"]: + + dev_data = {} + self.session.helper.device_recovered(device["device_id"]) + data = self.session.data.devices[device["device_id"]] + dev_data = { + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], + "device_id": device["device_id"], + "device_name": device["device_name"], + "status": {"current_operation": await self.get_mode(device)}, + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), + "custom": device.get("custom", None), + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] + ), + } + + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device + + async def get_schedule_now_next_later(self, device: dict): + """Hive get hotwater schedule now, next and later. + + Args: + device (dict): device to get schedule for. + + Returns: + dict: return now, next and later schedule. + """ + state = None + + try: + mode_current = await self.get_mode(device) + if mode_current == "SCHEDULE": + data = self.session.data.products[device["hive_id"]] + state = self.session.helper.get_schedule_nnl(data["state"]["schedule"]) + except KeyError as e: + await self.session.log.error(e) + + return state diff --git a/pyhiveapi/apyhiveapi/hub.py b/src/apyhiveapi/hub.py similarity index 71% rename from pyhiveapi/apyhiveapi/hub.py rename to src/apyhiveapi/hub.py index ec3bd92..dba99d2 100644 --- a/pyhiveapi/apyhiveapi/hub.py +++ b/src/apyhiveapi/hub.py @@ -1,5 +1,5 @@ """Hive Hub Module.""" -# pylint: skip-file + from .helper.const import HIVETOHA @@ -10,8 +10,8 @@ class HiveHub: object: Returns a hub object. """ - hubType = "Hub" - logType = "Sensor" + hub_type = "Hub" + log_type = "Sensor" def __init__(self, session: object = None): """Initialise hub. @@ -21,7 +21,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getSmokeStatus(self, device: dict): + async def get_smoke_status(self, device: dict): """Get the hub smoke status. Args: @@ -34,15 +34,15 @@ async def getSmokeStatus(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["sensors"]["SMOKE_CO"]["active"] - final = HIVETOHA[self.hubType]["Smoke"].get(state, state) + final = HIVETOHA[self.hub_type]["Smoke"].get(state, state) except KeyError as e: await self.session.log.error(e) return final - async def getDogBarkStatus(self, device: dict): + async def get_dog_bark_status(self, device: dict): """Get dog bark status. Args: @@ -55,15 +55,15 @@ async def getDogBarkStatus(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["sensors"]["DOG_BARK"]["active"] - final = HIVETOHA[self.hubType]["Dog"].get(state, state) + final = HIVETOHA[self.hub_type]["Dog"].get(state, state) except KeyError as e: await self.session.log.error(e) return final - async def getGlassBreakStatus(self, device: dict): + async def get_glass_break_status(self, device: dict): """Get the glass detected status from the Hive hub. Args: @@ -76,9 +76,9 @@ async def getGlassBreakStatus(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["sensors"]["GLASS_BREAK"]["active"] - final = HIVETOHA[self.hubType]["Glass"].get(state, state) + final = HIVETOHA[self.hub_type]["Glass"].get(state, state) except KeyError as e: await self.session.log.error(e) diff --git a/pyhiveapi/apyhiveapi/light.py b/src/apyhiveapi/light.py similarity index 58% rename from pyhiveapi/apyhiveapi/light.py rename to src/apyhiveapi/light.py index 703abe7..6276437 100644 --- a/pyhiveapi/apyhiveapi/light.py +++ b/src/apyhiveapi/light.py @@ -1,5 +1,5 @@ """Hive Light Module.""" -# pylint: skip-file + import colorsys from .helper.const import HIVETOHA @@ -12,9 +12,9 @@ class HiveLight: object: Hivelight """ - lightType = "Light" + light_type = "Light" - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get light current state. Args: @@ -27,15 +27,15 @@ async def getState(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["status"] - final = HIVETOHA[self.lightType].get(state, state) + final = HIVETOHA[self.light_type].get(state, state) except KeyError as e: await self.session.log.error(e) return final - async def getBrightness(self, device: dict): + async def get_brightness(self, device: dict): """Get light current brightness. Args: @@ -48,7 +48,7 @@ async def getBrightness(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["brightness"] final = (state / 100) * 255 except KeyError as e: @@ -56,7 +56,7 @@ async def getBrightness(self, device: dict): return final - async def getMinColorTemp(self, device: dict): + async def get_min_color_temp(self, device: dict): """Get light minimum color temperature. Args: @@ -69,7 +69,7 @@ async def getMinColorTemp(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["colourTemperature"]["max"] final = round((1 / state) * 1000000) except KeyError as e: @@ -77,7 +77,7 @@ async def getMinColorTemp(self, device: dict): return final - async def getMaxColorTemp(self, device: dict): + async def get_max_color_temp(self, device: dict): """Get light maximum color temperature. Args: @@ -90,7 +90,7 @@ async def getMaxColorTemp(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["colourTemperature"]["min"] final = round((1 / state) * 1000000) except KeyError as e: @@ -98,7 +98,7 @@ async def getMaxColorTemp(self, device: dict): return final - async def getColorTemp(self, device: dict): + async def get_color_temp(self, device: dict): """Get light current color temperature. Args: @@ -111,7 +111,7 @@ async def getColorTemp(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["colourTemperature"] final = round((1 / state) * 1000000) except KeyError as e: @@ -119,7 +119,7 @@ async def getColorTemp(self, device: dict): return final - async def getColor(self, device: dict): + async def get_color(self, device: dict): """Get light current colour. Args: @@ -132,7 +132,7 @@ async def getColor(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = [ (data["state"]["hue"]) / 360, (data["state"]["saturation"]) / 100, @@ -146,7 +146,7 @@ async def getColor(self, device: dict): return final - async def getColorMode(self, device: dict): + async def get_color_mode(self, device: dict): """Get Colour Mode. Args: @@ -158,14 +158,14 @@ async def getColorMode(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["colourMode"] except KeyError as e: await self.session.log.error(e) return state - async def setStatusOff(self, device: dict): + async def set_status_off(self, device: dict): """Set light to turn off. Args: @@ -177,22 +177,22 @@ async def setStatusOff(self, device: dict): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], status="OFF" + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( + data["type"], device["hive_id"], status="OFF" ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setStatusOn(self, device: dict): + async def set_status_on(self, device: dict): """Set light to turn on. Args: @@ -204,22 +204,22 @@ async def setStatusOn(self, device: dict): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] - resp = await self.session.api.setState( - data["type"], device["hiveID"], status="ON" + resp = await self.session.api.set_state( + data["type"], device["hive_id"], status="ON" ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setBrightness(self, device: dict, n_brightness: int): + async def set_brightness(self, device: dict, n_brightness: int): """Set brightness of the light. Args: @@ -232,24 +232,24 @@ async def setBrightness(self, device: dict, n_brightness: int): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], status="ON", brightness=n_brightness, ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setColorTemp(self, device: dict, color_temp: int): + async def set_color_temp(self, device: dict, color_temp: int): """Set light to turn on. Args: @@ -262,33 +262,33 @@ async def setColorTemp(self, device: dict, color_temp: int): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] if data["type"] == "tuneablelight": - resp = await self.session.api.setState( + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], colourTemperature=color_temp, ) else: - resp = await self.session.api.setState( + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], colourMode="WHITE", colourTemperature=color_temp, ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setColor(self, device: dict, new_color: list): + async def set_color(self, device: dict, new_color: list): """Set light to turn on. Args: @@ -301,15 +301,15 @@ async def setColor(self, device: dict, new_color: list): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] - resp = await self.session.api.setState( + resp = await self.session.api.set_state( data["type"], - device["hiveID"], + device["hive_id"], colourMode="COLOUR", hue=str(new_color[0]), saturation=str(new_color[1]), @@ -317,7 +317,7 @@ async def setColor(self, device: dict, new_color: list): ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final @@ -337,7 +337,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getLight(self, device: dict): + async def get_light(self, device: dict): """Get light data. Args: @@ -346,69 +346,69 @@ async def getLight(self, device: dict): Returns: dict: Updated device. """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} ) dev_data = {} - if device["deviceData"]["online"]: - self.session.helper.deviceRecovered(device["device_id"]) + if device["device_data"]["online"]: + self.session.helper.device_recovered(device["device_id"]) data = self.session.data.devices[device["device_id"]] dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], "device_id": device["device_id"], "device_name": device["device_name"], "status": { - "state": await self.getState(device), - "brightness": await self.getBrightness(device), + "state": await self.get_state(device), + "brightness": await self.get_brightness(device), }, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), "custom": device.get("custom", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] ), } - if device["hiveType"] in ("tuneablelight", "colourtuneablelight"): + if device["hive_type"] in ("tuneablelight", "colourtuneablelight"): dev_data.update( { - "min_mireds": await self.getMinColorTemp(device), - "max_mireds": await self.getMaxColorTemp(device), + "min_mireds": await self.get_min_color_temp(device), + "max_mireds": await self.get_max_color_temp(device), } ) dev_data["status"].update( - {"color_temp": await self.getColorTemp(device)} + {"color_temp": await self.get_color_temp(device)} ) - if device["hiveType"] == "colourtuneablelight": - mode = await self.getColorMode(device) + if device["hive_type"] == "colourtuneablelight": + mode = await self.get_color_mode(device) if mode == "COLOUR": dev_data["status"].update( { - "hs_color": await self.getColor(device), - "mode": await self.getColorMode(device), + "hs_color": await self.get_color(device), + "mode": await self.get_color_mode(device), } ) else: dev_data["status"].update( { - "mode": await self.getColorMode(device), + "mode": await self.get_color_mode(device), } ) - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device - async def turnOn(self, device: dict, brightness: int, color_temp: int, color: list): + async def turn_on(self, device: dict, brightness: int, color_temp: int, color: list): """Set light to turn on. Args: @@ -421,15 +421,15 @@ async def turnOn(self, device: dict, brightness: int, color_temp: int, color: li boolean: True/False if successful. """ if brightness is not None: - return await self.setBrightness(device, brightness) + return await self.set_brightness(device, brightness) if color_temp is not None: - return await self.setColorTemp(device, color_temp) + return await self.set_color_temp(device, color_temp) if color is not None: - return await self.setColor(device, color) + return await self.set_color(device, color) - return await self.setStatusOn(device) + return await self.set_status_on(device) - async def turnOff(self, device: dict): + async def turn_off(self, device: dict): """Set light to turn off. Args: @@ -438,4 +438,4 @@ async def turnOff(self, device: dict): Returns: boolean: True/False if successful. """ - return await self.setStatusOff(device) + return await self.set_status_off(device) diff --git a/pyhiveapi/apyhiveapi/plug.py b/src/apyhiveapi/plug.py similarity index 54% rename from pyhiveapi/apyhiveapi/plug.py rename to src/apyhiveapi/plug.py index c86e535..81f04ad 100644 --- a/pyhiveapi/apyhiveapi/plug.py +++ b/src/apyhiveapi/plug.py @@ -1,5 +1,5 @@ -"""Hive Switch Module.""" -# pylint: skip-file +"""Hive Plug Module.""" + from .helper.const import HIVETOHA @@ -10,9 +10,9 @@ class HiveSmartPlug: object: Returns Plug object """ - plugType = "Switch" + plug_type = "Switch" - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get smart plug state. Args: @@ -24,7 +24,7 @@ async def getState(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["state"]["status"] state = HIVETOHA["Switch"].get(state, state) except KeyError as e: @@ -32,7 +32,7 @@ async def getState(self, device: dict): return state - async def getPowerUsage(self, device: dict): + async def get_power_usage(self, device: dict): """Get smart plug current power usage. Args: @@ -44,14 +44,14 @@ async def getPowerUsage(self, device: dict): state = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] state = data["props"]["powerConsumption"] except KeyError as e: await self.session.log.error(e) return state - async def setStatusOn(self, device: dict): + async def set_status_on(self, device: dict): """Set smart plug to turn on. Args: @@ -63,21 +63,21 @@ async def setStatusOn(self, device: dict): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( data["type"], data["id"], status="ON" ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final - async def setStatusOff(self, device: dict): + async def set_status_off(self, device: dict): """Set smart plug to turn off. Args: @@ -89,17 +89,17 @@ async def setStatusOff(self, device: dict): final = False if ( - device["hiveID"] in self.session.data.products - and device["deviceData"]["online"] + device["hive_id"] in self.session.data.products + and device["device_data"]["online"] ): - await self.session.hiveRefreshTokens() - data = self.session.data.products[device["hiveID"]] - resp = await self.session.api.setState( + await self.session.hive_refresh_tokens() + data = self.session.data.products[device["hive_id"]] + resp = await self.session.api.set_state( data["type"], data["id"], status="OFF" ) if resp["original"] == 200: final = True - await self.session.getDevices(device["hiveID"]) + await self.session.get_devices() return final @@ -119,7 +119,7 @@ def __init__(self, session: object): """ self.session = session - async def getSwitch(self, device: dict): + async def get_switch(self, device: dict): """Home assistant wrapper to get switch device. Args: @@ -128,53 +128,53 @@ async def getSwitch(self, device: dict): Returns: dict: Return device after update is complete. """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} ) dev_data = {} - if device["deviceData"]["online"]: - self.session.helper.deviceRecovered(device["device_id"]) + if device["device_data"]["online"]: + self.session.helper.device_recovered(device["device_id"]) data = self.session.data.devices[device["device_id"]] dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], "device_id": device["device_id"], "device_name": device["device_name"], "status": { - "state": await self.getSwitchState(device), + "state": await self.get_switch_state(device), }, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), "custom": device.get("custom", None), "attributes": {}, } - if device["hiveType"] == "activeplug": + if device["hive_type"] == "activeplug": dev_data.update( { "status": { "state": dev_data["status"]["state"], - "power_usage": await self.getPowerUsage(device), + "power_usage": await self.get_power_usage(device), }, - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] ), } ) - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device - async def getSwitchState(self, device: dict): + async def get_switch_state(self, device: dict): """Home Assistant wrapper to get updated switch state. Args: @@ -183,12 +183,12 @@ async def getSwitchState(self, device: dict): Returns: boolean: Return True or False for the state. """ - if device["hiveType"] == "Heating_Heat_On_Demand": - return await self.session.heating.getHeatOnDemand(device) - else: - return await self.getState(device) + if device["hive_type"] == "heating_heat_on_demand": + return await self.session.heating.get_heat_on_demand(device) - async def turnOn(self, device: dict): + return await self.get_state(device) + + async def turn_on(self, device: dict): """Home Assisatnt wrapper for turning switch on. Args: @@ -197,12 +197,12 @@ async def turnOn(self, device: dict): Returns: function: Calls relevant function. """ - if device["hiveType"] == "Heating_Heat_On_Demand": - return await self.session.heating.setHeatOnDemand(device, "ENABLED") - else: - return await self.setStatusOn(device) + if device["hive_type"] == "Heating_Heat_On_Demand": + return await self.session.heating.set_heat_on_demand(device, "ENABLED") + + return await self.set_status_on(device) - async def turnOff(self, device: dict): + async def turn_off(self, device: dict): """Home Assisatnt wrapper for turning switch off. Args: @@ -211,7 +211,7 @@ async def turnOff(self, device: dict): Returns: function: Calls relevant function. """ - if device["hiveType"] == "Heating_Heat_On_Demand": - return await self.session.heating.setHeatOnDemand(device, "DISABLED") - else: - return await self.setStatusOff(device) + if device["hive_type"] == "Heating_Heat_On_Demand": + return await self.session.heating.set_heat_on_demand(device, "DISABLED") + + return await self.set_status_off(device) diff --git a/pyhiveapi/apyhiveapi/sensor.py b/src/apyhiveapi/sensor.py similarity index 51% rename from pyhiveapi/apyhiveapi/sensor.py rename to src/apyhiveapi/sensor.py index ceb0b99..9c901d6 100644 --- a/pyhiveapi/apyhiveapi/sensor.py +++ b/src/apyhiveapi/sensor.py @@ -1,14 +1,13 @@ """Hive Sensor Module.""" -# pylint: skip-file -from .helper.const import HIVE_TYPES, HIVETOHA, sensor_commands +from .helper.const import HIVE_TYPES, HIVETOHA class HiveSensor: """Hive Sensor Code.""" - sensorType = "Sensor" + sensor_type = "Sensor" - async def getState(self, device: dict): + async def get_state(self, device: dict): """Get sensor state. Args: @@ -21,10 +20,10 @@ async def getState(self, device: dict): final = None try: - data = self.session.data.products[device["hiveID"]] + data = self.session.data.products[device["hive_id"]] if data["type"] == "contactsensor": state = data["props"]["status"] - final = HIVETOHA[self.sensorType].get(state, state) + final = HIVETOHA[self.sensor_type].get(state, state) elif data["type"] == "motionsensor": final = data["props"]["motion"]["status"] except KeyError as e: @@ -47,7 +46,7 @@ async def online(self, device: dict): try: data = self.session.data.devices[device["device_id"]] state = data["props"]["online"] - final = HIVETOHA[self.sensorType].get(state, state) + final = HIVETOHA[self.sensor_type].get(state, state) except KeyError as e: await self.session.log.error(e) @@ -69,7 +68,7 @@ def __init__(self, session: object = None): """ self.session = session - async def getSensor(self, device: dict): + async def get_sensor(self, device: dict): """Gets updated sensor data. Args: @@ -78,68 +77,62 @@ async def getSensor(self, device: dict): Returns: dict: Updated device. """ - device["deviceData"].update( - {"online": await self.session.attr.onlineOffline(device["device_id"])} + device["device_data"].update( + {"online": await self.session.attr.online_offline(device["device_id"])} ) data = {} - if device["deviceData"]["online"] or device["hiveType"] in ( + if device["device_data"]["online"] or device["hive_type"] in ( "Availability", "Connectivity", ): - if device["hiveType"] not in ("Availability", "Connectivity"): - self.session.helper.deviceRecovered(device["device_id"]) + if device["hive_type"] not in ("Availability", "Connectivity"): + self.session.helper.device_recovered(device["device_id"]) dev_data = {} dev_data = { - "hiveID": device["hiveID"], - "hiveName": device["hiveName"], - "hiveType": device["hiveType"], - "haName": device["haName"], - "haType": device["haType"], + "hive_id": device["hive_id"], + "hive_name": device["hive_name"], + "hive_type": device["hive_type"], + "ha_name": device["ha_name"], + "ha_type": device["ha_type"], "device_id": device.get("device_id", None), "device_name": device.get("device_name", None), - "deviceData": {}, + "device_data": {}, "custom": device.get("custom", None), } if device["device_id"] in self.session.data.devices: data = self.session.data.devices.get(device["device_id"], {}) - elif device["hiveID"] in self.session.data.products: - data = self.session.data.products.get(device["hiveID"], {}) - - if ( - dev_data["hiveType"] in sensor_commands - or dev_data.get("custom", None) in sensor_commands - ): - code = sensor_commands.get( - dev_data["hiveType"], - sensor_commands.get(dev_data["custom"]), - ) + elif device["hive_id"] in self.session.data.products: + data = self.session.data.products.get(device["hive_id"], {}) + + sensor_state = self.session.helper.call_sensor_function(dev_data) + if sensor_state is not None: dev_data.update( { - "status": {"state": await eval(code)}, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), + "status": {"state": sensor_state}, + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), } ) - elif device["hiveType"] in HIVE_TYPES["Sensor"]: - data = self.session.data.devices.get(device["hiveID"], {}) + elif device["hive_type"] in HIVE_TYPES["Sensor"]: + data = self.session.data.devices.get(device["hive_id"], {}) dev_data.update( { - "status": {"state": await self.getState(device)}, - "deviceData": data.get("props", None), - "parentDevice": data.get("parent", None), - "attributes": await self.session.attr.stateAttributes( - device["device_id"], device["hiveType"] + "status": {"state": await self.get_state(device)}, + "device_data": data.get("props", None), + "parent_device": data.get("parent", None), + "attributes": await self.session.attr.state_attributes( + device["device_id"], device["hive_type"] ), } ) - self.session.devices.update({device["hiveID"]: dev_data}) - return self.session.devices[device["hiveID"]] - else: - await self.session.log.errorCheck( - device["device_id"], "ERROR", device["deviceData"]["online"] - ) - return device + self.session.devices.update({device["hive_id"]: dev_data}) + return self.session.devices[device["hive_id"]] + + await self.session.log.error_check( + device["device_id"], device["device_data"]["online"] + ) + return device diff --git a/pyhiveapi/apyhiveapi/session.py b/src/apyhiveapi/session.py similarity index 51% rename from pyhiveapi/apyhiveapi/session.py rename to src/apyhiveapi/session.py index 4868a6b..9b243b3 100644 --- a/pyhiveapi/apyhiveapi/session.py +++ b/src/apyhiveapi/session.py @@ -1,5 +1,5 @@ """Hive Session Module.""" -# pylint: skip-file + import asyncio import copy import json @@ -7,9 +7,9 @@ import os import time from datetime import datetime, timedelta +from typing import Optional from aiohttp.web import HTTPException -from apyhiveapi import API, Auth from .device_attributes import HiveAttributes from .helper.const import ACTIONS, DEVICES, HIVE_TYPES, PRODUCTS @@ -28,6 +28,13 @@ from .helper.logger import Logger from .helper.map import Map +if __name__ == "pyhiveapi": + from .api.hive_api import HiveApi as API + from .api.hive_auth import HiveAuth as Auth +else: + from .api.hive_async_api import HiveApiAsync as API + from .api.hive_auth_async import HiveAuthAsync as Auth + class HiveSession: """Hive Session Code. @@ -42,7 +49,7 @@ class HiveSession: object: Session object. """ - sessionType = "Session" + session_type = "Session" def __init__( self, @@ -61,16 +68,16 @@ def __init__( username=username, password=password, ) - self.api = API(hiveSession=self, websession=websession) + self.api = API(hive_session=self, websession=websession) self.helper = HiveHelper(self) self.attr = HiveAttributes(self) self.log = Logger(self) - self.updateLock = asyncio.Lock() + self.update_lock = asyncio.Lock() self.tokens = Map( { - "tokenData": {}, - "tokenCreated": datetime.now() - timedelta(seconds=4000), - "tokenExpiry": timedelta(seconds=3600), + "token_data": {}, + "token_created": datetime.now() - timedelta(seconds=4000), + "token_expiry": timedelta(seconds=3600), } ) self.config = Map( @@ -78,13 +85,13 @@ def __init__( "alarm": False, "battery": [], "camera": False, - "errorList": {}, + "error_list": {}, "file": False, - "homeID": None, - "lastUpdated": datetime.now(), + "home_id": None, + "last_updated": datetime.now(), "mode": [], - "scanInterval": timedelta(seconds=120), - "userID": None, + "scan_interval": timedelta(seconds=120), + "user_id": None, "username": username, } ) @@ -94,15 +101,15 @@ def __init__( "devices": {}, "actions": {}, "user": {}, - "minMax": {}, + "min_max": {}, "alarm": {}, "camera": {}, } ) self.devices = {} - self.deviceList = {} + self.device_list = {} - def openFile(self, file: str): + def open_file(self, file: str): """Open a file. Args: @@ -111,14 +118,14 @@ def openFile(self, file: str): Returns: dict: Data from the chosen file. """ - path = os.path.dirname(os.path.realpath(__file__)) + "/data/" + file + path = os.path.dirname(os.path.realpath(__file__)) + "/test_data/" + file path = path.replace("/pyhiveapi/", "/apyhiveapi/") - with open(path) as j: + with open(path, encoding="utf-8") as j: data = json.loads(j.read()) return data - def addList(self, entityType: str, data: dict, **kwargs: dict): + def add_list(self, entity_type: str, data: dict, **kwargs: dict): """Add entity to the list. Args: @@ -129,7 +136,7 @@ def addList(self, entityType: str, data: dict, **kwargs: dict): dict: Entity. """ try: - device = self.helper.getDeviceData(data) + device = self.helper.get_device_data(data) device_name = ( device["state"]["name"] if device["state"]["name"] != "Receiver" @@ -138,29 +145,35 @@ def addList(self, entityType: str, data: dict, **kwargs: dict): formatted_data = {} formatted_data = { - "hiveID": data.get("id", ""), - "hiveName": device_name, - "hiveType": data.get("type", ""), - "haType": entityType, - "deviceData": device.get("props", data.get("props", {})), - "parentDevice": data.get("parent", None), - "isGroup": data.get("isGroup", False), + "hive_id": data.get("id", ""), + "hive_name": device_name, + "hive_type": data.get("type", ""), + "ha_type": entity_type, + "device_data": device.get("props", data.get("props", {})), + "parent_device": data.get("parent", None), + "is_group": data.get("is_group", False), "device_id": device["id"], "device_name": device_name, } - if kwargs.get("haName", "FALSE")[0] == " ": - kwargs["haName"] = device_name + kwargs["haName"] + if kwargs.get("ha_name", "FALSE")[0] == " ": + kwargs["ha_name"] = device_name + kwargs["ha_name"] else: formatted_data["haName"] = device_name + formatted_data.update(kwargs) - self.deviceList[entityType].append(formatted_data) + + if data.get("type", "") == "hub": + self.device_list["parent"].append(formatted_data) + else: + self.device_list[entity_type].append(formatted_data) + return formatted_data except KeyError as error: - self.logger.error(error) + self.log.error(error) return None - async def updateInterval(self, new_interval: timedelta): + async def update_interval(self, new_interval: timedelta): """Update the scan interval. Args: @@ -171,20 +184,19 @@ async def updateInterval(self, new_interval: timedelta): interval = new_interval if interval < timedelta(seconds=15): - interval = timedelta(seconds=15) - self.config.scanInterval = interval + interval = max(interval, timedelta(seconds=15)) + self.config.scan_interval = interval - async def useFile(self, username: str = None): + async def use_file(self, username: Optional[str] = None) -> None: """Update to check if file is being used. Args: - username (str, optional): Looks for use@file.com. Defaults to None. + username (Optional[str], optional): Looks for use@file.com. Defaults to None. """ - using_file = True if username == "use@file.com" else False - if using_file: - self.config.file = True + using_file = username == "use@file.com" + self.config.file = using_file - async def updateTokens(self, tokens: dict, update_expiry_time: bool = True): + async def update_tokens(self, tokens: dict, update_expiry_time: bool = True): """Update session tokens. Args: @@ -197,20 +209,20 @@ async def updateTokens(self, tokens: dict, update_expiry_time: bool = True): data = {} if "AuthenticationResult" in tokens: data = tokens.get("AuthenticationResult") - self.tokens.tokenData.update({"token": data["IdToken"]}) + self.tokens.token_data.update({"token": data["IdToken"]}) if "RefreshToken" in data: - self.tokens.tokenData.update({"refreshToken": data["RefreshToken"]}) - self.tokens.tokenData.update({"accessToken": data["AccessToken"]}) + self.tokens.token_data.update({"refresh_token": data["RefreshToken"]}) + self.tokens.token_data.update({"access_token": data["AccessToken"]}) if update_expiry_time: - self.tokens.tokenCreated = datetime.now() + self.tokens.token_created = datetime.now() elif "token" in tokens: data = tokens - self.tokens.tokenData.update({"token": data["token"]}) - self.tokens.tokenData.update({"refreshToken": data["refreshToken"]}) - self.tokens.tokenData.update({"accessToken": data["accessToken"]}) + self.tokens.token_data.update({"token": data["token"]}) + self.tokens.token_data.update({"refresh_token": data["refreshToken"]}) + self.tokens.token_data.update({"access_token": data["accessToken"]}) if "ExpiresIn" in data: - self.tokens.tokenExpiry = timedelta(seconds=data["ExpiresIn"]) + self.tokens.token_expiry = timedelta(seconds=data["ExpiresIn"]) return self.tokens @@ -237,10 +249,10 @@ async def login(self): print("no_internet_available") if "AuthenticationResult" in result: - await self.updateTokens(result) + await self.update_tokens(result) return result - async def sms2fa(self, code, session): + async def sms_2fa(self, code, session): """Login to hive account with 2 factor authentication. Raises: @@ -261,10 +273,10 @@ async def sms2fa(self, code, session): print("no_internet_available") if "AuthenticationResult" in result: - await self.updateTokens(result) + await self.update_tokens(result) return result - async def deviceLogin(self): + async def device_login(self): """Login to hive account using device authentication. Raises: @@ -280,15 +292,15 @@ async def deviceLogin(self): try: result = await self.auth.device_login() - except HiveInvalidDeviceAuthentication: - raise HiveInvalidDeviceAuthentication + except HiveInvalidDeviceAuthentication as exc: + raise HiveInvalidDeviceAuthentication from exc if "AuthenticationResult" in result: - await self.updateTokens(result) - self.tokens.tokenExpiry = timedelta(seconds=0) + await self.update_tokens(result) + self.tokens.token_expiry = timedelta(seconds=0) return result - async def hiveRefreshTokens(self): + async def hive_refresh_tokens(self): """Refresh Hive tokens. Returns: @@ -298,45 +310,42 @@ async def hiveRefreshTokens(self): if self.config.file: return None - else: - expiry_time = self.tokens.tokenCreated + self.tokens.tokenExpiry - if datetime.now() >= expiry_time: - result = await self.auth.refresh_token( - self.tokens.tokenData["refreshToken"] - ) - if "AuthenticationResult" in result: - await self.updateTokens(result) - else: - raise HiveFailedToRefreshTokens + expiry_time = self.tokens.token_created + self.tokens.token_expiry + if datetime.now() >= expiry_time: + result = await self.auth.refresh_token( + self.tokens.token_data["refresh_token"] + ) + + if "AuthenticationResult" in result: + await self.update_tokens(result) + else: + raise HiveFailedToRefreshTokens return result - async def updateData(self, device: dict): + async def update_data(self): """Get latest data for Hive nodes - rate limiting. - Args: - device (dict): Device requesting the update. - Returns: boolean: True/False if update was successful """ updated = False - ep = self.config.lastUpdate + self.config.scanInterval - if datetime.now() >= ep and not self.updateLock.locked(): + ep = self.config.last_update + self.config.scan_interval + if datetime.now() >= ep and not self.update_lock.locked(): try: - await self.updateLock.acquire() - await self.getDevices(device["hiveID"]) - if len(self.deviceList["camera"]) > 0: + await self.update_lock.acquire() + await self.get_devices() + if len(self.device_list["camera"]) > 0: for camera in self.data.camera: - await self.getCamera(self.devices[camera]) + await self.get_camera(self.devices[camera]) updated = True finally: - self.updateLock.release() + self.update_lock.release() return updated - async def getAlarm(self): + async def get_alarm(self): """Get alarm data. Raises: @@ -344,151 +353,160 @@ async def getAlarm(self): HiveApiError: An API error code has been returned. """ if self.config.file: - api_resp_d = self.openFile("alarm.json") + api_resp_d = self.open_file("alarm.json") elif self.tokens is not None: - api_resp_d = await self.api.getAlarm() + api_resp_d = await self.api.get_alarm() if operator.contains(str(api_resp_d["original"]), "20") is False: raise HTTPException - elif api_resp_d["parsed"] is None: + if api_resp_d["parsed"] is None: raise HiveApiError self.data.alarm = api_resp_d["parsed"] - async def getCamera(self, device): + async def get_camera(self, device): """Get camera data. Raises: HTTPException: HTTP error has occurred updating the devices. HiveApiError: An API error code has been returned. """ - cameraImage = None - cameraRecording = None - hasCameraImage = False - hasCameraRecording = False + camera_info = { + "camera_image": {}, + "camera_recording": {}, + "has_camera_image": False, + "has_camera_recording": False, + } if self.config.file: - cameraImage = self.openFile("camera.json") - cameraRecording = self.openFile("camera.json") + camera_info["camera_image"] = self.open_file("camera.json") + camera_info["camera_recording"] = self.open_file("camera.json") elif self.tokens is not None: - cameraImage = await self.api.getCameraImage(device) - hasCameraRecording = bool( - cameraImage["parsed"]["events"][0]["hasRecording"] + camera_info["camera_image"] = await self.api.get_camera_image(device) + camera_info["has_camera_image"] = bool( + camera_info["camera_image"]["parsed"]["events"][0]["hasRecording"] ) - if hasCameraRecording: - cameraRecording = await self.api.getCameraRecording( - device, cameraImage["parsed"]["events"][0]["eventId"] + if camera_info["has_camera_image"]: + camera_info["camera_recording"] = await self.api.get_camera_recording( + device, + camera_info["camera_image"]["parsed"]["events"][0]["eventId"], + ) + camera_info["has_camera_recording"] = bool( + camera_info["camera_recording"]["parsed"]["events"][0][ + "hasRecording" + ] ) - if operator.contains(str(cameraImage["original"]), "20") is False: + if ( + operator.contains(str(camera_info["camera_image"]["original"]), "20") + is False + ): raise HTTPException - elif cameraImage["parsed"] is None: + if camera_info["camera_image"]["parsed"] is None: raise HiveApiError else: raise NoApiToken - hasCameraImage = bool(cameraImage["parsed"]["events"][0]) - + camera_info["has_camera_image"] = bool( + camera_info["camera_image"]["parsed"]["events"][0] + ) self.data.camera[device["id"]] = {} - self.data.camera[device["id"]]["cameraImage"] = None - self.data.camera[device["id"]]["cameraRecording"] = None - - if cameraImage is not None and hasCameraImage: - self.data.camera[device["id"]] = {} - self.data.camera[device["id"]]["cameraImage"] = cameraImage["parsed"][ - "events" - ][0] - if cameraRecording is not None and hasCameraRecording: - self.data.camera[device["id"]]["cameraRecording"] = cameraRecording[ - "parsed" - ] - - async def getDevices(self, n_id: str): - """Get latest data for Hive nodes. - - Args: - n_id (str): ID of the device requesting data. - - Raises: - HTTPException: HTTP error has occurred updating the devices. - HiveApiError: An API error code has been returned. - - Returns: - boolean: True/False if update was successful. - """ + self.data.camera[device["id"]]["camera_image"] = None + self.data.camera[device["id"]]["camera_recording"] = None + + if camera_info["camera_image"] is not None and camera_info["has_camera_image"]: + self.data.camera[device["id"]]["camera_image"] = camera_info[ + "camera_image" + ]["parsed"]["events"][0] + if ( + camera_info["camera_recording"] is not None + and camera_info["has_camera_recording"] + ): + self.data.camera[device["id"]]["camera_recording"] = camera_info[ + "camera_recording" + ]["parsed"]["events"][0] + + async def get_devices(self): + """Get node data.""" get_nodes_successful = False api_resp_d = None try: if self.config.file: - api_resp_d = self.openFile("data.json") + api_resp_d = self.open_file("data.json") elif self.tokens is not None: - await self.hiveRefreshTokens() - api_resp_d = await self.api.getAll() + await self.hive_refresh_tokens() + api_resp_d = await self.api.get_all() if operator.contains(str(api_resp_d["original"]), "20") is False: raise HTTPException - elif api_resp_d["parsed"] is None: + if api_resp_d["parsed"] is None: raise HiveApiError api_resp_p = api_resp_d["parsed"] - tmpProducts = {} - tmpDevices = {} - tmpActions = {} - - for hiveType in api_resp_p: - if hiveType == "user": - self.data.user = api_resp_p[hiveType] - self.config.userID = api_resp_p[hiveType]["id"] - if hiveType == "products": - for aProduct in api_resp_p[hiveType]: - tmpProducts.update({aProduct["id"]: aProduct}) - if hiveType == "devices": - for aDevice in api_resp_p[hiveType]: - tmpDevices.update({aDevice["id"]: aDevice}) - if aDevice["type"] == "siren": + temp_products = {} + temp_devices = {} + temp_actions = {} + + for hive_type in api_resp_p: + if hive_type == "user": + self.data.user = api_resp_p[hive_type] + self.config.user_id = api_resp_p[hive_type]["id"] + if hive_type == "products": + for a_product in api_resp_p[hive_type]: + temp_products.update({a_product["id"]: a_product}) + if hive_type == "devices": + for a_device in api_resp_p[hive_type]: + temp_devices.update({a_device["id"]: a_device}) + if a_device["type"] == "siren": self.config.alarm = True # if aDevice["type"] == "hivecamera": # await self.getCamera(aDevice) - if hiveType == "actions": - for aAction in api_resp_p[hiveType]: - tmpActions.update({aAction["id"]: aAction}) - if hiveType == "homes": - self.config.homeID = api_resp_p[hiveType]["homes"][0]["id"] - - if len(tmpProducts) > 0: - self.data.products = copy.deepcopy(tmpProducts) - if len(tmpDevices) > 0: - self.data.devices = copy.deepcopy(tmpDevices) - self.data.actions = copy.deepcopy(tmpActions) + if hive_type == "actions": + for a_action in api_resp_p[hive_type]: + temp_actions.update({a_action["id"]: a_action}) + if hive_type == "homes": + self.config.home_id = api_resp_p[hive_type]["homes"][0]["id"] + + if len(temp_products) > 0: + self.data.products = copy.deepcopy(temp_products) + if len(temp_devices) > 0: + self.data.devices = copy.deepcopy(temp_devices) + self.data.actions = copy.deepcopy(temp_actions) if self.config.alarm: - await self.getAlarm() - self.config.lastUpdate = datetime.now() + await self.get_alarm() + self.config.last_update = datetime.now() get_nodes_successful = True - except (OSError, RuntimeError, HiveApiError, ConnectionError, HTTPException): + except ( + OSError, + RuntimeError, + HiveApiError, + ConnectionError, + HTTPException, + ): get_nodes_successful = False return get_nodes_successful - async def startSession(self, config: dict = {}): + async def start_session(self, config: dict = None): """Setup the Hive platform. Args: config (dict, optional): Configuration for Home Assistant to use. Defaults to {}. Raises: - HiveUnknownConfiguration: Unknown configuration identifed. + HiveUnknownConfiguration: Unknown configuration identified. HiveReauthRequired: Tokens have expired and reauthentication is required. Returns: list: List of devices """ - await self.useFile(config.get("username", self.config.username)) - await self.updateInterval( - config.get("options", {}).get("scan_interval", self.config.scanInterval) + await self.use_file(config.get("username", self.config.username)) + await self.update_interval( + config.get("options", {}).get("scan_interval", self.config.scan_interval) ) if config != {}: if "tokens" in config and not self.config.file: - await self.updateTokens(config["tokens"], False) + await self.update_tokens(config["tokens"], False) if "device_data" in config and not self.config.file: self.auth.device_group_key = config["device_data"][0] @@ -499,72 +517,73 @@ async def startSession(self, config: dict = {}): raise HiveUnknownConfiguration try: - await self.getDevices("No_ID") + await self.get_devices() except HTTPException: return HTTPException if self.data.devices == {} or self.data.products == {}: raise HiveReauthRequired - return await self.createDevices() + return await self.create_devices() - async def createDevices(self): + async def create_devices(self): """Create list of devices. Returns: list: List of devices """ - self.deviceList["alarm_control_panel"] = [] - self.deviceList["binary_sensor"] = [] - self.deviceList["camera"] = [] - self.deviceList["climate"] = [] - self.deviceList["light"] = [] - self.deviceList["sensor"] = [] - self.deviceList["switch"] = [] - self.deviceList["water_heater"] = [] + self.device_list["parent"] = [] + self.device_list["alarm_control_panel"] = [] + self.device_list["binary_sensor"] = [] + self.device_list["camera"] = [] + self.device_list["climate"] = [] + self.device_list["light"] = [] + self.device_list["sensor"] = [] + self.device_list["switch"] = [] + self.device_list["water_heater"] = [] hive_type = HIVE_TYPES["Heating"] + HIVE_TYPES["Switch"] + HIVE_TYPES["Light"] - for aProduct in self.data.products: - p = self.data.products[aProduct] + for a_product in self.data.products: + p = self.data.products[a_product] if "error" in p: continue # Only consider single items or heating groups if ( p.get("isGroup", False) - and self.data.products[aProduct]["type"] not in HIVE_TYPES["Heating"] + and self.data.products[a_product]["type"] not in HIVE_TYPES["Heating"] ): continue - product_list = PRODUCTS.get(self.data.products[aProduct]["type"], []) - product_name = self.data.products[aProduct]["state"].get("name", "Unknown") + product_list = PRODUCTS.get(self.data.products[a_product]["type"], []) + product_name = self.data.products[a_product]["state"].get("name", "Unknown") for code in product_list: try: eval("self." + code) - except (NameError, AttributeError) as e: - self.logger.warning(f"Device {product_name} cannot be setup - {e}") - pass + except (NameError, AttributeError, KeyError) as e: + self.log.warning(f"Device {product_name} cannot be setup - {e}") - if self.data.products[aProduct]["type"] in hive_type: + if self.data.products[a_product]["type"] in hive_type: self.config.mode.append(p["id"]) hive_type = HIVE_TYPES["Thermo"] + HIVE_TYPES["Sensor"] - for aDevice in self.data["devices"]: - d = self.data.devices[aDevice] - device_list = DEVICES.get(self.data.devices[aDevice]["type"], []) + for a_device in self.data["devices"]: + d = self.data.devices[a_device] + device_list = DEVICES.get(self.data.devices[a_device]["type"], []) for code in device_list: eval("self." + code) - if self.data["devices"][aDevice]["type"] in hive_type: + if self.data["devices"][a_device]["type"] in hive_type: self.config.battery.append(d["id"]) if "action" in HIVE_TYPES["Switch"]: for action in self.data["actions"]: - a = self.data["actions"][action] # noqa: F841 + a = self.data["actions"][action] + type(a) eval("self." + ACTIONS) - return self.deviceList + return self.device_list @staticmethod - def epochTime(date_time: any, pattern: str, action: str): + def epoch_time(date_time: any, pattern: str, action: str): """date/time conversion to epoch. Args: @@ -577,8 +596,10 @@ def epochTime(date_time: any, pattern: str, action: str): """ if action == "to_epoch": pattern = "%d.%m.%Y %H:%M:%S" - epochtime = int(time.mktime(time.strptime(str(date_time), pattern))) - return epochtime - elif action == "from_epoch": + epoch_time = int(time.mktime(time.strptime(str(date_time), pattern))) + return epoch_time + if action == "from_epoch": date = datetime.fromtimestamp(int(date_time)).strftime(pattern) return date + + return None diff --git a/pyhiveapi/apyhiveapi/data/alarm.json b/src/apyhiveapi/test_data/alarm.json similarity index 100% rename from pyhiveapi/apyhiveapi/data/alarm.json rename to src/apyhiveapi/test_data/alarm.json diff --git a/src/apyhiveapi/test_data/cameraImage.json b/src/apyhiveapi/test_data/cameraImage.json new file mode 100644 index 0000000..8671e10 --- /dev/null +++ b/src/apyhiveapi/test_data/cameraImage.json @@ -0,0 +1,12 @@ +{ + "parsed": { + "events": [ + { + "thumbnailUrls": [ + "https://test.com/image" + ], + "hasRecording": true + } + ] + } +} \ No newline at end of file diff --git a/src/apyhiveapi/test_data/cameraRecording.json b/src/apyhiveapi/test_data/cameraRecording.json new file mode 100644 index 0000000..569e0e7 --- /dev/null +++ b/src/apyhiveapi/test_data/cameraRecording.json @@ -0,0 +1,3 @@ +{ + "parsed": "https://test.com/video" +} \ No newline at end of file diff --git a/pyhiveapi/apyhiveapi/data/data.json b/src/apyhiveapi/test_data/data.json similarity index 100% rename from pyhiveapi/apyhiveapi/data/data.json rename to src/apyhiveapi/test_data/data.json diff --git a/tests/API/async_auth.py b/tests/API/async_auth.py deleted file mode 100644 index b439b42..0000000 --- a/tests/API/async_auth.py +++ /dev/null @@ -1 +0,0 @@ -"""Test file.""" diff --git a/tests/__init__.py b/tests/__init__.py index d203639..cf2ff68 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -"""Tests for pyhiveapi.""" +"""Apyhiveapi tests.""" diff --git a/tests/apyhiveapi/test_async_hub.py b/tests/apyhiveapi/test_async_hub.py new file mode 100644 index 0000000..f676057 --- /dev/null +++ b/tests/apyhiveapi/test_async_hub.py @@ -0,0 +1,126 @@ +"""Tests for the hub object.""" + +import pytest + +from tests.common import MockSession + + +@pytest.mark.asyncio +async def test_hub_get_smoke_status_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products[smoke_sensor["hive_id"]]["props"]["sensors"][ + "SMOKE_CO" + ]["active"] = True + state = await hive_session.hub.get_smoke_status(smoke_sensor) + + assert state == 1 + + +@pytest.mark.asyncio +async def test_hub_get_smoke_status_not_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + + state = await hive_session.hub.get_smoke_status(smoke_sensor) + + assert state == 0 + + +@pytest.mark.asyncio +async def test_hub_get_smoke_status_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products.pop(smoke_sensor["hive_id"]) + state = await hive_session.hub.get_smoke_status(smoke_sensor) + + assert state is None + + +@pytest.mark.asyncio +async def test_hub_get_glass_break_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][0] + state = await hive_session.hub.get_glass_break_status(glass_sensor) + + assert state == 1 + + +@pytest.mark.asyncio +async def test_hub_get_glass_break_not_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products[glass_sensor["hive_id"]]["props"]["sensors"][ + "GLASS_BREAK" + ]["active"] = False + state = await hive_session.hub.get_glass_break_status(glass_sensor) + + assert state == 0 + + +@pytest.mark.asyncio +async def test_hub_get_glass_break_detection_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][0] + hive_session.session.data.products.pop(glass_sensor["hive_id"]) + state = await hive_session.hub.get_glass_break_status(glass_sensor) + + assert state is None + + +@pytest.mark.asyncio +async def test_hub_get_dog_bark_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][2] + hive_session.session.data.products[dog_sensor["hive_id"]]["props"]["sensors"][ + "DOG_BARK" + ]["active"] = True + state = await hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state == 1 + + +@pytest.mark.asyncio +async def test_hub_get_dog_bark_not_detected(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][2] + state = await hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state == 0 + + +@pytest.mark.asyncio +async def test_hub_get_dog_bark_detection_status_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][2] + hive_session.session.data.products.pop(dog_sensor["hive_id"]) + state = await hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state is None diff --git a/tests/apyhiveapi/test_async_plug.py b/tests/apyhiveapi/test_async_plug.py new file mode 100644 index 0000000..9781c6d --- /dev/null +++ b/tests/apyhiveapi/test_async_plug.py @@ -0,0 +1,202 @@ +"""Tests for the switch and plug object.""" + +from unittest.mock import patch + +import pytest + +from tests.common import MockSession + + +@pytest.mark.asyncio +async def test_switch_update_switch_online(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + device_data = await hive_session.switch.get_switch(switch) + + assert device_data != {} + + +@pytest.mark.asyncio +async def test_switch_update_switch_offline(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + switch["device_data"]["online"] = False + device_data = await hive_session.switch.get_switch(switch) + + assert device_data["hive_id"] in hive_session.session.config.error_list + + +@pytest.mark.asyncio +async def test_switch_get_plug_state(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + state = await hive_session.switch.get_switch_state(switch) + + assert state in (True, False) + + +@pytest.mark.asyncio +async def test_switch_get_plug_state_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + hive_session.session.data.products.pop(switch["hive_id"]) + state = await hive_session.switch.get_state(switch) + + assert state is None + + +@pytest.mark.asyncio +async def test_switch_get_heat_on_demand_state(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][0] + state = await hive_session.switch.get_switch_state(switch) + + assert state in (True, False) + + +@pytest.mark.asyncio +async def test_switch_turn_on_successfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_on(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_switch_turn_on_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_on(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_switch_turn_off_successfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_off(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_switch_turn_off_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_off(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_switch_heat_on_demand_turn_on_successfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][0] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_on(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_switch_heat_on_demand_turn_on_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][0] + + with patch( + "apyhiveapi.api.hive_async_api.HiveApiAsync.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = await hive_session.switch.turn_off(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +@pytest.mark.asyncio +async def test_plug_get_power_usage(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + power_usage = await hive_session.switch.get_power_usage(switch) + + assert power_usage is not None + + +@pytest.mark.asyncio +async def test_plug_get_power_usage_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + await hive.async_start_session() + hive_session = hive.async_hive + switch = hive_session.session.device_list["switch"][1] + hive_session.session.data.products.pop(switch["hive_id"]) + state = await hive_session.switch.get_power_usage(switch) + + assert state is None diff --git a/tests/apyhiveapi/test_async_session.py b/tests/apyhiveapi/test_async_session.py new file mode 100644 index 0000000..5a2be39 --- /dev/null +++ b/tests/apyhiveapi/test_async_session.py @@ -0,0 +1,14 @@ +"""Tests for the session object.""" + +import pytest + +from tests.common import MockSession + + +@pytest.mark.asyncio +async def test_start_session(): + """Test a session can be started.""" + hive = MockSession() + device_list = await hive.async_start_session() + + assert len(device_list) > 0 diff --git a/tests/apyhiveapi/test_async_switch.py b/tests/apyhiveapi/test_async_switch.py new file mode 100644 index 0000000..66a8773 --- /dev/null +++ b/tests/apyhiveapi/test_async_switch.py @@ -0,0 +1,77 @@ +import pytest +from unittest.mock import AsyncMock, MagicMock +from src.apyhiveapi.plug import Switch +from tests.common import MockSession + +@pytest.fixture +async def session(): + session = MockSession() + hive = await session.async_start_session() + return hive + + +@pytest.mark.asyncio +async def test_session_initialised(session): + assert session is not None + +@pytest.mark.asyncio +async def test_get_switch_happy_path(session): + device = {"device_id": "123", "device_data": {"online": True}, "hive_type": "activeplug"} + session.attr.online_offline = AsyncMock(return_value=True) + session.helper.device_recovered = MagicMock() + session.data.devices = {"123": {"props": "props_data", "parent": "parent_data"}} + session.attr.state_attributes = AsyncMock(return_value={"attr": "value"}) + session.switch.get_switch_state = AsyncMock(return_value="on") + session.switch.get_power_usage = AsyncMock(return_value="100W") + + result = await session.switch.get_switch(device) + + assert result["status"]["state"] == "on" + assert result["status"]["power_usage"] == "100W" + assert result["attributes"] == {"attr": "value"} + +@pytest.mark.asyncio +async def test_get_switch_unhappy_path(switch): + device = {"device_id": "123", "device_data": {"online": False}, "hive_type": "activeplug"} + switch.session.attr.online_offline = AsyncMock(return_value=False) + switch.session.log.error_check = AsyncMock() + + result = await switch.get_switch(device) + + assert result == device + +@pytest.mark.asyncio +async def test_turn_on_happy_path(switch): + device = {"hive_type": "activeplug"} + switch.set_status_on = AsyncMock(return_value="on") + + result = await switch.turn_on(device) + + assert result == "on" + +@pytest.mark.asyncio +async def test_turn_off_happy_path(switch): + device = {"hive_type": "activeplug"} + switch.set_status_off = AsyncMock(return_value="off") + + result = await switch.turn_off(device) + + assert result == "off" + +@pytest.mark.asyncio +async def test_turn_on_unhappy_path(switch): + device = {"hive_type": "Heating_Heat_On_Demand"} + switch.session.heating.set_heat_on_demand = AsyncMock(return_value="ENABLED") + + result = await switch.turn_on(device) + + assert result == "ENABLED" + +@pytest.mark.asyncio +async def test_turn_off_unhappy_path(switch): + device = {"hive_type": "Heating_Heat_On_Demand"} + switch.session.heating.set_heat_on_demand = AsyncMock(return_value="DISABLED") + + result = await switch.turn_off(device) + + assert result == "DISABLED" \ No newline at end of file diff --git a/tests/common.py b/tests/common.py index 95f9d4a..7e95be1 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,10 +1,57 @@ -"""Mock services for tests.""" -# pylint: skip-file +"""Test the helper method for writing tests.""" +from dataclasses import dataclass + +from apyhiveapi import Hive as HiveAsync + + +USERNAME = "use@file.com" +PASSWORD = "Test12345" +TEMP_CONFIG = { + "username": USERNAME, + "password": PASSWORD, + "options": {"scan_interval": 120}, +} + + +@dataclass class MockConfig: """Mock config for tests.""" + username: str + password: str + device_data: dict + tokens: dict + options: dict + +@dataclass class MockDevice: - """Mock Device for tests.""" + """Mock config for tests.""" + + username: str + password: str + device_data: dict + tokens: dict + options: dict + + +class MockSession: + """Mock Session for tests.""" + + def __init__(self): + """Initialize the Mock Session.""" + self.async_hive = None + self.sync_hive = None + + def sync_start_session(self): + """Start a sync session.""" + self.sync_hive = HiveSync(username=USERNAME, password=PASSWORD) + return self.sync_hive.start_session(TEMP_CONFIG) + + async def async_start_session(self): + """Start a async session.""" + self.async_hive = HiveAsync(username=USERNAME, password=PASSWORD) + await self.async_hive.start_session(TEMP_CONFIG) + return self.async_hive diff --git a/tests/pyhiveapi/test_sync_hub.py b/tests/pyhiveapi/test_sync_hub.py new file mode 100644 index 0000000..64eda5a --- /dev/null +++ b/tests/pyhiveapi/test_sync_hub.py @@ -0,0 +1,115 @@ +"""Tests for the hub object.""" + +from tests.common import MockSession + + +def test_hub_get_smoke_status_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products[smoke_sensor["hive_id"]]["props"]["sensors"][ + "SMOKE_CO" + ]["active"] = True + state = hive_session.hub.get_smoke_status(smoke_sensor) + + assert state == 1 + + +def test_hub_get_smoke_status_not_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + + state = hive_session.hub.get_smoke_status(smoke_sensor) + + assert state == 0 + + +def test_hub_get_smoke_status_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + smoke_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products.pop(smoke_sensor["hive_id"]) + state = hive_session.hub.get_smoke_status(smoke_sensor) + + assert state is None + + +def test_hub_get_glass_break_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][0] + state = hive_session.hub.get_glass_break_status(glass_sensor) + + assert state == 1 + + +def test_hub_get_glass_break_not_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products[glass_sensor["hive_id"]]["props"]["sensors"][ + "GLASS_BREAK" + ]["active"] = False + state = hive_session.hub.get_glass_break_status(glass_sensor) + + assert state == 0 + + +def test_hub_get_glass_break_detection_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + glass_sensor = hive_session.session.device_list["binary_sensor"][0] + hive_session.session.data.products.pop(glass_sensor["hive_id"]) + state = hive_session.hub.get_glass_break_status(glass_sensor) + + assert state is None + + +def test_hub_get_dog_bark_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][2] + hive_session.session.data.products[dog_sensor["hive_id"]]["props"]["sensors"][ + "DOG_BARK" + ]["active"] = True + state = hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state == 1 + + +def test_hub_get_dog_bark_not_detected(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][2] + state = hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state == 0 + + +def test_hub_get_dog_bark_detection_status_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + dog_sensor = hive_session.session.device_list["binary_sensor"][1] + hive_session.session.data.products.pop(dog_sensor["hive_id"]) + state = hive_session.hub.get_dog_bark_status(dog_sensor) + + assert state is None diff --git a/tests/pyhiveapi/test_sync_plug.py b/tests/pyhiveapi/test_sync_plug.py new file mode 100644 index 0000000..faf0528 --- /dev/null +++ b/tests/pyhiveapi/test_sync_plug.py @@ -0,0 +1,187 @@ +"""Tests for the switch and plug object.""" + +from unittest.mock import patch + +from tests.common import MockSession + + +def test_switch_update_switch_online(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + device_data = hive_session.switch.get_switch(switch) + + assert device_data != {} + + +def test_switch_update_switch_offline(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + switch["device_data"]["online"] = False + device_data = hive_session.switch.get_switch(switch) + + assert device_data["hive_id"] in hive_session.session.config.error_list + + +def test_switch_get_plug_state(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + state = hive_session.switch.get_switch_state(switch) + + assert state in (True, False) + + +def test_switch_get_plug_state_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + hive_session.session.data.products.pop(switch["hive_id"]) + state = hive_session.switch.get_state(switch) + + assert state is None + + +def test_switch_get_heat_on_demand_state(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][0] + state = hive_session.switch.get_switch_state(switch) + + assert state in (True, False) + + +def test_switch_turn_on_successfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_on(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +def test_switch_turn_on_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_on(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +def test_switch_turn_off_successfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_off(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +def test_switch_turn_off_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_off(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +def test_switch_heat_on_demand_turn_on_successfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][0] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 200, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_on(switch) + + assert result is True + assert len(api_call.mock_calls) == 1 + + +def test_switch_heat_on_demand_turn_on_unsuccessfully(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][0] + + with patch( + "pyhiveapi.api.hive_api.HiveApi.set_state", + return_value={"original": 401, "parsed": {}}, + ) as api_call: + result = hive_session.switch.turn_off(switch) + + assert result is False + assert len(api_call.mock_calls) == 1 + + +def test_plug_get_power_usage(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + power_usage = hive_session.switch.get_power_usage(switch) + + assert power_usage is not None + + +def test_plug_get_power_usage_with_key_error(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + hive_session = hive.sync_hive + switch = hive_session.session.device_list["switch"][1] + hive_session.session.data.products.pop(switch["hive_id"]) + state = hive_session.switch.get_power_usage(switch) + + assert state is None diff --git a/tests/pyhiveapi/test_sync_session.py b/tests/pyhiveapi/test_sync_session.py new file mode 100644 index 0000000..3f55e05 --- /dev/null +++ b/tests/pyhiveapi/test_sync_session.py @@ -0,0 +1,11 @@ +"""Tests for the session object.""" + +from tests.common import MockSession + + +def test_start_session(): + """Test a session can be started.""" + hive = MockSession() + hive.sync_start_session() + + assert len(hive.sync_start_session()) > 0 diff --git a/tests/test_hub.py b/tests/test_hub.py deleted file mode 100644 index 6c83435..0000000 --- a/tests/test_hub.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Test hub framework.""" - - -def test_hub_smoke(): - """Test for hub smoke.""" - result = None - - assert result