pax_global_header00006660000000000000000000000064147104052050014510gustar00rootroot0000000000000052 comment=28f43b36eb2fab54af9d91a97a48431c7593a62b Renson-endura-delta-library-1.7.2/000077500000000000000000000000001471040520500167705ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/.github/000077500000000000000000000000001471040520500203305ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/.github/workflows/000077500000000000000000000000001471040520500223655ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/.github/workflows/main.yml000066400000000000000000000020051471040520500240310ustar00rootroot00000000000000name: Python package on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest Renson-endura-delta-library-1.7.2/.github/workflows/master.yml000066400000000000000000000024361471040520500244100ustar00rootroot00000000000000name: Production Python package on: push: branches: - master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: '3.9' architecutre: 'x64' - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest - name: build package run: | pip install wheel twine python setup.py sdist bdist_wheel && twine check dist/* - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} Renson-endura-delta-library-1.7.2/.gitignore000066400000000000000000000034251471040520500207640ustar00rootroot00000000000000# 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/ venv/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .idea/ Renson-endura-delta-library-1.7.2/README.md000066400000000000000000000010071471040520500202450ustar00rootroot00000000000000# Renson endura delta library An unofficial library to get and control the Renson Endura Delta units. Following features are support: - Getting data for most of the fields that can be found in the official 'Endura Delta' app. - Override the auto mode and set a level manually - Set a timer for a level - Sync the current time to the Renson ventilation unit - Setup the breeze function - Setup the day and night time - Setup pollution settings - Set the filter clean time - Compare firmware version with online versionRenson-endura-delta-library-1.7.2/renson_endura_delta/000077500000000000000000000000001471040520500230035ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/renson_endura_delta/__init__.py000066400000000000000000000000001471040520500251020ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/renson_endura_delta/field_enum.py000066400000000000000000000057101471040520500254670ustar00rootroot00000000000000"""Enum of all the possible fields that can be read.""" from renson_endura_delta.general_enum import DataType class FieldEnum: """Enum of all the possible fields that can be read.""" name: str = None field_type: DataType = None def __init__(self, name: str, field_type: DataType): """Create enum with values name and field type.""" self.name = name self.field_type = field_type FIRMWARE_VERSION = FieldEnum("Firmware version", DataType.STRING) CO2_QUALITY_FIELD = FieldEnum("CO2", DataType.QUALITY) AIR_QUALITY_FIELD = FieldEnum("IAQ", DataType.QUALITY) CO2_FIELD = FieldEnum("CO2", DataType.NUMERIC) AIR_FIELD = FieldEnum("IAQ", DataType.NUMERIC) CURRENT_LEVEL_FIELD = FieldEnum("Current ventilation level", DataType.LEVEL) CURRENT_AIRFLOW_EXTRACT_FIELD = FieldEnum("Current ETA airflow", DataType.NUMERIC) CURRENT_AIRFLOW_INGOING_FIELD = FieldEnum("Current SUP airflow", DataType.NUMERIC) OUTDOOR_TEMP_FIELD = FieldEnum("T21", DataType.NUMERIC) INDOOR_TEMP_FIELD = FieldEnum("T11", DataType.NUMERIC) FILTER_REMAIN_FIELD = FieldEnum("Filter remaining time", DataType.NUMERIC) HUMIDITY_FIELD = FieldEnum("RH11", DataType.NUMERIC) FROST_PROTECTION_FIELD = FieldEnum("Frost protection active", DataType.BOOLEAN) MANUAL_LEVEL_FIELD = FieldEnum("Manual level", DataType.STRING) TIME_AND_DATE_FIELD = FieldEnum("Date and time", DataType.STRING) BREEZE_TEMPERATURE_FIELD = FieldEnum("Breeze activation temperature", DataType.NUMERIC) BREEZE_ENABLE_FIELD = FieldEnum("Breeze enable", DataType.BOOLEAN) BREEZE_LEVEL_FIELD = FieldEnum("Breeze level", DataType.STRING) DAYTIME_FIELD = FieldEnum("Start daytime", DataType.STRING) NIGHTTIME_FIELD = FieldEnum("Start night-time", DataType.STRING) DAY_POLLUTION_FIELD = FieldEnum("Day pollution-triggered ventilation level", DataType.STRING) NIGHT_POLLUTION_FIELD = FieldEnum("Night pollution-triggered ventilation level", DataType.STRING) HUMIDITY_CONTROL_FIELD = FieldEnum("Trigger internal pollution alert on RH", DataType.BOOLEAN) AIR_QUALITY_CONTROL_FIELD = FieldEnum("Trigger internal pollution alert on IAQ", DataType.BOOLEAN) CO2_CONTROL_FIELD = FieldEnum("Trigger internal pollution alert on CO2", DataType.BOOLEAN) CO2_THRESHOLD_FIELD = FieldEnum("CO2 threshold", DataType.NUMERIC) CO2_HYSTERESIS_FIELD = FieldEnum("CO2 hysteresis", DataType.NUMERIC) BREEZE_MET_FIELD = FieldEnum("Breeze conditions met", DataType.BOOLEAN) PREHEATER_FIELD = FieldEnum("Preheater enabled", DataType.BOOLEAN) BYPASS_TEMPERATURE_FIELD = FieldEnum("Bypass activation temperature", DataType.NUMERIC) BYPASS_LEVEL_FIELD = FieldEnum("Bypass level", DataType.STRING) FILTER_PRESET_FIELD = FieldEnum("Filter preset time", DataType.NUMERIC) DEVICE_NAME_FIELD = FieldEnum("Device name", DataType.STRING) FIRMWARE_VERSION_FIELD = FieldEnum("Firmware version", DataType.STRING) HARDWARE_VERSION_FIELD = FieldEnum("Hardware version", DataType.STRING) DEVICE_TYPE = FieldEnum("Device type", DataType.STRING) MAC_ADDRESS = FieldEnum("MAC", DataType.STRING) Renson-endura-delta-library-1.7.2/renson_endura_delta/general_enum.py000066400000000000000000000034201471040520500260150ustar00rootroot00000000000000"""File that contain all generic enums.""" from enum import Enum class ExtendedEnum(Enum): """Special enum for creating list of it.""" @classmethod def list(cls): """Get list of all values in the enum.""" return list(map(lambda c: c.value, cls)) class Quality(ExtendedEnum): """Enum with all quality values.""" GOOD = "good" POOR = "poor" BAD = "bad" class Level(ExtendedEnum): """Enum with all level values.""" OFF = "Off" LEVEL1 = "Level1" LEVEL2 = "Level2" LEVEL3 = "Level3" LEVEL4 = "Level4" BREEZE = "Breeze" HOLIDAY = "Holiday" class DataType(ExtendedEnum): """Enum with all data types the library can return.""" NUMERIC = "numeric" STRING = "string" LEVEL = "level" QUALITY = "quality" BOOLEAN = "boolean" class ServiceNames(ExtendedEnum): """All the service fields of the Renson ventilation unit.""" SET_MANUAL_LEVEL_FIELD = "Manual level" TIME_AND_DATE_FIELD = "Date and time" TIMER_FIELD = "Ventilation timer" BREEZE_TEMPERATURE_FIELD = "Breeze activation temperature" BREEZE_ENABLE_FIELD = "Breeze enable" BREEZE_LEVEL_FIELD = "Breeze level" DAYTIME_FIELD = "Start daytime" NIGHTTIME_FIELD = "Start night-time" DAY_POLLUTION_FIELD = "Day pollution-triggered ventilation level" NIGHT_POLLUTION_FIELD = "Night pollution-triggered ventilation level" HUMIDITY_CONTROL_FIELD = "Trigger internal pollution alert on RH" AIR_QUALITY_CONTROL_FIELD = "Trigger internal pollution alert on IAQ" CO2_CONTROL_FIELD = "Trigger internal pollution alert on CO2" CO2_THRESHOLD_FIELD = "CO2 threshold" CO2_HYSTERESIS_FIELD = "CO2 hysteresis" FILTER_DAYS_FIELD = "Filter preset time" FILTER_REMAIN_FIELD = "Filter remaining time" Renson-endura-delta-library-1.7.2/renson_endura_delta/renson.py000066400000000000000000000302531471040520500246640ustar00rootroot00000000000000"""Main class of the Renson ventilation library.""" import json import logging from datetime import datetime import re import requests from renson_endura_delta.field_enum import FILTER_PRESET_FIELD, FieldEnum, FIRMWARE_VERSION, FIRMWARE_VERSION_FIELD from renson_endura_delta.general_enum import (Level, Quality, ServiceNames, DataType, Level) _LOGGER = logging.getLogger(__name__) class ValueData: """Class for getting Renson data.""" def __init__(self, value): """Construct the class.""" self.Value = value class RensonVentilation: """Main class to get data and post data to the Renson unit.""" data_url = "http://[host]/JSON/ModifiedItems?wsn=150324488709" service_url = "http://[host]/JSON/Vars/[field]?index0=0&index1=0&index2=0" firmware_server_url = "http://www.renson-app.com/endura_delta/firmware/check.php" firmware_dowload_url = "http://www.renson-app.com/endura_delta/firmware/files/" host = None def __init__(self, host: str): """Initialize Renson Ventilation class by giving the host name or ip address.""" self.host = host def connect(self) -> bool: try: response = requests.get(self.data_url.replace("[host]", self.host)) return response.status_code == 200 except Exception: return False def get_all_data(self): response = requests.get(self.data_url.replace("[host]", self.host)) if response.status_code == 200: return response.json() else: _LOGGER.error(f"Error communicating with API: {response.status_code}") return '' def get_field_value(self, all_data, fieldname: str) -> str: """Search for the field in the Reson JSON and return the value of it.""" for data in all_data["ModifiedItems"]: if data["Name"] == fieldname == FIRMWARE_VERSION_FIELD.name: return data["Value"].split()[-1] elif data["Name"] == fieldname: return data["Value"] return '' def parse_value(self, value, data_type): """Parse value to correct type""" if data_type == DataType.NUMERIC: return self.parse_numeric(value) elif data_type == DataType.STRING: return value elif data_type == DataType.LEVEL: return self.parse_data_level(value).value elif data_type == DataType.BOOLEAN: return self.parse_boolean(value) elif data_type == DataType.QUALITY: return self.parse_quality(value).value def __get_service_url(self, field: ServiceNames): """Make the full url of the Renson API and return it.""" return self.service_url.replace("[host]", self.host).replace( "[field]", field.value.replace(" ", "%20") ) def __get_base_url(self, path: str): """Make the base url of the Renson API and return it.""" return "http://" + self.host + path def parse_numeric(self, value: str) -> float: """Get the value of the field and convert it to a numeric type.""" return round(float(value)) def parse_data_level(self, value: str) -> Level: """Get the value of the field and convert it to a Level type.""" return Level[value.split()[-1].upper()] def parse_boolean(self, value: str) -> bool: """Get the value of the field and convert it to a boolean type.""" return bool(int(value)) def parse_quality(self, value: str) -> Quality: """Get the value of the field and convert it to a Quality type.""" value = round(float(value)) if value < 950: return Quality.GOOD elif value < 1500: return Quality.POOR else: return Quality.BAD def set_manual_level(self, level: Level): """Set the manual level of the Renson unit. When set to 'Off' the unit will go back to auto program.""" data = ValueData(level.value) response = requests.post( self.__get_service_url(ServiceNames.SET_MANUAL_LEVEL_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def restart_device(self): """Restart device""" response = requests.post( self.__get_base_url("/Reset") ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def sync_time(self): """Sync time of the Renson unit to current date and time.""" response = requests.get(self.__get_service_url(ServiceNames.TIME_AND_DATE_FIELD)) if response.status_code == 200: json_result = response.json() device_time = datetime.strptime( json_result["Value"], "%d %b %Y %H:%M" ) current_time = datetime.now() if current_time != device_time: data = ValueData(current_time.strftime("%d %b %Y %H:%M").lower()) requests.post( self.__get_service_url(ServiceNames.TIME_AND_DATE_FIELD), data=json.dumps(data.__dict__) ) else: _LOGGER.error("Ventilation unit did not return 200") def set_timer_level(self, level: Level, time: int): """Set a level for a specific time (in minutes).""" if level == Level.OFF: raise Exception("Off is not a valid type for setting manual level") data = ValueData(str(time) + " min " + level.value) response = requests.post(self.__get_service_url(ServiceNames.TIMER_FIELD), data=json.dumps(data.__dict__)) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def set_breeze(self, level: Level, temperature: int, activated: bool): """Activate/deactivate breeze feature and give breeze parameters to the function.""" if level == Level.HOLIDAY or level == Level.OFF or level == Level.BREEZE: raise Exception("Holiday, Off, Breeze are not a valid types for setting breeze level") data = ValueData(str(level.value)) response = requests.post( self.__get_service_url(ServiceNames.BREEZE_LEVEL_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(temperature)) response = requests.post( self.__get_service_url(ServiceNames.BREEZE_TEMPERATURE_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(activated))) response = requests.post( self.__get_service_url(ServiceNames.BREEZE_ENABLE_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def set_day_time(self, day: str): """Set day time for the device.""" data = ValueData(day) response = requests.post(self.__get_service_url(ServiceNames.DAYTIME_FIELD), data=json.dumps(data.__dict__)) if response.status_code != 200: _LOGGER.error("Start daytime cannot be set") def set_night_time(self, night: str): """Set night time for the device.""" data = ValueData(night) response = requests.post(self.__get_service_url(ServiceNames.NIGHTTIME_FIELD), data=json.dumps(data.__dict__)) if response.status_code != 200: _LOGGER.error("Start nighttime cannot be set") def set_pollution(self, day: Level, night: Level, humidity_control: bool, airquality_control: bool, co2_control: bool, co2_threshold: bool, co2_hysteresis: bool): """Enable/disable special auto features of the Renson unit.""" if day == Level.HOLIDAY or day == Level.OFF or day == Level.BREEZE: raise Exception("Holiday, Off, Breeze are not a valid types for setting day level") if night == Level.HOLIDAY or night == Level.OFF or night == Level.BREEZE: raise Exception("Holiday, Off, Breeze are not a valid types for setting night level") data = ValueData(str(day.value)) response = requests.post( self.__get_service_url(ServiceNames.DAY_POLLUTION_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(night.value)) response = requests.post( self.__get_service_url(ServiceNames.NIGHT_POLLUTION_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(humidity_control))) response = requests.post( self.__get_service_url(ServiceNames.HUMIDITY_CONTROL_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(airquality_control))) response = requests.post( self.__get_service_url(ServiceNames.AIR_QUALITY_CONTROL_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(co2_control))) response = requests.post( self.__get_service_url(ServiceNames.CO2_CONTROL_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(co2_threshold))) response = requests.post( self.__get_service_url(ServiceNames.CO2_THRESHOLD_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") data = ValueData(str(int(co2_hysteresis))) response = requests.post( self.__get_service_url(ServiceNames.CO2_HYSTERESIS_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def set_filter_days(self, days: int): """Set the filter days.""" data = ValueData(str(int(days))) response = requests.post( self.__get_service_url(ServiceNames.FILTER_DAYS_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def set_remaining_filter_days(self, days: int): """Set the remaining filter days.""" data = ValueData(str(int(days))) response = requests.post( self.__get_service_url(ServiceNames.FILTER_REMAIN_FIELD), data=json.dumps(data.__dict__) ) if response.status_code != 200: _LOGGER.error("Ventilation unit did not return 200") def is_firmware_up_to_date(self, current_version) -> bool: """Check if the Renson firmware is up to date.""" # version = self.get_field_value(FIRMWARE_VERSION).split()[-1] version = current_version.split()[-1] json_string = '{"a":"check", "name":"D_' + version + '.fuf"}' response_server = requests.post(self.firmware_server_url, data=json_string) if response_server.status_code == 200: return bool((response_server.json())["latest"]) return False def get_latest_firmware_version(self) -> str: """Get the latest Renson firmware version.""" json_string = '{"a":"check", "name":"D_0.fuf"}' response_server = requests.post(self.firmware_server_url, data=json_string) if response_server.status_code == 200: return re.sub(r"D_(.*)\.fuf", r"\1", response_server.json()["url"]) return "" def reset_filter(self): """Reset filter timer""" data = self.get_all_data() filter_preset = self.parse_numeric(self.get_field_value(data, FILTER_PRESET_FIELD.name)) self.set_remaining_filter_days(filter_preset) Renson-endura-delta-library-1.7.2/requirements.txt000066400000000000000000000001111471040520500222450ustar00rootroot00000000000000pytest==7.1.2 pytest-runner==5.3.1 requests==2.26.0 requests-mock==1.9.3 Renson-endura-delta-library-1.7.2/setup.py000066400000000000000000000015731471040520500205100ustar00rootroot00000000000000import pathlib from setuptools import find_packages, setup # The directory containing this file HERE = pathlib.Path(__file__).parent # The text of the README file README = (HERE / "README.md").read_text() setup( name='renson_endura_delta', packages=find_packages(include=['renson_endura_delta']), version='1.7.2', description='Unofficial Renson endura delta Python library', long_description=README, long_description_content_type="text/markdown", author='JimmyD-be', license='MIT', install_requires=['requests>=2.26.0'], setup_requires=['pytest-runner'], tests_require=['pytest', 'requests_mock>=1.9.3'], test_suite='tests', url="https://github.com/jimmyd-be/Renson-endura-delta-library", project_urls={ "Bug Tracker": "https://github.com/jimmyd-be/Renson-endura-delta-library/issues", }, python_requires=">=3.6" ) Renson-endura-delta-library-1.7.2/tests/000077500000000000000000000000001471040520500201325ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/tests/__init__.py000066400000000000000000000000001471040520500222310ustar00rootroot00000000000000Renson-endura-delta-library-1.7.2/tests/test_data.py000066400000000000000000000437211471040520500224630ustar00rootroot00000000000000import requests_mock import pytest from renson_endura_delta.field_enum import (CO2_FIELD, CURRENT_LEVEL_FIELD, FROST_PROTECTION_FIELD, MANUAL_LEVEL_FIELD, CURRENT_AIRFLOW_EXTRACT_FIELD, CO2_QUALITY_FIELD) from renson_endura_delta.general_enum import Level, Quality from renson_endura_delta.renson import RensonVentilation responseText: str = '{"ModifiedItems":[{"Name":"Device type","Index":[0,0,0],"Value":"ED 330 T2\/B2 L SHT IAQ CO2 ' \ 'W02"},{"Name":"MAC","Index":[0,0,0],"Value":"11:22:AA:BC:B2:45"},{"Name":"Warranty number",' \ '"Index":[0,0,0],"Value":"test"},{"Name":"Registration key","Index":[0,0,0],"Value":"F2685FEC"},' \ '{"Name":"Firmware version","Index":[0,0,0],"Value":"Endura Delta 0.0.67"},{"Name":"Hardware ' \ 'version","Index":[0,0,0],"Value":"7"},{"Name":"Device name","Index":[0,0,0],"Value":"Endura ' \ 'Delta"},{"Name":"Static IP Address","Index":[0,0,0],"Value":"192.168.0.45"},{"Name":"Static ' \ 'subnet mask","Index":[0,0,0],"Value":"255.255.255.0"},{"Name":"Static gateway address",' \ '"Index":[0,0,0],"Value":"192.168.0.1"},{"Name":"DHCP Enabled","Index":[0,0,0],"Value":"1"},' \ '{"Name":"Week program points per day","Index":[0,0,0],"Value":"2"},{"Name":"Week program points ' \ 'per day","Index":[0,0,1],"Value":"2"},{"Name":"Week program points per day","Index":[0,0,2],' \ '"Value":"2"},{"Name":"Week program points per day","Index":[0,0,3],"Value":"2"},{"Name":"Week ' \ 'program points per day","Index":[0,0,4],"Value":"2"},{"Name":"Week program points per day",' \ '"Index":[0,0,5],"Value":"2"},{"Name":"Week program points per day","Index":[0,0,6],"Value":"2"},' \ '{"Name":"Week program time","Index":[0,0,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,0,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,0,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,0,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,0,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,0,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,1,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,1,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,1,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,1,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,1,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,1,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,2,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,2,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,2,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,2,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,2,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,2,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,3,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,3,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,3,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,3,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,3,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,3,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,4,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,4,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,4,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,4,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,4,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,4,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,5,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,5,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,5,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,5,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,5,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,5,5],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,6,0],"Value":"7:00"},{"Name":"Week program time",' \ '"Index":[0,6,1],"Value":"22:00"},{"Name":"Week program time","Index":[0,6,2],"Value":"0:00"},' \ '{"Name":"Week program time","Index":[0,6,3],"Value":"0:00"},{"Name":"Week program time",' \ '"Index":[0,6,4],"Value":"0:00"},{"Name":"Week program time","Index":[0,6,5],"Value":"0:00"},' \ '{"Name":"Week program level","Index":[0,0,0],"Value":"Level2"},{"Name":"Week program level",' \ '"Index":[0,0,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,0,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,0,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,0,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,0,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,1,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,1,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,1,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,1,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,1,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,1,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,2,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,2,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,2,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,2,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,2,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,2,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,3,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,3,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,3,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,3,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,3,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,3,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,4,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,4,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,4,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,4,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,4,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,4,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,5,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,5,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,5,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,5,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,5,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,5,5],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,6,0],"Value":"Level2"},{"Name":"Week ' \ 'program level","Index":[0,6,1],"Value":"Level1"},{"Name":"Week program level","Index":[0,6,2],' \ '"Value":"Level1"},{"Name":"Week program level","Index":[0,6,3],"Value":"Level1"},{"Name":"Week ' \ 'program level","Index":[0,6,4],"Value":"Level1"},{"Name":"Week program level","Index":[0,6,5],' \ '"Value":"Level1"},{"Name":"Date and time","Index":[0,0,0],"Value":"22 aug 2021 13:12"},' \ '{"Name":"Current program level","Index":[0,0,0],"Value":"Level2"},{"Name":"Breeze activation ' \ 'temperature","Index":[0,0,0],"Value":"20"},{"Name":"Breeze conditions met","Index":[0,0,0],' \ '"Value":"1"},{"Name":"Breeze enable","Index":[0,0,0],"Value":"0"},{"Name":"QualiSensor error ' \ 'count","Index":[0,0,0],"Value":"0"},{"Name":"QualiSensor pollution alert","Index":[0,0,0],' \ '"Value":"0"},{"Name":"Trigger internal pollution alert on RH","Index":[0,0,0],"Value":"1"},' \ '{"Name":"Trigger internal pollution alert on IAQ","Index":[0,0,0],"Value":"1"},{"Name":"Trigger ' \ 'internal pollution alert on CO2","Index":[0,0,0],"Value":"1"},{"Name":"Internal RH pollution ' \ 'alert","Index":[0,0,0],"Value":"0"},{"Name":"Internal IAQ pollution alert","Index":[0,0,0],' \ '"Value":"1"},{"Name":"Internal CO2 pollution alert","Index":[0,0,0],"Value":"0"},' \ '{"Name":"External pollution alert","Index":[0,0,0],"Value":"0"},{"Name":"CO2 threshold",' \ '"Index":[0,0,0],"Value":"600"},{"Name":"CO2 hysteresis","Index":[0,0,0],"Value":"100"},' \ '{"Name":"Start daytime","Index":[0,0,0],"Value":"7:00"},{"Name":"Start night-time","Index":[0,0,' \ '0],"Value":"21:30"},{"Name":"Day pollution-triggered ventilation level","Index":[0,0,0],' \ '"Value":"Level3"},{"Name":"Night pollution-triggered ventilation level","Index":[0,0,0],' \ '"Value":"Level2"},{"Name":"Current pollution level","Index":[0,0,0],"Value":"Level3"},' \ '{"Name":"Ventilation timer","Index":[0,0,0],"Value":"0 min Level4"},{"Name":"Manual level",' \ '"Index":[0,0,0],"Value":"Off"},{"Name":"Current ventilation level","Index":[0,0,0],"Value":"Auto ' \ 'Level3"},{"Name":"Fireplace remaining time","Index":[0,0,0],"Value":"0"},{"Name":"Fireplace ' \ 'preset time","Index":[0,0,0],"Value":"15"},{"Name":"Fireplace flow delta","Index":[0,0,0],' \ '"Value":"30"},{"Name":"Total nominal airflow","Index":[0,0,0],"Value":"340"},{"Name":"Level1 ' \ 'airflow percentage","Index":[0,0,0],"Value":"25"},{"Name":"Level2 airflow percentage",' \ '"Index":[0,0,0],"Value":"50"},{"Name":"Level3 airflow percentage","Index":[0,0,0],"Value":"75"},' \ '{"Name":"Level4 airflow percentage","Index":[0,0,0],"Value":"100"},{"Name":"Breeze level",' \ '"Index":[0,0,0],"Value":"Level3"},{"Name":"Target SUP airflow","Index":[0,0,0],' \ '"Value":"255.000000"},{"Name":"Target ETA airflow","Index":[0,0,0],"Value":"255.000000"},' \ '{"Name":"Current SUP airflow","Index":[0,0,0],"Value":"255.000000"},{"Name":"Current ETA ' \ 'airflow","Index":[0,0,0],"Value":"255.000000"},{"Name":"Measured SUP airflow","Index":[0,0,0],' \ '"Value":"255.491486"},{"Name":"Measured ETA airflow","Index":[0,0,0],"Value":"265.873413"},' \ '{"Name":"SUP fan active","Index":[0,0,0],"Value":"1"},{"Name":"ETA fan active","Index":[0,0,0],' \ '"Value":"1"},{"Name":"Unbalance","Index":[0,0,0],"Value":"0"},{"Name":"T11","Index":[0,0,0],' \ '"Value":"23.710419"},{"Name":"RH11","Index":[0,0,0],"Value":"66.509766"},{"Name":"T21",' \ '"Index":[0,0,0],"Value":"23.039999"},{"Name":"T21bis","Index":[0,0,0],"Value":"-63.030334"},' \ '{"Name":"T22","Index":[0,0,0],"Value":"23.360001"},{"Name":"T12","Index":[0,0,0],' \ '"Value":"23.493334"},{"Name":"RH12","Index":[0,0,0],"Value":"67.327538"},{"Name":"IAQ",' \ '"Index":[0,0,0],"Value":"2496"},{"Name":"CO2","Index":[0,0,0],"Value":"533"},{"Name":"Bypass ' \ 'activation temperature","Index":[0,0,0],"Value":"23"},{"Name":"Bypass level","Index":[0,0,0],' \ '"Value":"100"},{"Name":"Frost protection active","Index":[0,0,0],"Value":"0"},{"Name":"Preheater ' \ 'enabled","Index":[0,0,0],"Value":"0"},{"Name":"Preheater power","Index":[0,0,0],"Value":"0"},' \ '{"Name":"Filter used time","Index":[0,0,0],"Value":"31"},{"Name":"Filter remaining time",' \ '"Index":[0,0,0],"Value":"149"},{"Name":"Filter preset time","Index":[0,0,0],"Value":"180"},' \ '{"Name":"Region","Index":[0,0,0],"Value":"Belgium"},{"Name":"Input 1 function","Index":[0,0,0],' \ '"Value":"0"},{"Name":"Input 2 function","Index":[0,0,0],"Value":"0"},{"Name":"Input 3 function",' \ '"Index":[0,0,0],"Value":"0"},{"Name":"Input 1 value","Index":[0,0,0],"Value":"0"},{"Name":"Input ' \ '2 value","Index":[0,0,0],"Value":"0"},{"Name":"Input 3 value","Index":[0,0,0],' \ '"Value":"0.000000"},{"Name":"Output 1 function","Index":[0,0,0],"Value":"0"},{"Name":"Output 2 ' \ 'function","Index":[0,0,0],"Value":"0"},{"Name":"Output 3 function","Index":[0,0,0],"Value":"0"},' \ '{"Name":"Output 1 value","Index":[0,0,0],"Value":"0"},{"Name":"Output 2 value","Index":[0,0,0],' \ '"Value":"0"},{"Name":"Output 3 value","Index":[0,0,0],"Value":"0.000000"},{"Name":"Registration ' \ 'complete","Index":[0,0,0],"Value":"1"},{"Name":"Error list","Index":[0,0,0],"Value":""},' \ '{"Name":"Error list","Index":[0,0,1],"Value":""},{"Name":"Error list","Index":[0,0,2],' \ '"Value":""},{"Name":"Error list","Index":[0,0,3],"Value":""},{"Name":"Error list","Index":[0,0,' \ '4],"Value":""},{"Name":"System startup","Index":[0,0,0],"Value":"1"},{"Name":"SD card mounted",' \ '"Index":[0,0,0],"Value":"1"},{"Name":"SUP fan voltage","Index":[0,0,0],"Value":"8.697593"},' \ '{"Name":"ETA fan voltage","Index":[0,0,0],"Value":"6.807638"},{"Name":"SUP fan speed",' \ '"Index":[0,0,0],"Value":"2364"},{"Name":"ETA fan speed","Index":[0,0,0],"Value":"1839"},' \ '{"Name":"SUP constant flow sensor value","Index":[0,0,0],"Value":"54.845024"},{"Name":"ETA ' \ 'constant flow sensor value","Index":[0,0,0],"Value":"35.548702"},{"Name":"SUP flow offset",' \ '"Index":[0,0,0],"Value":"0.000000"},{"Name":"ETA flow offset","Index":[0,0,0],' \ '"Value":"0.000000"}],"Wsn":34363714635} ' def test_connect_with_connection(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") assert data.connect() def test_connect_without_connection(): data = RensonVentilation("example.mock") assert not data.connect() def test_get_data_numeric(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() value = data.get_field_value(all_data, CO2_FIELD.name) assert data.parse_numeric(value) == 533 def test_get_data_with_cache(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() co2_value = data.get_field_value(all_data, CO2_FIELD.name) current_level_value = data.get_field_value(all_data, CURRENT_LEVEL_FIELD.name) assert data.parse_numeric(co2_value) == 533 assert data.get_field_value(all_data, MANUAL_LEVEL_FIELD.name) == "Off" assert data.parse_data_level(current_level_value) == Level.LEVEL3 assert mock.called assert mock.called_once def test_get_data_string(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() assert data.get_field_value(all_data, CURRENT_LEVEL_FIELD.name) == "Auto Level3" def test_get_data_level(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() value = data.get_field_value(all_data, CURRENT_LEVEL_FIELD.name) assert data.parse_data_level(value) == Level.LEVEL3 def test_get_data_boolean(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() value = data.get_field_value(all_data, FROST_PROTECTION_FIELD.name) assert not data.parse_boolean(value) def test_get_data_quality(): with requests_mock.Mocker() as mock: mock.get("http://example.mock/JSON/ModifiedItems?wsn=150324488709", text=responseText) data = RensonVentilation("example.mock") all_data = data.get_all_data() value = data.get_field_value(all_data, CO2_QUALITY_FIELD.name) assert data.parse_quality(value) == Quality.GOOD Renson-endura-delta-library-1.7.2/tests/test_service_calls.py000066400000000000000000000013571471040520500243670ustar00rootroot00000000000000import requests_mock from renson_endura_delta.general_enum import Level from renson_endura_delta.renson import RensonVentilation as Services def test_set_manual_level(): with requests_mock.Mocker() as m: m.post("http://example.mock/JSON/Vars/Manual%20level?index0=0&index1=0&index2=0", text='ok') service = Services("example.mock") service.set_manual_level(Level.LEVEL2) assert m.called def test_set_filter_days(): with requests_mock.Mocker() as m: m.post("http://example.mock/JSON/Vars/Filter%20preset%20time?index0=0&index1=0&index2=0", text="{'Value'='30'}") service = Services("example.mock") service.set_filter_days(30) assert m.called