From df1be4c1440873d3c989ecf7d553e8e129461461 Mon Sep 17 00:00:00 2001 From: jfm Date: Thu, 9 Oct 2025 16:27:33 +0200 Subject: [PATCH] Initial commit - Support for Get Hydrometer and Get Fermentation Chamber --- .gitignore | 162 ++++++++ Pipfile | 16 + README.md | 0 pytest.ini | 4 + src/live_test_client.py | 10 + src/rapt/__init__.py | 0 src/rapt/client.py | 65 +++ src/rapt/fermentation_chamber.py | 53 +++ src/rapt/hydrometer.py | 53 +++ src/rapt/model/__init__.py | 0 src/rapt/model/amount.py | 6 + src/rapt/model/base.py | 13 + src/rapt/model/device.py | 39 ++ src/rapt/model/fermentation_chamber.py | 102 +++++ src/rapt/model/hydrometer.py | 46 +++ src/rapt/model/profile.py | 134 ++++++ src/rapt/model/toggle.py | 6 + src/rapt/model/yeast.py | 29 ++ .../get_fermentation_chamber_response.json | 384 +++++++++++++++++ ...rmentation_chamber_telemetry_response.json | 28 ++ .../get_fermentation_chambers_response.json | 386 ++++++++++++++++++ tests/json/get_hydrometer_response.json | 35 ++ .../get_hydrometer_telemetry_response.json | 14 + tests/json/get_hydrometers_response.json | 37 ++ tests/json/token_response.json | 6 + tests/test_fermentation_chamber.py | 83 ++++ tests/test_hydrometer.py | 83 ++++ 27 files changed, 1794 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 README.md create mode 100644 pytest.ini create mode 100644 src/live_test_client.py create mode 100644 src/rapt/__init__.py create mode 100644 src/rapt/client.py create mode 100644 src/rapt/fermentation_chamber.py create mode 100644 src/rapt/hydrometer.py create mode 100644 src/rapt/model/__init__.py create mode 100644 src/rapt/model/amount.py create mode 100644 src/rapt/model/base.py create mode 100644 src/rapt/model/device.py create mode 100644 src/rapt/model/fermentation_chamber.py create mode 100644 src/rapt/model/hydrometer.py create mode 100644 src/rapt/model/profile.py create mode 100644 src/rapt/model/toggle.py create mode 100644 src/rapt/model/yeast.py create mode 100644 tests/json/get_fermentation_chamber_response.json create mode 100644 tests/json/get_fermentation_chamber_telemetry_response.json create mode 100644 tests/json/get_fermentation_chambers_response.json create mode 100644 tests/json/get_hydrometer_response.json create mode 100644 tests/json/get_hydrometer_telemetry_response.json create mode 100644 tests/json/get_hydrometers_response.json create mode 100644 tests/json/token_response.json create mode 100644 tests/test_fermentation_chamber.py create mode 100644 tests/test_hydrometer.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aef64db --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#t +Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..5f8e782 --- /dev/null +++ b/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" +pydantic = "*" +loguru = "*" + +[dev-packages] +pytest = "*" +responses = "*" + +[requires] +python_version = "3.13" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..d0c1901 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +pythonpath = src +testpaths = tests +addopts = -s diff --git a/src/live_test_client.py b/src/live_test_client.py new file mode 100644 index 0000000..51f9896 --- /dev/null +++ b/src/live_test_client.py @@ -0,0 +1,10 @@ +from rapt.client import Client +from rapt.hydrometer import Hydrometer + +client = Client("jfm@moerks.dk", "9OBkd5yv1xY8") +hydrometer = Hydrometer(client) + +#hydrometer.get_hydrometers() +#hydrometer.get_hydrometer("2aa3b02c-78de-4715-8f5b-61bf7c3d1b62") +hydrometer.get_telemetry("2aa3b02c-78de-4715-8f5b-61bf7c3d1b62", "2025-10-07T12:51:07.3321557+00:00", "2025-10-08T12:51:07.3321557+00:00", "") + diff --git a/src/rapt/__init__.py b/src/rapt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/rapt/client.py b/src/rapt/client.py new file mode 100644 index 0000000..54f4c4d --- /dev/null +++ b/src/rapt/client.py @@ -0,0 +1,65 @@ +import requests +from datetime import datetime, timedelta +from loguru import logger + + +class ClientException(Exception): + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message + +class HttpClientException(ClientException): + def __init__(self, message, http_code): + self.message = message + self.http_code = http_code + logger.error(str(self)) + + def __str__(self): + return "["+str(self.http_code)+"] - " + self.message + + + +class Client(): + def __init__(self, username, api_key) -> None: + self.host = "https://api.rapt.io" + self.username = username + self.api_key = api_key + self.token = None + self.expires_seconds = None + self.expires = None + + def get_jwt_token(self): + url = "https://id.rapt.io/connect/token" + response = requests.post(url, data={ + "client_id": "rapt-user", + "grant_type": "password", + "username": self.username, + "password": self.api_key + }) + + if response.status_code == 200: + response_json = response.json() + self.token = response_json["access_token"] + self.expires_seconds = response_json["expires_in"] + self.expires = datetime.now() + timedelta(seconds=self.expires_seconds) + logger.trace("Expires: " + str(self.expires)) + else: + raise HttpClientException(response.reason, response.status_code) + + def get_auth_headers(self): + #TODO: Check expiry + if self.expires is None or self.expires <= datetime.now(): + self.get_jwt_token() + + return {"Authorization": "Bearer {}".format(self.token)} + + def get_json(self, url, parameters): + headers = self.get_auth_headers() + response = requests.get(self.host+url, headers=headers, params=parameters) + logger.trace(response.request.url) + if response.status_code == 200: + return response.json() + else: + raise HttpClientException(response.reason, response.status_code) diff --git a/src/rapt/fermentation_chamber.py b/src/rapt/fermentation_chamber.py new file mode 100644 index 0000000..5b8fb70 --- /dev/null +++ b/src/rapt/fermentation_chamber.py @@ -0,0 +1,53 @@ +from rapt.client import ClientException +from rapt.model.fermentation_chamber import FermentationChamberModel, FermentationChamberTelemetryModel +from typing import Optional, List +from pydantic import TypeAdapter +from loguru import logger + + +class FermentationChamber(): + def __init__(self, client) -> None: + self.client = client + + def get_fermentation_chambers(self) -> Optional[List[FermentationChamberModel]]: + logger.debug("## GET FERMENTATION CHAMBERS ##") + try: + response_json = self.client.get_json("/api/fermentationchambers/getfermentationchambers", None) + logger.trace(response_json) + fermentation_chambers_adapter = TypeAdapter(list[FermentationChamberModel]) + return fermentation_chambers_adapter.validate_python(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + + def get_fermentation_chamber(self, fermentation_chamber_id) -> Optional[FermentationChamberModel]: + logger.debug("## GET FERMENTATION CHAMBER ##") + params = { + "fermentationChamberId": fermentation_chamber_id + } + try: + response_json = self.client.get_json("/api/fermentationchambers/getfermentationchamber", params) + logger.trace(response_json) + return FermentationChamberModel.model_validate(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + + + def get_telemetry(self, fermentation_chamber_id, start_date, end_date, profile_session_id) -> Optional[List[FermentationChamberTelemetryModel]]: + logger.debug("## GET TELEMETRY ##") + params = { + "fermentationChamberId": fermentation_chamber_id, + "startDate": start_date, + "endDate": end_date, + "profileSessionId": profile_session_id + } + try: + response_json = self.client.get_json("/api/fermentationchambers/gettelemetry", params) + logger.trace(response_json) + fermentation_chambers_adapter = TypeAdapter(list[FermentationChamberTelemetryModel]) + return fermentation_chambers_adapter.validate_python(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + diff --git a/src/rapt/hydrometer.py b/src/rapt/hydrometer.py new file mode 100644 index 0000000..01ccf5a --- /dev/null +++ b/src/rapt/hydrometer.py @@ -0,0 +1,53 @@ +from rapt.client import ClientException +from rapt.model.hydrometer import HydrometerModel, HydrometerTelemetryModel +from pydantic import TypeAdapter +from typing import Optional, List +from loguru import logger + + +class Hydrometer(): + def __init__(self, client) -> None: + self.client = client + + def get_hydrometers(self) -> Optional[List[HydrometerModel]]: + logger.debug("## GET HYDROMETERS ##") + try: + response_json = self.client.get_json("/api/hydrometers/gethydrometers", None) + logger.trace(response_json) + hydrometers_adapter = TypeAdapter(list[HydrometerModel]) + return hydrometers_adapter.validate_python(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + + def get_hydrometer(self, hydrometer_id) -> Optional[HydrometerModel]: + logger.debug("## GET HYDROMETER ##") + params = { + "hydrometerId": hydrometer_id + } + try: + response_json = self.client.get_json("/api/hydrometers/gethydrometer", params) + logger.trace(response_json) + return HydrometerModel.model_validate(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + + + def get_telemetry(self, hydrometer_id, start_date, end_date, profile_session_id) -> Optional[List[HydrometerTelemetryModel]]: + logger.debug("## GET TELEMETRY ##") + params = { + "hydrometerId": hydrometer_id, + "startDate": start_date, + "endDate": end_date, + "profileSessionId": profile_session_id + } + try: + response_json = self.client.get_json("/api/hydrometers/gettelemetry", params) + logger.trace(response_json) + hydrometers_adapter = TypeAdapter(list[HydrometerTelemetryModel]) + return hydrometers_adapter.validate_python(response_json) + except ClientException: + #TODO: Handle Exception gracefully + return None + diff --git a/src/rapt/model/__init__.py b/src/rapt/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/rapt/model/amount.py b/src/rapt/model/amount.py new file mode 100644 index 0000000..66bbe8d --- /dev/null +++ b/src/rapt/model/amount.py @@ -0,0 +1,6 @@ +from enum import Enum + +class AmountTypes(str, Enum): + Weight = "Weight", + Volume = "Volume", + Units = "Units" diff --git a/src/rapt/model/base.py b/src/rapt/model/base.py new file mode 100644 index 0000000..f7cc276 --- /dev/null +++ b/src/rapt/model/base.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, Field +from datetime import datetime +from uuid import UUID + + +class RaptBaseModel(BaseModel): + id: UUID + deleted: bool + created_on: datetime = Field(alias="createdOn") + created_by: UUID = Field(alias="createdBy") + modified_on: datetime = Field(alias="modifiedOn") + modified_by: UUID = Field(alias="modifiedBy") + diff --git a/src/rapt/model/device.py b/src/rapt/model/device.py new file mode 100644 index 0000000..3a7c533 --- /dev/null +++ b/src/rapt/model/device.py @@ -0,0 +1,39 @@ +from enum import Enum + + +class DeviceTypes(str, Enum): + FermentationChamber = "FermentationChamber", + TemperatureController = "TemperatureController", + Hydrometer = "Hydrometer", + GrainWeigh = "GrainWeigh", + BrewZilla = "BrewZilla", + CanFiller = "CanFiller", + GlycolChiller = "GlycolChiller", + Still = "Still", + BLETemperature = "BLETemperature", + BLEHumidity = "BLEHumidity", + BLETempHumidity = "BLETempHumidity", + BLEPressure = "BLEPressure", + External = "External", + Fridge = "Fridge", + GrainWeighDevice = "GrainWeighDevice", + Unknown = "Unknown" + +class DeviceTypesNullable(str, Enum): + FermentationChamber = "FermentationChamber", + TemperatureController = "TemperatureController", + Hydrometer = "Hydrometer", + GrainWeigh = "GrainWeigh", + BrewZilla = "BrewZilla", + CanFiller = "CanFiller", + GlycolChiller = "GlycolChiller", + Still = "Still", + BLETemperature = "BLETemperature", + BLEHumidity = "BLEHumidity", + BLETempHumidity = "BLETempHumidity", + BLEPressure = "BLEPressure", + External = "External", + Fridge = "Fridge", + GrainWeighDevice = "GrainWeighDevice", + Unknown = "Unknown" + null = "null" diff --git a/src/rapt/model/fermentation_chamber.py b/src/rapt/model/fermentation_chamber.py new file mode 100644 index 0000000..0b2a041 --- /dev/null +++ b/src/rapt/model/fermentation_chamber.py @@ -0,0 +1,102 @@ +from rapt.model.base import RaptBaseModel +from rapt.model.profile import ProfileSessionModel, ProfileSessionStatusModel +from rapt.model.device import DeviceTypes +from rapt.model.toggle import ToggleStates +from datetime import datetime +from uuid import UUID +from pydantic import BaseModel, Field +from typing import Optional, List + + +class FermentationChamberTelemetryModel(BaseModel): + id: UUID + row_key: Optional[str] = Field(alias="rowKey", default=None) + created_on: datetime = Field(alias="createdOn") + mac_address: Optional[str] = Field(alias="macAddress", default=None) + rssi: float + control_device_type: Optional[str] = Field(alias="controlDeviceType", default=None) + control_device_mac_address: Optional[str] = Field(alias="controlDeviceMacAddress", default=None) + control_device_temperature: Optional[float] = Field(alias="controlDeviceTemperature", default=None) + temperature: float + target_temperature: Optional[float] = Field(alias="targetTemperature", default=None) + min_target_temperature: Optional[float] = Field(alias="minTargetTemperature", default=None) + max_target_temperature: Optional[float] = Field(alias="maxTargetTemperature", default=None) + total_run_time: float = Field(alias="totalRunTime") + compressor_run_time: float = Field(alias="compressorRunTime") + compressor_starts: float = Field(alias="compressorStarts") + heating_run_time: float = Field(alias="heatingRunTime") + heating_starts: float = Field(alias="heatingStarts") + auxillary_run_time: float = Field(alias="auxillaryRunTime") + auxillary_starts: float = Field(alias="auxillaryStarts") + profile_id: Optional[UUID] = Field(alias="profileId", default=None) + profile_step_id: Optional[UUID] = Field(alias="profileStepId", default=None) + profile_session_start_date: Optional[datetime] = Field(alias="profileSessionStartDate", default=None) + profile_session_time: Optional[int] = Field(alias="profileSessionTime", default=None) + profile_step_progress: Optional[int] = Field(alias="profileStepProgress", default=None) + +class FermentationChamberModel(RaptBaseModel): + name: str + serial_number: Optional[str] = Field(alias="serialNumber", default=None) + mac_address: str = Field(alias="macAddress") + device_type: DeviceTypes = Field(alias="deviceType") + active: bool + disabled: bool + username: Optional[str] = None + connection_state: Optional[str] = Field(alias="connectionState", default=None) + status: Optional[str] = None + error: Optional[str] = None + last_activity_time: Optional[datetime] = Field(alias="lastActivityTime", default=None) + rssi: float + firmware_version: Optional[str] = Field(alias="firmwareVersion", default=None) + is_latest_firmware: bool = Field(alias="isLatestFirmware") + active_profile_id: Optional[UUID] = Field(alias="activeProfileId", default=None) + active_profile_step_id: Optional[UUID] = Field(alias="activeProfileStepId", default=None) + active_profile_session: ProfileSessionStatusModel = Field(alias="activeProfileSession") + profile_sessions: Optional[List[ProfileSessionModel]] = Field(alias="profileSessions", default=None) + beta_updates: bool = Field(alias="betaUpdates") + bluetooth_enabled: bool = Field(alias="bluetoothEnabled") + graph_zoom_level: float = Field(alias="graphZoomLevel") + temperature: float + target_temperature: Optional[float] = Field(alias="targetTemperature", default=None) + min_target_temperature: Optional[float] = Field(alias="minTargetTemperature", default=None) + max_target_temperature: Optional[float] = Field(alias="maxTargetTemperature", default=None) + total_run_time: float = Field(alias="totalRunTime") + cooling_enabled: bool = Field(alias="coolingEnabled") + cooling_run_time: float = Field(alias="coolingRunTime") + cooling_starts: float = Field(alias="coolingStarts") + heating_enabled: bool = Field(alias="heatingEnabled") + heating_run_time: float = Field(alias="heatingRunTime") + heating_starts: float = Field(alias="heatingStarts") + heating_utilisation: float = Field(alias="heatingUtilisation") + high_temp_alarm: float = Field(alias="highTempAlarm") + low_temp_alarm: float = Field(alias="lowTempAlarm") + ntc_beta: float = Field(alias="ntcBeta") + ntc_ref_resistance: float = Field(alias="ntcRefResistance") + ntc_ref_temperature: float = Field(alias="ntcRefTemperature") + pid_cycle_time: float = Field(alias="pidCycleTime") + pid_enabled: bool = Field(alias="pidEnabled") + pid_proportional: float = Field(alias="pidProportional") + pid_integral: float = Field(alias="pidIntegral") + pid_derivative: float = Field(alias="pidDerivative") + sensor_differential: float = Field(alias="sensorDifferential") + sensor_timeout: float = Field(alias="sensorTimeout") + show_graph: bool = Field(alias="showGraph") + sounds_enabled: bool = Field(alias="soundsEnabled") + temp_unit: Optional[str] = Field(alias="tempUnit", default=None) + use_internal_sensor: bool = Field(alias="useInternalSensor") + control_device_type: Optional[str] = Field(alias="controlDeviceType", default=None) + control_device_mac_address: Optional[str] = Field(alias="controlDeviceMacAddress", default=None) + control_device_temperature: Optional[float] = Field(alias="controlDeviceTemperature", default=None) + customer_use: str = Field(alias="customerUse") + telemetry_frequency: int = Field(alias="telemetryFrequency") + compressor_delay: float = Field(alias="compressorDelay") + mode_switch_delay: float = Field(alias="modeSwitchDelay") + cooling_hysteresis: float = Field(alias="coolingHysteresis") + heating_hysteresis: float = Field(alias="heatingHysteresis") + telemetry: Optional[List[FermentationChamberTelemetryModel]] = None + compressor_run_time: float = Field(alias="compressorRunTime") + compressor_starts: float = Field(alias="compressorStarts") + auxillary_run_time: float = Field(alias="auxillaryRunTime") + auxillary_starts: float = Field(alias="auxillaryStarts") + fan_enabled: bool = Field(alias="fanEnabled") + light_enabled: ToggleStates = Field(alias="lightEnabled") diff --git a/src/rapt/model/hydrometer.py b/src/rapt/model/hydrometer.py new file mode 100644 index 0000000..3a7cb84 --- /dev/null +++ b/src/rapt/model/hydrometer.py @@ -0,0 +1,46 @@ +from datetime import datetime +from uuid import UUID +from pydantic import BaseModel, Field +from typing import Optional, List +from rapt.model.base import RaptBaseModel +from rapt.model.device import DeviceTypes, DeviceTypesNullable +from rapt.model.profile import ProfileSessionStatusModel + + +class HydrometerTelemetryModel(BaseModel): + id: UUID + row_key: Optional[str] = Field(alias="rowKey") + created_on: datetime = Field(alias="createdOn") + mac_address: Optional[str] = Field(alias="macAddress") + rssi: float + temperature: float + gravity: float + gravity_velocity: float = Field(alias="gravityVelocity") + battery: float + version: Optional[str] + +class HydrometerModel(RaptBaseModel): + name: str + serial_number: Optional[str] = Field(alias="serialNumber", default=None) + mac_address: str = Field(alias="macAddress") + device_type: DeviceTypes = Field(alias="deviceType") + active: bool + disabled: bool + username: Optional[str] = None + connection_state: Optional[str] = Field(alias="connectionState", default=None) + status: Optional[str] = None + error: Optional[str] = None + last_activity_time: Optional[datetime] = Field(alias="lastActivityTime") + rssi: float + firmware_version: Optional[str] = Field(alias="firmwareVersion") + is_latest_firmware: bool = Field(alias="isLatestFirmware") + active_profile_id: Optional[UUID] = Field(alias="activeProfileId", default=None) + active_profile_step: Optional[UUID] = Field(alias="activeProfileStepId", default=None) + active_profile_session: Optional[ProfileSessionStatusModel] = Field(alias="activeProfileSession", default=None) + telemetry: Optional[List[HydrometerTelemetryModel]] + paired_device_type: Optional[DeviceTypesNullable] = Field(alias="pairedDeviceType", default=None) + paired_device_id: Optional[UUID] = Field(alias="pairedDeviceId", default=None) + temperature: float + gravity: float + gravity_velocity: float = Field(alias="gravityVelocity") + battery: float diff --git a/src/rapt/model/profile.py b/src/rapt/model/profile.py new file mode 100644 index 0000000..2390cc4 --- /dev/null +++ b/src/rapt/model/profile.py @@ -0,0 +1,134 @@ +from pydantic import Field +from typing import Optional, List +from enum import Enum +from datetime import datetime +from uuid import UUID +from rapt.model.base import RaptBaseModel +from rapt.model.yeast import YeastModel + + +class ProfileAlertTriggers(str, Enum): + StepStart = "StepStart", + StepEnd = "StepEnd", + Temperature = "Temperature", + Gravity = "Gravity", + GravityVelocity = "GravityVelocity", + Duration = "Duration" + +class ProfileValueOperatorsNullable(str, Enum): + Equals = "Equals", + LessThan = "LessThan", + GreaterThan = "GreaterThan", + null = "null" + +class ProfileStepControlTypes(str, Enum): + Target = "Target", + Ramp = "Ramp", + FreeRise = "FreeRise" + +class ProfileStepEndTypes(str, Enum): + Duration = "Duration", + Temperature = "Temperature", + Gravity = "Gravity", + GravityVelocity = "GravityVelocity", + Manual = "Manual" + +class ProfileStepDurationTypesNullable(str, Enum): + Start = "Start", + Temperature = "Temperature", + Gravity = "Gravity", + GravityVelocity = "GravityVelocity", + Manual = "Manual", + null = "null" + +class ProfileAlertModel(RaptBaseModel): + alert_text: str = Field(alias="alertText") + trigger: ProfileAlertTriggers + operator: ProfileValueOperatorsNullable + temperature: float + gravity: float + profile_id: UUID = Field(alias="profileId") + +class ProfileStepAlertModel(RaptBaseModel): + alert_text: Optional[str] = Field(alias="alertText") + trigger: ProfileAlertTriggers + operator: Optional[ProfileValueOperatorsNullable] + temperature: Optional[float] + gravity: Optional[float] + length: Optional[int] + profile_step_id: UUID = Field(alias="profileStepId") + +class ProfileStepModel(RaptBaseModel): + name: Optional[str] + order: int + control_type: ProfileStepControlTypes = Field(alias="controlType") + end_type: ProfileStepEndTypes = Field(alias="endType") + duration_type: Optional[ProfileStepDurationTypesNullable] = Field(alias="durationType") + operator: Optional[ProfileValueOperatorsNullable] + length: Optional[int] + temperature: Optional[float] + min_temperature: Optional[float] = Field(alias="minTemperature") + max_temperature: Optional[float] = Field(alias="maxTemperature") + gravity: Optional[float] + pump_enabled: Optional[bool] = Field(alias="pumpEnabled") + pump_utilisation: Optional[float] = Field(alias="pumpUtilisation") + heating_utilisation: Optional[float] = Field(alias="heatingUtilisation") + pid_enabled: Optional[bool] = Field(alias="pidEnabled") + sensor_differential: Optional[float] = Field(alias="sensorDifferential") + profile_id: UUID = Field(alias="profileId") + alerts: Optional[List[ProfileStepAlertModel]] + +class ProfileSessionModel(RaptBaseModel): + name: Optional[str] + description: Optional[str] + profile_id: Optional[UUID] = Field(alias="profileId") +# profile: ProfileModel + brewzilla_id: Optional[UUID] = Field(alias="brewZillaId") + fermentation_chamger_id: Optional[UUID] = Field(alias="fermentationChamberId") + hydrometer_id: Optional[UUID] = Field(alias="hydrometerId") + still_id: Optional[UUID] = Field(alias="stillId") + temperature_controller_id: Optional[UUID] = Field(alias="temperatureControllerId") + start_date: Optional[datetime] = Field(alias="startDate") + end_date: Optional[datetime] = Field(alias="endDate") + original_gravity: Optional[float] = Field(alias="originalGravity") + final_gravity: Optional[float] = Field(alias="finalGravity") + yeast_id: Optional[UUID] = Field(alias="yeastId") + yeast: YeastModel + sent_alerts: List[Optional[ProfileAlertModel]] = Field(alias="sentAlerts") + +class ProfileModel(RaptBaseModel): + name: str + description: Optional[str] + public: bool + profile_name: Optional[str] = Field(alias="profileName") + rating: float + rating_count: int = Field(alias="ratingCount") + rating_score: float = Field(alias="ratingScore") + copy_count: float = Field(alias="copyCount") + view_count: float = Field(alias="viewCount") + profile_type_id: Optional[UUID] = Field(alias="profileTypeId") + alerts: Optional[List[ProfileAlertModel]] + steps: Optional[List[ProfileStepModel]] + profile_sessions: Optional[List[ProfileSessionModel]] = Field(alias="profileSessions") + +class ProfileSessionStatusModel(RaptBaseModel): + name: Optional[str] + description: Optional[str] + profile_id: Optional[UUID] = Field(alias="profileId") + profile: ProfileModel + brewzilla_id: Optional[UUID] = Field(alias="brewZillaId") + fermentation_chamger_id: Optional[UUID] = Field(alias="fermentationChamberId") + hydrometer_id: Optional[UUID] = Field(alias="hydrometerId") + still_id: Optional[UUID] = Field(alias="stillId") + temperature_controller_id: Optional[UUID] = Field(alias="temperatureControllerId") + start_date: Optional[datetime] = Field(alias="startDate") + end_date: Optional[datetime] = Field(alias="endDate") + original_gravity: Optional[float] = Field(alias="originalGravity") + final_gravity: Optional[float] = Field(alias="finalGravity") + yeast_id: Optional[UUID] = Field(alias="yeastId") + yeast: YeastModel + sent_alerts: List[Optional[ProfileAlertModel]] = Field(alias="sentAlerts") + estimated_end_date: Optional[datetime] = Field(alias="estimatedEndDate") + profile_length: Optional[float] = Field(alias="profileLength") + current_profile_time: Optional[float] = Field(alias="currentProfileTime") + remaining_profile_time: Optional[float] = Field(alias="remainingProfileTime") diff --git a/src/rapt/model/toggle.py b/src/rapt/model/toggle.py new file mode 100644 index 0000000..d69b0c2 --- /dev/null +++ b/src/rapt/model/toggle.py @@ -0,0 +1,6 @@ +from enum import Enum + +class ToggleStates(str, Enum): + AlwaysOn = "AlwaysOn", + AlwaysOff = "AlwaysOff", + Automatic = "Automatic" diff --git a/src/rapt/model/yeast.py b/src/rapt/model/yeast.py new file mode 100644 index 0000000..9a93450 --- /dev/null +++ b/src/rapt/model/yeast.py @@ -0,0 +1,29 @@ +from pydantic import Field +from typing import Optional +from uuid import UUID +from rapt.model.base import RaptBaseModel +from rapt.model.amount import AmountTypes + + +class YeastModel(RaptBaseModel): + name: Optional[str] + product_id: Optional[str] = Field(alias="productId") + laboratory: Optional[str] + supplier: Optional[str] + kegland_product_code: Optional[str] = Field(alias="keglandProductCode") + yeast_type: Optional[str] = Field(alias="type") + form: Optional[str] + min_temperature: float = Field(alias="minTemperature") + max_temperature: float = Field(alias="maxTemperature") + flocculation: Optional[str] + attenuation: float + notes: Optional[str] + best_for: Optional[str] = Field(alias="bestFor") + max_reuse: int = Field(alias="maxReuse") + add_to_secondary: bool = Field(alias="addToSecondary") + amount_types: AmountTypes = Field(alias="amountType") + inventory_amount: Optional[float] = Field(alias="inventoryAmount") + archived: bool + stock_level: float = Field(alias="stockLevel") + global_yeast_id: Optional[UUID] = Field(alias="globalYeastId") + is_global: bool = Field(alias="isGlobal") diff --git a/tests/json/get_fermentation_chamber_response.json b/tests/json/get_fermentation_chamber_response.json new file mode 100644 index 0000000..abc05f1 --- /dev/null +++ b/tests/json/get_fermentation_chamber_response.json @@ -0,0 +1,384 @@ +{ + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "serialNumber": "string", + "macAddress": "string", + "deviceType": "FermentationChamber", + "active": true, + "disabled": true, + "username": "string", + "connectionState": "string", + "status": "string", + "error": "string", + "lastActivityTime": "2025-10-09T11:56:29.302Z", + "rssi": 0, + "firmwareVersion": "string", + "isLatestFirmware": true, + "activeProfileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "activeProfileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "activeProfileSession": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "public": true, + "profileName": "string", + "rating": 0, + "ratingCount": 0, + "ratingScore": 0, + "copyCount": 0, + "viewCount": 0, + "profileTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "steps": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "order": 0, + "controlType": "Target", + "endType": "Duration", + "durationType": "Start", + "operator": "Equals", + "length": 0, + "temperature": 0, + "minTemperature": 0, + "maxTemperature": 0, + "gravity": 0, + "pumpEnabled": true, + "pumpUtilisation": 0, + "heatingUtilisation": 0, + "pidEnabled": true, + "sensorDifferential": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "length": 0, + "profileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ], + "profileSessions": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": "string", + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:56:29.302Z", + "endDate": "2025-10-09T11:56:29.302Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ] + }, + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:56:29.302Z", + "endDate": "2025-10-09T11:56:29.302Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "estimatedEndDate": "2025-10-09T11:56:29.302Z", + "profileLength": 0, + "currentProfileTime": 0, + "remainingProfileTime": 0 + }, + "profileSessions": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": "string", + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:56:29.302Z", + "endDate": "2025-10-09T11:56:29.302Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:56:29.302Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:56:29.302Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ], + "betaUpdates": true, + "bluetoothEnabled": true, + "graphZoomLevel": 0, + "temperature": 0, + "targetTemperature": 0, + "minTargetTemperature": 0, + "maxTargetTemperature": 0, + "totalRunTime": 0, + "coolingEnabled": true, + "coolingRunTime": 0, + "coolingStarts": 0, + "heatingEnabled": true, + "heatingRunTime": 0, + "heatingStarts": 0, + "heatingUtilisation": 0, + "highTempAlarm": 0, + "lowTempAlarm": 0, + "ntcBeta": 0, + "ntcRefResistance": 0, + "ntcRefTemperature": 0, + "pidCycleTime": 0, + "pidEnabled": true, + "pidProportional": 0, + "pidIntegral": 0, + "pidDerivative": 0, + "sensorDifferential": 0, + "sensorTimeout": 0, + "showGraph": true, + "soundsEnabled": true, + "tempUnit": "string", + "useInternalSensor": true, + "controlDeviceType": "string", + "controlDeviceMacAddress": "string", + "controlDeviceTemperature": 0, + "customerUse": "string", + "telemetryFrequency": 14440, + "compressorDelay": 10, + "modeSwitchDelay": 30, + "coolingHysteresis": 10, + "heatingHysteresis": 10, + "telemetry": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "rowKey": "string", + "createdOn": "2025-10-09T11:56:29.302Z", + "macAddress": "string", + "rssi": 0, + "controlDeviceType": "string", + "controlDeviceMacAddress": "string", + "controlDeviceTemperature": 0, + "temperature": 0, + "targetTemperature": 0, + "minTargetTemperature": 0, + "maxTargetTemperature": 0, + "totalRunTime": 0, + "compressorRunTime": 0, + "compressorStarts": 0, + "heatingRunTime": 0, + "heatingStarts": 0, + "auxillaryRunTime": 0, + "auxillaryStarts": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileSessionStartDate": "2025-10-09T11:56:29.302Z", + "profileSessionTime": 0, + "profileStepProgress": 0 + } + ], + "compressorRunTime": 0, + "compressorStarts": 0, + "auxillaryRunTime": 0, + "auxillaryStarts": 0, + "fanEnabled": true, + "lightEnabled": "AlwaysOn" +} diff --git a/tests/json/get_fermentation_chamber_telemetry_response.json b/tests/json/get_fermentation_chamber_telemetry_response.json new file mode 100644 index 0000000..de48686 --- /dev/null +++ b/tests/json/get_fermentation_chamber_telemetry_response.json @@ -0,0 +1,28 @@ +[ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "rowKey": "string", + "createdOn": "2025-10-09T09:29:04.638Z", + "macAddress": "string", + "rssi": 0, + "controlDeviceType": "string", + "controlDeviceMacAddress": "string", + "controlDeviceTemperature": 0, + "temperature": 0, + "targetTemperature": 0, + "minTargetTemperature": 0, + "maxTargetTemperature": 0, + "totalRunTime": 0, + "compressorRunTime": 0, + "compressorStarts": 0, + "heatingRunTime": 0, + "heatingStarts": 0, + "auxillaryRunTime": 0, + "auxillaryStarts": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileSessionStartDate": "2025-10-09T09:29:04.638Z", + "profileSessionTime": 0, + "profileStepProgress": 0 + } +] diff --git a/tests/json/get_fermentation_chambers_response.json b/tests/json/get_fermentation_chambers_response.json new file mode 100644 index 0000000..a67cc4e --- /dev/null +++ b/tests/json/get_fermentation_chambers_response.json @@ -0,0 +1,386 @@ +[ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "serialNumber": "string", + "macAddress": "string", + "deviceType": "FermentationChamber", + "active": true, + "disabled": true, + "username": "string", + "connectionState": "string", + "status": "string", + "error": "string", + "lastActivityTime": "2025-10-09T11:13:05.885Z", + "rssi": 0, + "firmwareVersion": "string", + "isLatestFirmware": true, + "activeProfileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "activeProfileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "activeProfileSession": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "public": true, + "profileName": "string", + "rating": 0, + "ratingCount": 0, + "ratingScore": 0, + "copyCount": 0, + "viewCount": 0, + "profileTypeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "steps": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "order": 0, + "controlType": "Target", + "endType": "Duration", + "durationType": "Start", + "operator": "Equals", + "length": 0, + "temperature": 0, + "minTemperature": 0, + "maxTemperature": 0, + "gravity": 0, + "pumpEnabled": true, + "pumpUtilisation": 0, + "heatingUtilisation": 0, + "pidEnabled": true, + "sensorDifferential": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "length": 0, + "profileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ], + "profileSessions": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": "string", + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:13:05.885Z", + "endDate": "2025-10-09T11:13:05.885Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ] + }, + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:13:05.885Z", + "endDate": "2025-10-09T11:13:05.885Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "estimatedEndDate": "2025-10-09T11:13:05.885Z", + "profileLength": 0, + "currentProfileTime": 0, + "remainingProfileTime": 0 + }, + "profileSessions": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "description": "string", + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profile": "string", + "brewZillaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fermentationChamberId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "hydrometerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "temperatureControllerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "startDate": "2025-10-09T11:13:05.885Z", + "endDate": "2025-10-09T11:13:05.885Z", + "originalGravity": 0, + "finalGravity": 0, + "yeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "yeast": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "productId": "string", + "laboratory": "string", + "supplier": "string", + "keglandProductCode": "string", + "type": "string", + "form": "string", + "minTemperature": 0, + "maxTemperature": 0, + "flocculation": "string", + "attenuation": 0, + "notes": "string", + "bestFor": "string", + "maxReuse": 0, + "addToSecondary": true, + "amountType": "Weight", + "inventoryAmount": 0, + "archived": true, + "stockLevel": 0, + "globalYeastId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "isGlobal": true + }, + "sentAlerts": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "deleted": true, + "createdOn": "2025-10-09T11:13:05.885Z", + "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "modifiedOn": "2025-10-09T11:13:05.885Z", + "modifiedBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "alertText": "string", + "trigger": "StepStart", + "operator": "Equals", + "temperature": 0, + "gravity": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ] + } + ], + "betaUpdates": true, + "bluetoothEnabled": true, + "graphZoomLevel": 0, + "temperature": 0, + "targetTemperature": 0, + "minTargetTemperature": 0, + "maxTargetTemperature": 0, + "totalRunTime": 0, + "coolingEnabled": true, + "coolingRunTime": 0, + "coolingStarts": 0, + "heatingEnabled": true, + "heatingRunTime": 0, + "heatingStarts": 0, + "heatingUtilisation": 0, + "highTempAlarm": 0, + "lowTempAlarm": 0, + "ntcBeta": 0, + "ntcRefResistance": 0, + "ntcRefTemperature": 0, + "pidCycleTime": 0, + "pidEnabled": true, + "pidProportional": 0, + "pidIntegral": 0, + "pidDerivative": 0, + "sensorDifferential": 0, + "sensorTimeout": 0, + "showGraph": true, + "soundsEnabled": true, + "tempUnit": "string", + "useInternalSensor": true, + "controlDeviceType": "string", + "controlDeviceMacAddress": "string", + "controlDeviceTemperature": 0, + "customerUse": "string", + "telemetryFrequency": 14440, + "compressorDelay": 10, + "modeSwitchDelay": 30, + "coolingHysteresis": 10, + "heatingHysteresis": 10, + "telemetry": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "rowKey": "string", + "createdOn": "2025-10-09T11:13:05.885Z", + "macAddress": "string", + "rssi": 0, + "controlDeviceType": "string", + "controlDeviceMacAddress": "string", + "controlDeviceTemperature": 0, + "temperature": 0, + "targetTemperature": 0, + "minTargetTemperature": 0, + "maxTargetTemperature": 0, + "totalRunTime": 0, + "compressorRunTime": 0, + "compressorStarts": 0, + "heatingRunTime": 0, + "heatingStarts": 0, + "auxillaryRunTime": 0, + "auxillaryStarts": 0, + "profileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "profileSessionStartDate": "2025-10-09T11:13:05.885Z", + "profileSessionTime": 0, + "profileStepProgress": 0 + } + ], + "compressorRunTime": 0, + "compressorStarts": 0, + "auxillaryRunTime": 0, + "auxillaryStarts": 0, + "fanEnabled": true, + "lightEnabled": "AlwaysOn" + } +] diff --git a/tests/json/get_hydrometer_response.json b/tests/json/get_hydrometer_response.json new file mode 100644 index 0000000..be8927f --- /dev/null +++ b/tests/json/get_hydrometer_response.json @@ -0,0 +1,35 @@ +{ + "telemetry": [ + { + "temperature": 17.5625, + "gravity": 1325.07, + "gravityVelocity": 0, + "battery": 0, + "version": "20250319_055542_b3788ba", + "id": "68423584-1a35-4341-b67c-78456f4ae9f3", + "rowKey": "2516424593326678442", + "createdOn": "2025-10-07T12:51:07.3321557+00:00", + "macAddress": "ac-15-18-df-84-94", + "rssi": -56 + } + ], + "temperature": 17.5625, + "gravity": 1325.07, + "gravityVelocity": 0, + "battery": 0, + "name": "Hegnsgården Yellow", + "macAddress": "ac-15-18-df-84-94", + "deviceType": "Hydrometer", + "active": false, + "disabled": false, + "lastActivityTime": "2025-10-07T12:51:07.3321557+00:00", + "rssi": -56, + "firmwareVersion": "20250319_055542_b3788ba", + "isLatestFirmware": true, + "modifiedOn": "2025-10-07T12:51:07.3603727+00:00", + "modifiedBy": "00000000-0000-0000-0000-000000000000", + "id": "2aa3b02c-78de-4715-8f5b-61bf7c3d1b62", + "deleted": false, + "createdOn": "2025-10-07T09:44:01.1515163+00:00", + "createdBy": "475ebc33-8e90-4be5-2424-08ddfbe49482" +} diff --git a/tests/json/get_hydrometer_telemetry_response.json b/tests/json/get_hydrometer_telemetry_response.json new file mode 100644 index 0000000..1800948 --- /dev/null +++ b/tests/json/get_hydrometer_telemetry_response.json @@ -0,0 +1,14 @@ +[ + { + "temperature": 17.5625, + "gravity": 1325.07, + "gravityVelocity": 0, + "battery": 0, + "version": "20250319_055542_b3788ba", + "id": "50d5885c-b637-4343-bb47-cc76e9f3c0c1", + "rowKey": "2516424593326678442", + "createdOn": "2025-10-07T12:51:07.3321557+00:00", + "macAddress": "ac-15-18-df-84-94", + "rssi": -56 + } +] diff --git a/tests/json/get_hydrometers_response.json b/tests/json/get_hydrometers_response.json new file mode 100644 index 0000000..4909218 --- /dev/null +++ b/tests/json/get_hydrometers_response.json @@ -0,0 +1,37 @@ +[ + { + "telemetry": [ + { + "temperature": 17.5625, + "gravity": 1325.07, + "gravityVelocity": 0, + "battery": 0, + "version": "20250319_055542_b3788ba", + "id": "55462886-4400-4608-b283-3ce4426dafd3", + "rowKey": "2516424593326678442", + "createdOn": "2025-10-07T12:51:07.3321557+00:00", + "macAddress": "ac-15-18-df-84-94", + "rssi": -56 + } + ], + "temperature": 17.5625, + "gravity": 1325.07, + "gravityVelocity": 0, + "battery": 0, + "name": "Hegnsgården Yellow", + "macAddress": "ac-15-18-df-84-94", + "deviceType": "Hydrometer", + "active": false, + "disabled": false, + "lastActivityTime": "2025-10-07T12:51:07.3321557+00:00", + "rssi": -56, + "firmwareVersion": "20250319_055542_b3788ba", + "isLatestFirmware": false, + "modifiedOn": "2025-10-07T12:51:07.3603727+00:00", + "modifiedBy": "00000000-0000-0000-0000-000000000000", + "id": "2aa3b02c-78de-4715-8f5b-61bf7c3d1b62", + "deleted": false, + "createdOn": "2025-10-07T09:44:01.1515163+00:00", + "createdBy": "475ebc33-8e90-4be5-2424-08ddfbe49482" + } +] diff --git a/tests/json/token_response.json b/tests/json/token_response.json new file mode 100644 index 0000000..8c6da2d --- /dev/null +++ b/tests/json/token_response.json @@ -0,0 +1,6 @@ +{ + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkYzM0E0QzYzNkZFOUE3RjBFNzVGQkRBQTQ1REIwOTdBQjUxRUVGMTlSUzI1NiIsIng1dCI6Ijh6cE1ZMl9wcF9Eblg3MnFSZHNKZXJVZTd4ayIsInR5cCI6ImF0K2p3dCJ9.eyJpc3MiOiJodHRwczovL2lkLnJhcHQuaW8iLCJuYmYiOjE3NTk5MjcyMjQsImlhdCI6MTc1OTkyNzIyNCwiZXhwIjoxNzU5OTMwODI0LCJhdWQiOiJyYXB0LWFwaSIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJyYXB0LWFwaSIsInJhcHQtYXBpLnB1YmxpYyJdLCJhbXIiOlsiY3VzdG9tIl0sImNsaWVudF9pZCI6InJhcHQtdXNlciIsInN1YiI6IjQ3NWViYzMzLThlOTAtNGJlNS0yNDI0LTA4ZGRmYmU0OTQ4MiIsImF1dGhfdGltZSI6MTc1OTkyNzIyNCwiaWRwIjoibG9jYWwiLCJmaXJzdG5hbWUiOiJKZXNwZXIiLCJsYXN0bmFtZSI6IkZ1c3NpbmcgTVx1MDBGOHJrIiwiY291bnRyeSI6IkRlbm1hcmsiLCJlbWFpbCI6ImpmbUBtb2Vya3MuZGsiLCJwcm9maWxlbmFtZSI6ImpmbSIsImVtYWlsY29uZmlybWVkIjoiZmFsc2UiLCJqdGkiOiI3RDM3QjcxMDNBMTc4QkQ0RkMwM0ZCRDNGNTBFMDY2MiJ9.HJtC6Cfps5rqsukMYFRi7S4awpz0KqJjSX1EdL1K6NMpxVEmqmiiA3LwXrsjCbu2FoLjswxyM7rCFqVBZnnQ71DcQJ9gJCe1Ddonj-l5SfA90KHjlvLPQOhvCLrYHc9ulDEi64Dz-WfH8V7WCy1fgl3lY8xmRstS8rjO-5jVwQFFlZvyOHoke09iMsRQmCyuiGYbqmn1i31VSK6L3fMuRfrc1o165T1Xts66vAupJ6nqEhHnAusvCM2gagEvAiXx3hH3xxid4K6zCQLvbg0jxKUHnKI9q2xVj30bW0C2_lKSDKpOOTLjWpjrwjVag5DYxYFggNVgTvF90f_hYddxFX8s9_dgY6EbTunSA1ki86r82Rw7Ebvfwy1phjNHllgu85Y73tIYADcQYxzjglNKPX1Dzed8liUOAXTHgL2Z9-4_q8bwmgNrGy9eH4K_PjyqEuuhDvy2GQxjhOojOXJOU-aSaE4KwaHTod1CZB-kaikMAKjsHDTi3cRspFU4sli665zgPZ2UKYsjTWSlqoKpA7wO3RrMKQiCrNEqyE0ldOi1ctCS-dBEBkuhc-THTwOqMDQTQ6Iqqk1dK72Nm8uB5TwPur-TP4YYxi1hY6w0jSOs0JzUVpaMGgAXg3CM_zhWgTE9S44R3gjxX6EsXayI5f5WtstxpHMbB3rO_DrZPjA", + "expires_in": 3600, + "token_type": "Bearer", + "scope": "openid profile rapt-api rapt-api.public" +} diff --git a/tests/test_fermentation_chamber.py b/tests/test_fermentation_chamber.py new file mode 100644 index 0000000..3cbcdb2 --- /dev/null +++ b/tests/test_fermentation_chamber.py @@ -0,0 +1,83 @@ +import pytest +import json +import responses +from rapt.client import Client +from rapt.fermentation_chamber import FermentationChamber + + +@pytest.fixture +def client(): + yield Client("test", "test") + +@responses.activate +def test_get_fermentation_chambers(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/fermentationchambers/getfermentationchambers", json=json_reader("./tests/json/get_fermentation_chambers_response.json")) + fermentation_chamber = FermentationChamber(client) + ferms = fermentation_chamber.get_fermentation_chambers() + + assert ferms is not None + assert len(ferms) == 1 + +@responses.activate +def test_get_fermentation_chambers_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/fermentationchambers/getfermentationchambers", + status=500, + ) + fermentation_chamber = FermentationChamber(client) + ferms = fermentation_chamber.get_fermentation_chambers() + + assert ferms is None + +@responses.activate +def test_get_fermentation_chamber(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/fermentationchambers/getfermentationchamber", json=json_reader("./tests/json/get_fermentation_chamber_response.json")) + fermentation_chamber = FermentationChamber(client) + ferms = fermentation_chamber.get_fermentation_chamber("") + + assert ferms is not None + +@responses.activate +def test_get_fermentation_chamber_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/fermentationchambers/getfermentationchamber", + status=500, + ) + fermentation_chamber = FermentationChamber(client) + ferms = fermentation_chamber.get_fermentation_chamber("") + + assert ferms is None + +@responses.activate +def test_get_telemetry(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/fermentationchambers/gettelemetry", json=json_reader("./tests/json/get_fermentation_chamber_telemetry_response.json")) + fermentation_chamber = FermentationChamber(client) + telemetry = fermentation_chamber.get_telemetry("", "", "", "") + + assert telemetry is not None + assert len(telemetry) == 1 + +@responses.activate +def test_get_telemetry_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/fermentationchambers/gettelemetry", + status=500, + ) + fermentation_chamber = FermentationChamber(client) + telemetry = fermentation_chamber.get_telemetry("", "", "", "") + + assert telemetry is None + + +def json_reader(path): + with open(path) as f: + return json.load(f) diff --git a/tests/test_hydrometer.py b/tests/test_hydrometer.py new file mode 100644 index 0000000..ad5bcb4 --- /dev/null +++ b/tests/test_hydrometer.py @@ -0,0 +1,83 @@ +import pytest +import json +import responses +from rapt.client import Client +from rapt.hydrometer import Hydrometer + + +@pytest.fixture +def client(): + yield Client("test", "test") + +@responses.activate +def test_get_hydrometers(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/hydrometers/gethydrometers", json=json_reader("./tests/json/get_hydrometers_response.json")) + hydrometer = Hydrometer(client) + hydros = hydrometer.get_hydrometers() + + assert hydros is not None + assert len(hydros) == 1 + +@responses.activate +def test_get_hydrometers_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/hydrometers/gethydrometers", + status=500, + ) + hydrometer = Hydrometer(client) + hydros = hydrometer.get_hydrometers() + + assert hydros is None + +@responses.activate +def test_get_hydrometer(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/hydrometers/gethydrometer", json=json_reader("./tests/json/get_hydrometer_response.json")) + hydrometer = Hydrometer(client) + hydros = hydrometer.get_hydrometer("") + + assert hydros is not None + +@responses.activate +def test_get_hydrometer_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/hydrometers/gethydrometer", + status=500, + ) + hydrometer = Hydrometer(client) + hydros = hydrometer.get_hydrometer("") + + assert hydros is None + + +@responses.activate +def test_get_telemetry(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.get("https://api.rapt.io/api/hydrometers/gettelemetry", json=json_reader("./tests/json/get_hydrometer_telemetry_response.json")) + hydrometer = Hydrometer(client) + telemetry = hydrometer.get_telemetry("", "", "", "") + + assert telemetry is not None + +@responses.activate +def test_get_telemetry_500(client): + responses.post("https://id.rapt.io/connect/token", json=json_reader("./tests/json/token_response.json")) + responses.add( + responses.GET, + "https://api.rapt.io/api/hydrometers/gettelemetry", + status=500, + ) + hydrometer = Hydrometer(client) + telemetry = hydrometer.get_telemetry("", "", "", "") + + assert telemetry is None + + +def json_reader(path): + with open(path) as f: + return json.load(f)