pax_global_header00006660000000000000000000000064147407721460014526gustar00rootroot0000000000000052 comment=0334f95f6ce6b79c9a91a96ef7ada8b940d82f8a dotvav-py-palazzetti-api-0334f95/000077500000000000000000000000001474077214600166725ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/.coveragerc000066400000000000000000000000241474077214600210070ustar00rootroot00000000000000[run] omit = tests/*dotvav-py-palazzetti-api-0334f95/.github/000077500000000000000000000000001474077214600202325ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/.github/workflows/000077500000000000000000000000001474077214600222675ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/.github/workflows/publish.yml000066400000000000000000000025031474077214600244600ustar00rootroot00000000000000name: Publish Python Package on: release: types: [created] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: '**/pyproject.toml' - name: Install dependencies run: | pip install -r requirements.txt -r requirements-test.txt - name: Run tests run: | pytest --cov-branch --cov-report=xml - name: Upload results to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} deploy: runs-on: ubuntu-latest needs: [test] environment: release permissions: id-token: write steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' cache: pip cache-dependency-path: '**/pyproject.toml' - name: Install dependencies run: | pip install setuptools wheel build - name: Build run: | python -m build - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 dotvav-py-palazzetti-api-0334f95/.gitignore000066400000000000000000000061031474077214600206620ustar00rootroot00000000000000# 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/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # 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. #.idea/ dotvav-py-palazzetti-api-0334f95/.vscode/000077500000000000000000000000001474077214600202335ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/.vscode/launch.json000066400000000000000000000007321474077214600224020ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python Debugger: Example.py", "type": "debugpy", "request": "launch", "program": "example.py", "console": "integratedTerminal" } ] }dotvav-py-palazzetti-api-0334f95/.vscode/settings.json000066400000000000000000000005451474077214600227720ustar00rootroot00000000000000{ "[python]": { "editor.formatOnSave": true, "editor.defaultFormatter": "charliermarsh.ruff" }, "python.testing.unittestArgs": [ "-v", "-s", "./tests", "-p", "test_*.py" ], "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, "python.testing.pytestArgs": [] }dotvav-py-palazzetti-api-0334f95/.vscode/tasks.json000066400000000000000000000031651474077214600222600ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "Code Coverage", "detail": "Generate code coverage report for a given integration.", "type": "shell", "command": "pytest ./tests/ --cov --cov-report term-missing", "dependsOn": ["Compile English translations"], "group": { "kind": "test", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, { "label": "Update syrupy snapshots", "detail": "Update syrupy snapshots for a given integration.", "type": "shell", "command": "pytest ./tests/ --snapshot-update", "group": { "kind": "test", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, { "label": "Install all Requirements", "type": "shell", "command": "pip install -r requirements.txt", "group": { "kind": "build", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, { "label": "Install all Test Requirements", "type": "shell", "command": "pip install -r requirements-test.txt", "group": { "kind": "build", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, ] } dotvav-py-palazzetti-api-0334f95/CHANGELOG.md000066400000000000000000000031571474077214600205110ustar00rootroot00000000000000# Changelog ## 0.1.19 * Rename fans LEFT and RIGHT instead of SECOND and THIRD * Create syrupy snapshot tests ## 0.1.18 * Bugfix: missing current_fan_speed function ## 0.1.17 * Second and third fans control * Code quality improvements ## 0.1.16 * Fix the silent mode * Make fan speed 0 available * Fix a bug that throws an exception when listing the temperatiures on hydro stoves (issue #6) ## 0.1.15 * Add more devices for test mocks * Sanitize the pellets quantity to prevent the value from decreasing * Bugfix: distinguish fan speed 0 from silent * Bugfix: redact the wifi address when a redacted json is requested ## 0.1.14 * Make status codes lower case * Add `to_dict` function, allow to redact sensitive info * Allows an `aiohttp.ClientSession` to be passed in the `PalazzettiClient` constructor ## 0.1.13 * Refactor temperatures and undeprecate `T1`, `T2`, `T3`, `T4`, `T5` * Expose `has_pellet_level` and `pellet_level` ## 0.1.12 * Fix `is_heating` state: remove ECOMODE (51) is not considered heating anymore * Refactor the temperature sensors. `PalazzettiClient.list_temperatures` will return a list of labelled temperature sensors that are present * `T1`, `T2`, `T3`, `T4`, `T5` properties of `PalazzettiClient` are marked as deprecated ## 0.1.11 * Fix temperature sensors ## 0.1.10 * Fix `setup.py` ## 0.1.9 * Fix `setup.py` (thanks @joostlek, @onkelbeh). * Add `CHANGELOG.md` ## 0.1.8 * Return the user-provided `host` value instead of an internal property. ## 0.1.7 * Add the `is_on` property, which is different from `is_heating`. ## 0.1.6 * First version usable in Home Assistant with all the important state values.dotvav-py-palazzetti-api-0334f95/LICENSE000066400000000000000000000020471474077214600177020ustar00rootroot00000000000000MIT License Copyright (c) 2024 dotvav Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dotvav-py-palazzetti-api-0334f95/README.md000066400000000000000000000024361474077214600201560ustar00rootroot00000000000000# Python Palazzetti API An async Python library to access and control a Palazzetti stove through a Palazzetti Connection Box or a [WPAlaControl](https://github.com/Domochip/WPalaControl). ## Getting started ```python import asyncio from pypalazzetti.client import PalazzettiClient async def main(): client = PalazzettiClient("192.168.1.73") print(f"Connection: {await client.connect()}") print(f"Check if online: {await client.is_online()}") print(f"Update: {await client.update_state()}") print(f"MAC address: {client.mac}") print(f"Name: {client.name}") print(f"Room temperature: {client.room_temperature}") print(f"Target temperature: {client.target_temperature}") print(f"Status: {client.status}") print(f"Set target temperature: {await client.set_target_temperature(22)}") print(f"Target temperature: {client.target_temperature}") print(f"Min fan speed: {client.fan_speed_min}") print(f"Max fan speed: {client.fan_speed_max}") print(f"Set fan speed: {await client.set_fan_auto()}") print(f"Fan speed: {client.fan_speed}") print("---") for temp in client.list_temperatures(): print(f"{temp.description_key}={temp.value}") print("---") print(client.to_json()) asyncio.new_event_loop().run_until_complete(main()) ``` dotvav-py-palazzetti-api-0334f95/example.py000066400000000000000000000022011474077214600206720ustar00rootroot00000000000000"""Example usage script.""" import asyncio from pypalazzetti.client import PalazzettiClient async def main(): client = PalazzettiClient("192.168.1.73") print(f"Connection: {await client.connect()}") print(f"Check if online: {await client.is_online()}") print(f"Update: {await client.update_state()}") print(f"MAC address: {client.mac}") print(f"Name: {client.name}") print(f"Room temperature: {client.room_temperature}") print(f"Target temperature: {client.target_temperature}") print(f"Status: {client.status}") print(f"Set target temperature: {await client.set_target_temperature(22)}") print(f"Target temperature: {client.target_temperature}") print(f"Min fan speed: {client.fan_speed_min}") print(f"Max fan speed: {client.fan_speed_max}") print(f"Set fan speed: {await client.set_fan_auto()}") print(f"Fan speed: {client.fan_speed}") print("---") for temp in client.list_temperatures(): print(f"{temp.description_key}={getattr(client, temp.state_property)}") print("---") print(client.to_json(redact=True)) asyncio.new_event_loop().run_until_complete(main()) dotvav-py-palazzetti-api-0334f95/pypalazzetti/000077500000000000000000000000001474077214600214325ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/pypalazzetti/__init__.py000066400000000000000000000000271474077214600235420ustar00rootroot00000000000000__version__ = "0.1.19" dotvav-py-palazzetti-api-0334f95/pypalazzetti/client.py000066400000000000000000000336271474077214600232750ustar00rootroot00000000000000"""Python wrapper for the Palazzetti Connection Box API.""" import json from json.decoder import JSONDecodeError import aiohttp from .config import PalazzettiClientConfig from .const import ( API_COMMAND_URL_TEMPLATE, COMMAND_CHECK_ONLINE, COMMAND_SET_FAN_SILENT, COMMAND_SET_MAIN_FAN_SPEED, COMMAND_SET_LEFT_FAN_SPEED, COMMAND_SET_RIGHT_FAN_SPEED, COMMAND_SET_OFF, COMMAND_SET_ON, COMMAND_SET_POWER_MODE, COMMAND_SET_TEMPERATURE, COMMAND_UPDATE_PROPERTIES, COMMAND_UPDATE_STATE, REDACTED_DATA, ) from .exceptions import CommunicationError, ValidationError from .fan import FanType from .state import _PalazzettiAPIData, _PalazzettiState from .temperature import TemperatureDefinition class PalazzettiClient: """Interface class for the Overkiz API.""" connected = False def __init__( self, hostname: str, session: aiohttp.ClientSession | None = None, config: PalazzettiClientConfig = PalazzettiClientConfig(), ): self._hostname = hostname self._session = session or aiohttp.ClientSession() self._config = config self._state = _PalazzettiState(config) async def connect(self) -> bool: """Connect to the device.""" r = await self._execute_command( command=COMMAND_UPDATE_PROPERTIES, merge_state=False, ) if r.success: self._state.merge_properties(r) self.connected = True else: self.connected = False return self.connected async def is_online(self) -> bool: """Test if the device is online.""" if not self.connected: await self.connect() return (await self._execute_command(command=COMMAND_CHECK_ONLINE)).success async def update_state(self) -> bool: """Update the device's state.""" # Connect if not connected yet if not self.connected: await self.connect() # Check if connection was successful before updating if self.connected: self.connected = ( await self._execute_command(command=COMMAND_UPDATE_STATE) ).success return self.connected @property def sw_version(self) -> str: """Return the software version.""" return self._state.sw_version @property def hw_version(self) -> str: """Return the hardware version""" return self._state.hw_version @property def has_on_off_switch(self) -> bool: """Return the availability of the on/of switch""" return self._state.has_on_off_switch @property def target_temperature(self) -> int: """Return the target temperature""" return self._state.target_temperature @property def current_temperature(self) -> float: """Return the current temperature.""" return self._state.current_temperature @property def has_air_outlet_temperature(self) -> bool: """Return the air outlet temperature.""" return self._state.has_air_outlet_temperature @property def air_outlet_temperature(self) -> float: """Return the air outlet temperature.""" return self._state.air_outlet_temperature @property def room_temperature(self) -> float: """DEPRECATED - Return the room temperature.""" return self._state.current_temperature @property def outlet_temperature(self) -> float: """DEPRECATED - Return the outlet temperature.""" return self._state.air_outlet_temperature @property def has_wood_combustion_temperature(self) -> bool: """Return the air outlet temperature.""" return self._state.has_wood_combustion_temperature @property def wood_combustion_temperature(self) -> float: """Return the wood combustion temperature.""" return self._state.wood_combustion_temperature @property def T1(self) -> float: """Return the T1 temperature.""" return self._state.T1 @property def T2(self) -> float: """Return the T2 temperature.""" return self._state.T2 @property def T3(self) -> float: """Return the T3 temperature.""" return self._state.T3 @property def T4(self) -> float: """Return the T4 temperature.""" return self._state.T4 @property def T5(self) -> float: """Return the T5 temperature.""" return self._state.T5 def list_temperatures(self) -> list[TemperatureDefinition]: """Return a list of all available temperatures.""" return self._state.list_temperatures() @property def host(self) -> str: """Return the host name or IP address.""" return self._hostname @property def mac(self) -> str: """Return the mac address.""" return self._state.mac @property def name(self) -> str: """Return the stove's name.""" return self._state.name @property def status(self) -> int: """Return the stove's status.""" return self._state.status @property def fan_speed(self) -> int: """DEPRECATED - Return the fan mode.""" return self._state.main_fan_speed def current_fan_speed(self, fan: FanType = FanType.MAIN) -> int: """Return a fan's speed""" if fan == FanType.MAIN: return self._state.main_fan_speed elif fan == FanType.LEFT: return self._state.left_fan_speed elif fan == FanType.RIGHT: return self._state.right_fan_speed else: return 0 @property def power_mode(self) -> int: """Return the power mode.""" return self._state.power_mode @property def pellet_quantity(self) -> int: """Return the pellet quantity.""" return self._state.pellet_quantity @property def has_pellet_level(self) -> bool: """Return the availability of the pellet level.""" return ( self._state.has_leveltronic_pellet_sensor or self._state.has_capacitive_pellet_sensor ) @property def pellet_level(self) -> float: """Return the pellet level.""" return self._state.pellet_level @property def pellet_level_min(self) -> float: """Return the minimum pellet level.""" return self._state.pellet_level_min @property def pellet_level_max(self) -> float: """Return the maximum pellet level.""" return self._state.pellet_level_max @property def pellet_level_threshold(self) -> float: """Return the pellet level threshold.""" return self._state.pellet_level_threshold @property def is_on(self) -> bool: """Check if the stove is on.""" return self._state.is_on @property def is_heating(self) -> bool: """Check if the stove is currently heating.""" return self._state.is_heating @property def has_fan_silent(self) -> bool: """Check if the fan has the silent mode available.""" return self._state.has_fan_mode_silent @property def has_fan_high(self) -> bool: """Check if the fan has the high mode available.""" return self._state.has_fan_mode_high @property def has_fan_auto(self) -> bool: """Check if the fan has the auto mode available.""" return self._state.has_fan_mode_auto def has_fan(self, fan: FanType = FanType.MAIN) -> bool: """Check if a fan is available""" if fan == FanType.MAIN: return self._state.has_main_fan elif fan == FanType.LEFT: return self._state.has_left_fan elif fan == FanType.RIGHT: return self._state.has_right_fan return False @property def fan_speed_min(self) -> int: """DEPRRECATED - Return the minimum fan speed.""" return self._state.main_fan_min @property def fan_speed_max(self) -> int: """DEPRRECATED - Return the maximum fan speed.""" return self._state.main_fan_max def min_fan_speed(self, fan: FanType = FanType.MAIN) -> int: """Return the minimum fan speed.""" if fan == FanType.MAIN: return self._state.main_fan_min elif fan == FanType.LEFT: return self._state.left_fan_min elif fan == FanType.RIGHT: return self._state.right_fan_min return 0 def max_fan_speed(self, fan: FanType = FanType.MAIN) -> int: """Return the maximum fan speed.""" if fan == FanType.MAIN: return self._state.main_fan_max elif fan == FanType.LEFT: return self._state.left_fan_max elif fan == FanType.RIGHT: return self._state.right_fan_max return 1 @property def target_temperature_min(self) -> int: """Return the minimum target temperature.""" return self._state.target_temperature_min @property def target_temperature_max(self) -> int: """Return the maximum target temperature.""" return self._state.target_temperature_max async def set_target_temperature(self, temperature: int) -> bool: """Sets the target temperature.""" if ( temperature >= self._state.target_temperature_min and temperature <= self._state.target_temperature_max ): res = await self._execute_command( command=COMMAND_SET_TEMPERATURE, parameter=temperature, ) return self._state.merge_state(res) return False async def set_fan_silent(self) -> bool: """Set the fan to silent mode.""" return ( await self._execute_command( command=COMMAND_SET_FAN_SILENT, merge_state=True ) ).success async def set_fan_high(self) -> bool: """Set the fan to high mode.""" return await self.set_fan_speed(6) async def set_fan_auto(self) -> bool: """Set the fan to auto mode.""" return await self.set_fan_speed(7) async def set_fan_speed(self, fan_speed: int, fan: FanType = FanType.MAIN) -> bool: """Set the fan speed.""" if not self.has_fan(fan): raise ValidationError(f'Fan "{fan}" not available.') if ( (self.min_fan_speed(fan) <= fan_speed <= self.max_fan_speed(fan)) or ( fan == FanType.MAIN and fan_speed == 6 and self._state.has_fan_mode_high ) or ( fan == FanType.MAIN and fan_speed == 7 and self._state.has_fan_mode_auto ) ): if fan == FanType.LEFT: command = COMMAND_SET_LEFT_FAN_SPEED elif fan == FanType.RIGHT: command = COMMAND_SET_RIGHT_FAN_SPEED else: command = COMMAND_SET_MAIN_FAN_SPEED return ( await self._execute_command( command=command, parameter=fan_speed, merge_state=True, ) ).success raise ValidationError(f'Fan "{fan}" speed ({fan_speed}) out of range.') async def set_power_mode(self, power: int) -> bool: """Set the power mode.""" if 1 <= power <= 5: return ( await self._execute_command( command=COMMAND_SET_POWER_MODE, parameter=power, ) ).success raise ValidationError(f"Power mode ({power}) out of range.") async def set_on(self, on: bool) -> bool: """Set the stove on or off.""" if self._state.has_on_off_switch: return ( await self._execute_command( command=COMMAND_SET_ON if on else COMMAND_SET_OFF, ) ).success raise ValidationError("Main operation switch not available.") async def _execute_command( self, command: str, parameter: str | int | None = None, merge_state=True, ) -> _PalazzettiAPIData: request_url = API_COMMAND_URL_TEMPLATE.format( host=self._hostname, command_and_parameter=f"{command} {parameter}" if parameter else command, ) try: async with self._session.get(request_url) as response: payload = _PalazzettiAPIData(await response.text()) except (TypeError, JSONDecodeError) as ex: self.connected = False raise CommunicationError("Invalid API response") from ex except aiohttp.ClientError as ex: self.connected = False raise CommunicationError("API communication error") from ex if merge_state: self._state.merge_state(payload) return payload def to_json(self, redact: bool = False) -> str: """Return a snapshot of the client as a json string.""" return json.dumps(self.to_dict(redact)) def to_dict( self, redact: bool = False, ) -> dict[str, bool | dict[str, str | bool | int | float | list[int | str]]]: """Return a snapshot of the client as a dict.""" data = { "host": self._hostname, "connected": self.connected, "state": self._state.to_dict(), } if redact: redacted = { "DNS": [REDACTED_DATA], "EADDR": REDACTED_DATA, "EGW": REDACTED_DATA, "EMAC": REDACTED_DATA, "GATEWAY": REDACTED_DATA, "MAC": REDACTED_DATA, "SN": REDACTED_DATA, "WADR": REDACTED_DATA, "WBCST": REDACTED_DATA, "WMAC": REDACTED_DATA, "WGW": REDACTED_DATA, "WSSID": REDACTED_DATA, } data["host"] = REDACTED_DATA data["state"]["properties"] = data["state"]["properties"] | redacted data["state"]["attributes"] = data["state"]["attributes"] | redacted return data dotvav-py-palazzetti-api-0334f95/pypalazzetti/config.py000066400000000000000000000007761474077214600232630ustar00rootroot00000000000000"""Palazzetti client configuration""" class PalazzettiClientConfig: """A client configuration. Attributes ---------- pellet_quantity_sanitize: bool When set to `True` the pellet quantity will be sanitized when updated: it's value will be updated only if it is greater or equal to the previously known value. """ def __init__( self, pellet_quantity_sanitize: bool = False, ) -> None: self.pellet_quantity_sanitize: bool = pellet_quantity_sanitize dotvav-py-palazzetti-api-0334f95/pypalazzetti/const.py000066400000000000000000000043141474077214600231340ustar00rootroot00000000000000"""Constants.""" from typing import Final API_COMMAND_URL_TEMPLATE: Final = ( "http://{host}/cgi-bin/sendmsg.lua?cmd={command_and_parameter}" ) STATUSES: Final[dict[int, str]] = { 0: "off", 1: "off_timer", 2: "test_fire", 3: "heatup", 4: "fueling", 5: "ign_test", 6: "burning", 7: "burning_mod", 8: "unknown", 9: "cool_fluid", 10: "fire_stop", 11: "clean_fire", 12: "cooling", 50: "cleanup", 51: "ecomode", 241: "chimney_alarm", 243: "grate_error", 244: "pellet_water_error", 245: "t05_error", 247: "hatch_door_open", 248: "pressure_error", 249: "main_probe_failure", 250: "flue_probe_failure", 252: "exhaust_temp_high", 253: "pellet_finished", 501: "off", 502: "fueling", 503: "ign_test", 504: "burning", 505: "firewood_finished", 506: "cooling", 507: "clean_fire", 1000: "general_error", 1001: "general_error", 1239: "door_open", 1240: "temp_too_high", 1241: "cleaning_warning", 1243: "fuel_error", 1244: "pellet_water_error", 1245: "t05_error", 1247: "hatch_door_open", 1248: "pressure_error", 1249: "main_probe_failure", 1250: "flue_probe_failure", 1252: "exhaust_temp_high", 1253: "pellet_finished", 1508: "general_error", } HEATING_STATUSES: Final = [2, 3, 4, 5, 6, 7, 502, 503, 504] OFF_STATUSES: Final = [0, 1] TEMPERATURE_PROBES: Final = ["T1", "T2", "T3", "T4", "T5"] FAN_SILENT: Final = "SILENT" FAN_HIGH: Final = "HIGH" FAN_AUTO: Final = "AUTO" FAN_MODES: Final = [ FAN_SILENT, # Deprecated "0", "1", "2", "3", "4", "5", FAN_HIGH, FAN_AUTO, ] COMMAND_CHECK_ONLINE: Final = "GET STAT" COMMAND_UPDATE_PROPERTIES: Final = "GET STDT" COMMAND_UPDATE_STATE: Final = "GET ALLS" COMMAND_SET_TEMPERATURE: Final = "SET SETP" COMMAND_SET_FAN_SPEED: Final = "SET RFAN" # DEPRECATED COMMAND_SET_MAIN_FAN_SPEED: Final = "SET RFAN" COMMAND_SET_LEFT_FAN_SPEED: Final = "SET FN3L" COMMAND_SET_RIGHT_FAN_SPEED: Final = "SET FN4L" COMMAND_SET_FAN_SILENT: Final = "SET SLNT 1" COMMAND_SET_POWER_MODE: Final = "SET POWR" COMMAND_SET_ON: Final = "CMD ON" COMMAND_SET_OFF: Final = "CMD OFF" REDACTED_DATA: Final = "XXXXXXXX" dotvav-py-palazzetti-api-0334f95/pypalazzetti/exceptions.py000066400000000000000000000003201474077214600241600ustar00rootroot00000000000000"""Exceptions.""" class CommunicationError(Exception): """Exception raise on API communication errors.""" class ValidationError(Exception): """Exception raised on input data validation errors.""" dotvav-py-palazzetti-api-0334f95/pypalazzetti/fan.py000066400000000000000000000001721474077214600225500ustar00rootroot00000000000000"""Fans definition""" from enum import Enum class FanType(Enum): FUMES = 1 MAIN = 2 LEFT = 3 RIGHT = 4 dotvav-py-palazzetti-api-0334f95/pypalazzetti/state.py000066400000000000000000000265621474077214600231370ustar00rootroot00000000000000"""Palazzetti data parsing and logic.""" import json from .config import PalazzettiClientConfig from .const import HEATING_STATUSES, OFF_STATUSES, TEMPERATURE_PROBES from .temperature import TemperatureDefinition, TemperatureDescriptionKey class _PalazzettiAPIData(dict[str, bool | dict[str, str | int | float]]): """Palazzetti API Data.""" def __init__(self, payload: str): super().__init__(json.loads(payload)) @property def success(self): return "SUCCESS" in self and self["SUCCESS"] class _PalazzettiState: _properties: dict[str, str | int | float] # Static data _attributes: dict[str, str | int | float] # Mostly sensors data def __init__(self, config: PalazzettiClientConfig): self._properties = {} self._attributes = {} self._config = config def merge_properties(self, state_data: _PalazzettiAPIData) -> bool: """Updates the current properties.""" if state_data.success: self._properties = self._properties | state_data["DATA"] return True return False def merge_state( self, state_data: _PalazzettiAPIData, ) -> bool: """Updates the attributes.""" if state_data.success: if ( "PQT" in state_data["DATA"] and self._config.pellet_quantity_sanitize and "PQT" in self._attributes ): state_data["DATA"]["PQT"] = max( state_data["DATA"]["PQT"], self._attributes["PQT"] ) self._attributes = self._attributes | state_data["DATA"] return True return False def _compare_versions(self, v1: str, v2: str): v1_tokens = v1.split(".") v2_tokens = v2.split(".") for token1, token2 in zip(v1_tokens, v2_tokens): if token1 != token2: return int(token1) - int(token2) return len(v1_tokens) - len(v2_tokens) @property def has_power_regulation(self) -> bool: return self._properties["STOVETYPE"] != 8 @property def has_ecostart(self) -> bool: return self._compare_versions(str(self._properties["SYSTEM"]), "2.1.1") > 0 @property def has_time_synchronization(self) -> bool: return self._compare_versions(str(self._properties["SYSTEM"]), "10000.0.0") > 0 @property def has_chrono(self) -> bool: return self._properties.get("CHRONOTYPE") > 1 @property def has_target_temperature(self) -> bool: return self._attributes.get("SETP") != 0 @property def has_on_off_switch(self) -> bool: return self._properties["STOVETYPE"] not in [7, 8] and self._attributes[ "LSTATUS" ] in [0, 1, 6, 7, 9, 11, 12, 51, 501, 504, 505, 506, 507] @property def has_error(self) -> bool: return int(self._attributes["LSTATUS"]) >= 1000 @property def has_switch_on_multifire_pellet(self) -> bool: return self._properties["STOVETYPE"] in [3, 4] @property def is_air(self) -> bool: return self._properties["STOVETYPE"] in [1, 3, 5, 7, 8] @property def is_hydro(self) -> bool: return self._properties["STOVETYPE"] in [2, 4, 6] @property def is_first_fan_on(self) -> bool: return bool(self._attributes.get("F2LF", 0)) @property def has_fan_mode_silent(self) -> bool: return self._properties.get("FAN2TYPE", 0) > 2 @property def has_fan_mode_auto(self) -> bool: return self._properties.get("FAN2MODE", 0) in [2, 3] @property def has_fan_mode_high(self) -> bool: return self._properties.get("FAN2MODE", 0) == 3 @property def has_fan_mode_prop(self) -> bool: return self._properties.get("FAN2MODE", 0) == 4 @property def has_main_fan(self) -> bool: return self._properties.get("FAN2TYPE", 0) > 1 @property def has_left_fan(self) -> bool: return self._properties.get("FAN2TYPE", 0) > 3 @property def has_right_fan(self) -> bool: return self._properties.get("FAN2TYPE", 0) > 2 @property def has_leveltronic_pellet_sensor(self) -> bool: return self._properties.get("PSENSTYPE", 0) == 1 @property def has_capacitive_pellet_sensor(self) -> bool: return self._properties.get("PSENSTYPE", 0) == 2 @property def pellet_level_min(self) -> float: return float(self._properties.get("PSENSLMIN", 0)) @property def pellet_level_max(self) -> float: return float(self._properties.get("PSENSLMAX", 0)) @property def pellet_level_threshold(self) -> float: return float(self._properties.get("PSENSLTSH", 0)) @property def pellet_level(self) -> float: return float(self._attributes.get("PLEVEL", 0)) @property def has_wood_combustion_temperature(self) -> bool: return self._properties["STOVETYPE"] in [7, 8] @property def has_air_outlet_temperature(self) -> bool: return ( self._properties["STOVETYPE"] in [7, 8] and self._properties["FAN2TYPE"] > 1 ) @property def has_door_control(self) -> bool: return self._properties.get("DOORMOTOR", 0) == 1 @property def has_light_control(self) -> bool: return self._properties.get("LIGHTCONT", 0) == 1 @property def product_type(self) -> int: return int(self._properties.get("STOVETYPE", 0)) @property def is_product_on(self) -> bool: return self._attributes["STATUS"] not in [0, 1] @property def hydro_t1_temperature(self) -> float: return float(self._attributes.get("T1", 0)) @property def hydro_t2_temperature(self) -> float: return float(self._attributes.get("T2", 0)) @property def wood_combustion_temperature(self) -> float: return float(self._attributes.get("T3", 0)) @property def air_outlet_temperature(self) -> float: return float(self._attributes.get("T4", 0)) def _main_temperature_probe_index(self) -> int: if self.is_hydro: if self._properties["UICONFIG"] == 1: return 1 # T2 if self._properties["UICONFIG"] == 10: return 4 # T5 return int(self._properties["MAINTPROBE"]) def _main_temperature_description(self) -> TemperatureDescriptionKey: if self.is_hydro: if self._properties["UICONFIG"] == 1: return TemperatureDescriptionKey.RETURN_WATER_TEMP if self._properties["UICONFIG"] in [3, 4]: return TemperatureDescriptionKey.TANK_WATER_TEMP return TemperatureDescriptionKey.ROOM_TEMP @property def current_temperature(self) -> float: return float( self._attributes[TEMPERATURE_PROBES[self._main_temperature_probe_index()]] ) @property def T1(self) -> float: return float(self._attributes.get("T1", 0)) @property def T2(self) -> float: return float(self._attributes.get("T2", 0)) @property def T3(self) -> float: return float(self._attributes.get("T3", 0)) @property def T4(self) -> float: return float(self._attributes.get("T4", 0)) @property def T5(self) -> float: return float(self._attributes.get("T5", 0)) @property def power_mode(self) -> int: return int(self._attributes.get("PWR", 0)) @property def target_temperature_min(self) -> int: return int(self._properties.get("SPLMIN", 0)) @property def target_temperature_max(self) -> int: return int(self._properties.get("SPLMAX", 0)) @property def target_temperature(self) -> int: return int(self._attributes.get("SETP", 0)) @property def main_fan_speed(self) -> int: return int(self._attributes.get("F2L", 0)) @property def left_fan_speed(self) -> int: return int(self._attributes.get("F3L", 0)) @property def right_fan_speed(self) -> int: return int(self._attributes.get("F4L", 0)) @property def main_fan_min(self) -> int: return int(self._attributes["FANLMINMAX"][0]) @property def main_fan_max(self) -> int: return int(self._attributes["FANLMINMAX"][1]) @property def left_fan_min(self) -> int: return int(self._attributes["FANLMINMAX"][2]) @property def left_fan_max(self) -> int: return int(self._attributes["FANLMINMAX"][3]) @property def right_fan_min(self) -> int: return int(self._attributes["FANLMINMAX"][4]) @property def right_fan_max(self) -> int: return int(self._attributes["FANLMINMAX"][5]) @property def door_status(self) -> int: return int(self._attributes.get("DOOR", 0)) @property def light_status(self) -> int: return int(self._attributes.get("LIGHT", 0)) @property def status(self) -> int: return int(self._attributes.get("LSTATUS", 0)) @property def mac(self) -> str: return str(self._properties.get("MAC", "X")) @property def name(self) -> str: return str(self._properties.get("LABEL", "X")) @property def pellet_quantity(self) -> int: return int(self._attributes.get("PQT", 0)) @property def is_on(self) -> bool: return self._attributes["LSTATUS"] not in OFF_STATUSES @property def is_heating(self) -> bool: return bool(self._attributes["LSTATUS"] in HEATING_STATUSES) @property def sw_version(self) -> str: return str(self._properties.get("plzbridge", "X")) @property def hw_version(self) -> str: return str(self._properties.get("SYSTEM", "X")) def list_temperatures(self) -> list[TemperatureDefinition]: """Return a list of temperature sensor definitions""" result: list[TemperatureDefinition] = [] result.append( TemperatureDefinition( state_property=TEMPERATURE_PROBES[self._main_temperature_probe_index()], description_key=self._main_temperature_description(), ), ) if self.has_air_outlet_temperature or self.air_outlet_temperature != 0: result.append( TemperatureDefinition( state_property="T4", description_key=TemperatureDescriptionKey.AIR_OUTLET_TEMP, ), ) if ( self.has_wood_combustion_temperature or self.wood_combustion_temperature != 0 ): result.append( TemperatureDefinition( state_property="T3", description_key=TemperatureDescriptionKey.WOOD_COMBUSTION_TEMP, ), ) if self.is_hydro: result.append( TemperatureDefinition( state_property="T1", description_key=TemperatureDescriptionKey.T1_HYDRO_TEMP, ), ) result.append( TemperatureDefinition( state_property="T2", description_key=TemperatureDescriptionKey.T2_HYDRO_TEMP, ), ) return result def to_dict( self, ) -> dict[str, bool | dict[str, str | bool | int | float | list[int | str]]]: """Return a snapshot of the state.""" return { "properties": self._properties.copy(), "attributes": self._attributes.copy(), } dotvav-py-palazzetti-api-0334f95/pypalazzetti/temperature.py000066400000000000000000000014341474077214600243430ustar00rootroot00000000000000"""Temperature sensor definition""" from enum import Enum class TemperatureDescriptionKey(str, Enum): """Temperature description key enum.""" ROOM_TEMP = "room_temperature" RETURN_WATER_TEMP = "return_water_temperature" TANK_WATER_TEMP = "tank_water_temperature" WOOD_COMBUSTION_TEMP = "wood_combustion_temperature" AIR_OUTLET_TEMP = "air_outlet_temperature" T1_HYDRO_TEMP = "t1_hydro" T2_HYDRO_TEMP = "t2_hydro" class TemperatureDefinition: """A temperature sensor""" state_property: str description_key: TemperatureDescriptionKey def __init__( self, state_property: str, description_key: TemperatureDescriptionKey, ): self.state_property = state_property self.description_key = description_key dotvav-py-palazzetti-api-0334f95/pyproject.toml000066400000000000000000000000571474077214600216100ustar00rootroot00000000000000[tool.pytest.ini_options] asyncio_mode = "auto"dotvav-py-palazzetti-api-0334f95/requirements-test.txt000066400000000000000000000000501474077214600231260ustar00rootroot00000000000000pytest pytest-asyncio pytest-cov syrupy dotvav-py-palazzetti-api-0334f95/requirements.txt000066400000000000000000000000221474077214600221500ustar00rootroot00000000000000aiohttp >= 3.10.3 dotvav-py-palazzetti-api-0334f95/scripts/000077500000000000000000000000001474077214600203615ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/scripts/build.sh000077500000000000000000000001351474077214600220160ustar00rootroot00000000000000#!/bin/sh # Local build set -e cd "$(dirname "$0")/.." rm -rf dist python3 setup.py sdist dotvav-py-palazzetti-api-0334f95/scripts/release.sh000077500000000000000000000002341474077214600223370ustar00rootroot00000000000000#!/bin/sh # Pushes a new version to PyPi set -e cd "$(dirname "$0")/.." rm -rf dist python3 setup.py sdist python3 -m twine upload dist/* --skip-existingdotvav-py-palazzetti-api-0334f95/setup.cfg000066400000000000000000000000761474077214600205160ustar00rootroot00000000000000# Inside of setup.cfg [metadata] description-file = README.md dotvav-py-palazzetti-api-0334f95/setup.py000066400000000000000000000012651474077214600204100ustar00rootroot00000000000000import setuptools with open("README.md", encoding="utf-8") as fh: long_description = fh.read() setuptools.setup( name="pypalazzetti", version="0.1.19", author="Vincent Roukine", author_email="vincent.roukine@gmail.com", description="A Python library to access and control a Palazzetti stove through a Palazzetti Connection Box", long_description=long_description, long_description_content_type="text/markdown", license="MIT", url="https://github.com/dotvav/py-palazzetti-api", packages=setuptools.find_packages(exclude=("tests", "tests.*")), install_requires=["aiohttp>=3.10.3"], python_requires=">=3.10", include_package_data=True, ) dotvav-py-palazzetti-api-0334f95/tests/000077500000000000000000000000001474077214600200345ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/__init__.py000066400000000000000000000000001474077214600221330ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/__snapshots__/000077500000000000000000000000001474077214600226525ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/__snapshots__/test_client.ambr000066400000000000000000000326421474077214600260410ustar00rootroot00000000000000# serializer version: 1 # name: test_snapshot[jotul_pf911s] PalazzettiClient( T1=18.5, T2=19.2, T3=14.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, connected=True, current_temperature=18.5, fan_speed=1, fan_speed_max=5, fan_speed_min=0, has_air_outlet_temperature=False, has_fan_auto=True, has_fan_high=False, has_fan_silent=False, has_on_off_switch=True, has_pellet_level=False, has_wood_combustion_temperature=False, host='127.0.0.1', hw_version='2.5.3 2022-09-01 16:24:09 (5cd2579)', is_heating=False, is_on=False, mac='40:F3:85:79:99:99', name='Salon', outlet_temperature=0.0, pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=65535, power_mode=5, room_temperature=18.5, status=0, sw_version='2.2.1 2022-09-01 16:23:43', target_temperature=23, target_temperature_max=51, target_temperature_min=5, wood_combustion_temperature=14.0, ) # --- # name: test_snapshot[jotul_pf911s].1 _PalazzettiState( T1=18.5, T2=19.2, T3=14.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, current_temperature=18.5, door_status=0, has_air_outlet_temperature=False, has_capacitive_pellet_sensor=False, has_chrono=True, has_door_control=False, has_ecostart=True, has_error=False, has_fan_mode_auto=True, has_fan_mode_high=False, has_fan_mode_prop=False, has_fan_mode_silent=False, has_left_fan=False, has_leveltronic_pellet_sensor=False, has_light_control=False, has_main_fan=True, has_on_off_switch=True, has_power_regulation=True, has_right_fan=False, has_switch_on_multifire_pellet=False, has_target_temperature=True, has_time_synchronization=False, has_wood_combustion_temperature=False, hw_version='2.5.3 2022-09-01 16:24:09 (5cd2579)', hydro_t1_temperature=18.5, hydro_t2_temperature=19.2, is_air=True, is_first_fan_on=False, is_heating=False, is_hydro=False, is_on=False, is_product_on=False, left_fan_max=1, left_fan_min=0, left_fan_speed=0, light_status=0, mac='40:F3:85:79:99:99', main_fan_max=5, main_fan_min=0, main_fan_speed=1, name='Salon', pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=65535, power_mode=5, product_type=1, right_fan_max=1, right_fan_min=0, right_fan_speed=0, status=0, sw_version='2.2.1 2022-09-01 16:23:43', target_temperature=23, target_temperature_max=51, target_temperature_min=5, wood_combustion_temperature=14.0, ) # --- # name: test_snapshot[palazzetti_beatrice] PalazzettiClient( T1=20.3, T2=31.3, T3=410.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, connected=True, current_temperature=20.3, fan_speed=3, fan_speed_max=5, fan_speed_min=2, has_air_outlet_temperature=False, has_fan_auto=False, has_fan_high=False, has_fan_silent=False, has_on_off_switch=True, has_pellet_level=True, has_wood_combustion_temperature=False, host='127.0.0.1', hw_version='2.5.3 2021-10-08 10:30:20 (657c8cf)', is_heating=True, is_on=True, mac='XXXXXXXX', name='Poele Sejour', outlet_temperature=0.0, pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=884, power_mode=5, room_temperature=20.3, status=6, sw_version='2.2.1 2021-10-08 09:30:45', target_temperature=24, target_temperature_max=51, target_temperature_min=12, wood_combustion_temperature=410.0, ) # --- # name: test_snapshot[palazzetti_beatrice].1 _PalazzettiState( T1=20.3, T2=31.3, T3=410.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, current_temperature=20.3, door_status=0, has_air_outlet_temperature=False, has_capacitive_pellet_sensor=True, has_chrono=True, has_door_control=False, has_ecostart=True, has_error=False, has_fan_mode_auto=False, has_fan_mode_high=False, has_fan_mode_prop=False, has_fan_mode_silent=False, has_left_fan=False, has_leveltronic_pellet_sensor=False, has_light_control=False, has_main_fan=True, has_on_off_switch=True, has_power_regulation=True, has_right_fan=False, has_switch_on_multifire_pellet=False, has_target_temperature=True, has_time_synchronization=False, has_wood_combustion_temperature=False, hw_version='2.5.3 2021-10-08 10:30:20 (657c8cf)', hydro_t1_temperature=20.3, hydro_t2_temperature=31.3, is_air=True, is_first_fan_on=False, is_heating=True, is_hydro=False, is_on=True, is_product_on=True, left_fan_max=1, left_fan_min=0, left_fan_speed=0, light_status=0, mac='XXXXXXXX', main_fan_max=5, main_fan_min=2, main_fan_speed=3, name='Poele Sejour', pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=884, power_mode=5, product_type=1, right_fan_max=1, right_fan_min=0, right_fan_speed=0, status=6, sw_version='2.2.1 2021-10-08 09:30:45', target_temperature=24, target_temperature_max=51, target_temperature_min=12, wood_combustion_temperature=410.0, ) # --- # name: test_snapshot[palazzetti_emily] PalazzettiClient( T1=125.0, T2=22.5, T3=26.0, T4=0.0, T5=21.8, air_outlet_temperature=0.0, connected=True, current_temperature=21.8, fan_speed=7, fan_speed_max=5, fan_speed_min=0, has_air_outlet_temperature=False, has_fan_auto=True, has_fan_high=True, has_fan_silent=False, has_on_off_switch=True, has_pellet_level=False, has_wood_combustion_temperature=False, host='127.0.0.1', hw_version='2.5.3 2022-11-08 15:52:19 (9e8e347)', is_heating=False, is_on=True, mac='AA:AA:AA:71:F2:72', name='Emily', outlet_temperature=0.0, pellet_level=0.0, pellet_level_max=12.0, pellet_level_min=240.0, pellet_level_threshold=10.0, pellet_quantity=53, power_mode=2, room_temperature=21.8, status=51, sw_version='2.2.1 2022-10-24 11:13:21', target_temperature=20, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=26.0, ) # --- # name: test_snapshot[palazzetti_emily].1 _PalazzettiState( T1=125.0, T2=22.5, T3=26.0, T4=0.0, T5=21.8, air_outlet_temperature=0.0, current_temperature=21.8, door_status=0, has_air_outlet_temperature=False, has_capacitive_pellet_sensor=False, has_chrono=True, has_door_control=False, has_ecostart=True, has_error=False, has_fan_mode_auto=True, has_fan_mode_high=True, has_fan_mode_prop=False, has_fan_mode_silent=False, has_left_fan=False, has_leveltronic_pellet_sensor=False, has_light_control=False, has_main_fan=True, has_on_off_switch=True, has_power_regulation=True, has_right_fan=False, has_switch_on_multifire_pellet=False, has_target_temperature=True, has_time_synchronization=False, has_wood_combustion_temperature=False, hw_version='2.5.3 2022-11-08 15:52:19 (9e8e347)', hydro_t1_temperature=125.0, hydro_t2_temperature=22.5, is_air=True, is_first_fan_on=True, is_heating=False, is_hydro=False, is_on=True, is_product_on=True, left_fan_max=1, left_fan_min=0, left_fan_speed=0, light_status=0, mac='AA:AA:AA:71:F2:72', main_fan_max=5, main_fan_min=0, main_fan_speed=7, name='Emily', pellet_level=0.0, pellet_level_max=12.0, pellet_level_min=240.0, pellet_level_threshold=10.0, pellet_quantity=53, power_mode=2, product_type=1, right_fan_max=1, right_fan_min=0, right_fan_speed=0, status=51, sw_version='2.2.1 2022-10-24 11:13:21', target_temperature=20, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=26.0, ) # --- # name: test_snapshot[palazzetti_ginger] PalazzettiClient( T1=21.5, T2=25.1, T3=45.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, connected=True, current_temperature=21.5, fan_speed=6, fan_speed_max=5, fan_speed_min=0, has_air_outlet_temperature=False, has_fan_auto=True, has_fan_high=True, has_fan_silent=False, has_on_off_switch=True, has_pellet_level=False, has_wood_combustion_temperature=False, host='127.0.0.1', hw_version='2.5.3 2021-10-08 10:30:20 (657c8cf)', is_heating=False, is_on=True, mac='40:F3:85:71:23:45', name='Name', outlet_temperature=0.0, pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=1807, power_mode=3, room_temperature=21.5, status=51, sw_version='2.2.1 2021-10-08 09:30:45', target_temperature=21, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=45.0, ) # --- # name: test_snapshot[palazzetti_ginger].1 _PalazzettiState( T1=21.5, T2=25.1, T3=45.0, T4=0.0, T5=0.0, air_outlet_temperature=0.0, current_temperature=21.5, door_status=0, has_air_outlet_temperature=False, has_capacitive_pellet_sensor=False, has_chrono=True, has_door_control=False, has_ecostart=True, has_error=False, has_fan_mode_auto=True, has_fan_mode_high=True, has_fan_mode_prop=False, has_fan_mode_silent=False, has_left_fan=False, has_leveltronic_pellet_sensor=False, has_light_control=False, has_main_fan=True, has_on_off_switch=True, has_power_regulation=True, has_right_fan=False, has_switch_on_multifire_pellet=False, has_target_temperature=True, has_time_synchronization=False, has_wood_combustion_temperature=False, hw_version='2.5.3 2021-10-08 10:30:20 (657c8cf)', hydro_t1_temperature=21.5, hydro_t2_temperature=25.1, is_air=True, is_first_fan_on=True, is_heating=False, is_hydro=False, is_on=True, is_product_on=True, left_fan_max=1, left_fan_min=0, left_fan_speed=0, light_status=0, mac='40:F3:85:71:23:45', main_fan_max=5, main_fan_min=0, main_fan_speed=6, name='Name', pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=1807, power_mode=3, product_type=1, right_fan_max=1, right_fan_min=0, right_fan_speed=0, status=51, sw_version='2.2.1 2021-10-08 09:30:45', target_temperature=21, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=45.0, ) # --- # name: test_snapshot[palazzetti_juliepro2] PalazzettiClient( T1=0.0, T2=22.4, T3=220.0, T4=0.0, T5=18.4, air_outlet_temperature=0.0, connected=True, current_temperature=18.4, fan_speed=7, fan_speed_max=5, fan_speed_min=0, has_air_outlet_temperature=False, has_fan_auto=True, has_fan_high=True, has_fan_silent=True, has_on_off_switch=True, has_pellet_level=False, has_wood_combustion_temperature=False, host='127.0.0.1', hw_version='2.5.3 2021-12-06 06:54:35 (5a791b6)', is_heating=True, is_on=True, mac='--', name='Poele', outlet_temperature=0.0, pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=1384, power_mode=3, room_temperature=18.4, status=6, sw_version='2.2.1 2021-12-06 06:48:51', target_temperature=20, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=220.0, ) # --- # name: test_snapshot[palazzetti_juliepro2].1 _PalazzettiState( T1=0.0, T2=22.4, T3=220.0, T4=0.0, T5=18.4, air_outlet_temperature=0.0, current_temperature=18.4, door_status=0, has_air_outlet_temperature=False, has_capacitive_pellet_sensor=False, has_chrono=True, has_door_control=False, has_ecostart=True, has_error=False, has_fan_mode_auto=True, has_fan_mode_high=True, has_fan_mode_prop=False, has_fan_mode_silent=True, has_left_fan=False, has_leveltronic_pellet_sensor=False, has_light_control=False, has_main_fan=True, has_on_off_switch=True, has_power_regulation=True, has_right_fan=True, has_switch_on_multifire_pellet=False, has_target_temperature=True, has_time_synchronization=False, has_wood_combustion_temperature=False, hw_version='2.5.3 2021-12-06 06:54:35 (5a791b6)', hydro_t1_temperature=0.0, hydro_t2_temperature=22.4, is_air=True, is_first_fan_on=True, is_heating=True, is_hydro=False, is_on=True, is_product_on=True, left_fan_max=5, left_fan_min=0, left_fan_speed=0, light_status=0, mac='--', main_fan_max=5, main_fan_min=0, main_fan_speed=7, name='Poele', pellet_level=0.0, pellet_level_max=0.0, pellet_level_min=0.0, pellet_level_threshold=0.0, pellet_quantity=1384, power_mode=3, product_type=1, right_fan_max=5, right_fan_min=0, right_fan_speed=3, status=6, sw_version='2.2.1 2021-12-06 06:48:51', target_temperature=20, target_temperature_max=51, target_temperature_min=6, wood_combustion_temperature=220.0, ) # --- dotvav-py-palazzetti-api-0334f95/tests/conftest.py000066400000000000000000000001051474077214600222270ustar00rootroot00000000000000"""Tests configuration.""" pytest_plugins = "aiohttp.pytest_plugin" dotvav-py-palazzetti-api-0334f95/tests/mock_json/000077500000000000000000000000001474077214600220165ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/jotul_pf911s/000077500000000000000000000000001474077214600242565ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/jotul_pf911s/GET_ALLS.json000066400000000000000000000013071474077214600264040ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1731832712 }, "SUCCESS": true, "DATA": { "T2": 19.2, "F2LF": 0, "PQT": 65535, "PWR": 5, "CHRSTATUS": 0, "SECO": 2, "FDR": 0, "F2V": 0, "MOD": 7000, "DPT": 65535, "APLWDAY": 7, "MAC": "40:F3:85:79:99:99", "SETP": 23, "APLTS": "2024-11-17 09:38:28", "BECO": 0, "STATUS": 0, "T3": 14, "T1": 18.5, "PUMP": 0, "T5": 0, "F1RPM": 0, "OUT": 0, "F1V": 0, "EFLAGS": 0, "LSTATUS": 0, "T4": 0, "F2L": 1, "CORE": 10, "DP": 0, "FANLMINMAX": [0, 5, 0, 1, 0, 1], "IN": 3, "VER": 5, "MBTYPE": 0, "FWDATE": "0-00-00" } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/jotul_pf911s/GET_STDT.json000066400000000000000000000027501474077214600264320ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET STDT", "TS": 1731832856 }, "SUCCESS": true, "DATA": { "GWDEVICE": "wlan0", "GATEWAY": "192.168.1.1", "NOMINALPWR": 9, "PSENSLTSH": 0, "WMAC": "40:F3:85:79:99:99", "plzbridge": "2.2.1 2022-09-01 16:23:43", "HWTYPE": 7, "AUTONOMYTYPE": 1, "CHRONOTYPE": 5, "WMODE": "sta", "STOVETYPE": 1, "SPLMIN": 5, "DSPTYPE": 9, "FAN2MODE": 2, "MOD": 7000, "FAN2TYPE": 2, "EGW": "192.168.0.116", "MAC": "40:F3:85:79:99:99", "FLUID": 0, "SPLMAX": 51, "ECBL": "down", "PSENSLMIN": 0, "WADR": "192.168.0.100", "CLOUD_ENABLED": true, "EPR": "static", "PELLETTYPE": 3, "PSENSLMAX": 0, "SNCHK": 0, "PSENSTYPE": 0, "CONFIG": 1, "DSPFWVER": 9, "LABEL": "Salon", "EMSK": "255.255.255.0", "BLEMBMODE": 3, "WGW": "192.168.1.1", "UICONFIG": 1, "BLEDSPMODE": 1, "MAINTPROBE": 0, "ICONN": 1, "EBCST": "", "FWDATE": "0-00-00", "WSSID": "wifi ssid", "WPR": "dhcp", "VER": 5, "WPWR": "-66 dBm", "MBTYPE": 0, "WENC": "psk2", "CBTYPE": "miniembplug", "EADR": "192.168.0.177", "sendmsg": "2.2.1 2021-10-12 12:22:53", "WMSK": "255.255.255.0", "EMAC": "40:F3:85:79:99:99", "CORE": 10, "SN": "XXXX", "APLCONN": 1, "DNS": ["192.168.1.1", "192.168.1.1", "127.0.0.1"], "SYSTEM": "2.5.3 2022-09-01 16:24:09 (5cd2579)", "WBCST": "192.168.1.255", "WCH": "1" } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_beatrice/000077500000000000000000000000001474077214600260435ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_beatrice/GET_ALLS.json000066400000000000000000000017751474077214600302020ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1731245177 }, "SUCCESS": true, "DATA": { "PSENSCSTA": 1, "F2LF": 0, "PQT": 884, "PWR": 5, "CHRSTATUS": 0, "FANLMINMAX": [ 2, 5, 0, 1, 0, 1 ], "FDR": 2.5, "F2V": 170, "MOD": 332, "DPT": 0, "APLWDAY": 7, "MAC": "XXXXXXXX", "SETP": 24, "APLTS": "2025-01-01 11:21:26", "STATUS": 6, "T3": 410, "T1": 20.3, "PUMP": 0, "T5": 0, "F1RPM": 2417, "OUT": 6, "SN": "XXXXXXXX", "F1V": 2417, "EFLAGS": 0, "LSTATUS": 6, "T4": 0, "T2": 31.3, "F2L": 3, "CORE": 136, "DP": 1046, "PSENSLEMP": 0, "IN": 15, "FWDATE": "2021-07-22", "VER": 47, "MBTYPE": 0, "DNS": [ "XXXXXXXX" ] } }dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_beatrice/GET_STDT.json000066400000000000000000000032421474077214600302140ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET STDT", "TS": 1731245113 }, "SUCCESS": true, "DATA": { "GWDEVICE": "eth0.1", "GATEWAY": "XXXXXXXX", "PSENSLTSH": 0, "WMAC": "XXXXXXXX", "plzbridge": "2.2.1 2021-10-08 09:30:45", "HWTYPE": 6, "NOMINALPWR": 6, "AUTONOMYTYPE": 1, "WMODE": "sta", "STOVETYPE": 1, "CHRONOTYPE": 5, "SPLMIN": 12, "DSPFWVER": 31, "MOD": 332, "FAN2MODE": 1, "EGW": "XXXXXXXX", "MAC": "XXXXXXXX", "FAN2TYPE": 2, "SPLMAX": 51, "ECBL": "up", "FLUID": 0, "WADR": "", "CLOUD_ENABLED": true, "EPR": "dhcp", "PELLETTYPE": 3, "PSENSLMIN": 0, "SNCHK": 1, "PSENSLMAX": 0, "PSENSTYPE": 2, "CONFIG": 1, "LABEL": "Poele Sejour", "EMSK": "255.255.255.0", "BLEMBMODE": 3, "WGW": "XXXXXXXX", "UICONFIG": 1, "BLEDSPMODE": 1, "MAINTPROBE": 0, "ICONN": 1, "EBCST": "XXXXXXXX", "FWDATE": "2021-07-22", "WSSID": "XXXXXXXX", "WPR": "dhcp", "VER": 47, "WPWR": "", "MBTYPE": 0, "WENC": "psk2", "CBTYPE": "miniembplug", "EADR": "XXXXXXXX", "sendmsg": "2.1.2 2018-03-28 10:19:09", "WMSK": "", "EMAC": "XXXXXXXX", "CORE": 136, "SN": "XXXXXXXX", "APLCONN": 1, "DNS": [ "XXXXXXXX" ], "SYSTEM": "2.5.3 2021-10-08 10:30:20 (657c8cf)", "WBCST": "XXXXXXXX", "WCH": "", "EADDR": "XXXXXXXX" } }dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_emily/000077500000000000000000000000001474077214600254045ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_emily/GET_ALLS.json000066400000000000000000000013721474077214600275340ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1731245177 }, "SUCCESS": true, "DATA": { "T2": 22.5, "F2LF": 2, "PQT": 53, "PWR": 2, "CHRSTATUS": 0, "FANLMINMAX": [0, 5, 0, 1, 0, 1], "FDR": 0, "F2V": 0, "MOD": 1295, "DPT": 0, "APLWDAY": 1, "MAC": "AA:AA:AA:71:F2:72", "SETP": 20, "FSTATUS": 0, "APLTS": "2024-11-10 14:26:30", "BECO": 1, "STATUS": 9, "T3": 26, "T1": 125, "PUMP": 0, "T5": 21.8, "F1RPM": 0, "OUT": 0, "F1V": 0, "SN": "XXXXXXXXXXXXXXXXXX", "EFLAGS": 1, "T4": 0, "LSTATUS": 51, "F2L": 7, "CORE": 44, "DP": 0, "VER": 3, "IN": 3, "SECO": 1.2, "MBTYPE": 0, "FWDATE": "2024-07-17" } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_emily/GET_STDT.json000066400000000000000000000030021474077214600275470ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET STDT", "TS": 1731245113 }, "SUCCESS": true, "DATA": { "GWDEVICE": "wlan0", "GATEWAY": "192.168.0.254", "NOMINALPWR": 9, "PSENSLTSH": 10, "WMAC": "AA:AA:AA:71:F2:72", "plzbridge": "2.2.1 2022-10-24 11:13:21", "HWTYPE": 7, "AUTONOMYTYPE": 2, "CHRONOTYPE": 5, "WMODE": "sta", "STOVETYPE": 1, "SPLMIN": 6, "DSPTYPE": 3, "FAN2MODE": 3, "MOD": 1295, "FAN2TYPE": 2, "EGW": "192.168.0.116", "MAC": "AA:AA:AA:71:F2:72", "FLUID": 0, "SPLMAX": 51, "ECBL": "down", "PSENSLMIN": 240, "WADR": "192.168.0.10", "CLOUD_ENABLED": true, "EPR": "static", "PELLETTYPE": 3, "PSENSLMAX": 12, "SNCHK": 1, "PSENSTYPE": 0, "CONFIG": 1, "DSPFWVER": 37, "LABEL": "Emily", "EMSK": "255.255.255.0", "BLEMBMODE": 17, "WGW": "192.168.0.254", "UICONFIG": 1, "BLEDSPMODE": 1, "MAINTPROBE": 4, "ICONN": 1, "EBCST": "", "FWDATE": "2024-07-17", "WSSID": "some_wifi", "WPR": "dhcp", "VER": 3, "WPWR": "-72 dBm", "MBTYPE": 0, "WENC": "psk2", "CBTYPE": "miniembplug", "EADR": "192.168.0.177", "sendmsg": "2.2.1 2021-10-12 12:22:53", "WMSK": "255.255.255.0", "EMAC": "AA:AA:AA:71:F2:72", "CORE": 44, "SN": "XXXXXXXXXXXXXX", "APLCONN": 1, "DNS": ["192.168.1.254", "192.168.1.254", "127.0.0.1"], "SYSTEM": "2.5.3 2022-11-08 15:52:19 (9e8e347)", "WBCST": "192.168.0.255", "WCH": "6" } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_ginger/000077500000000000000000000000001474077214600255405ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_ginger/GET_ALLS.json000066400000000000000000000013271474077214600276700ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1728831839 }, "SUCCESS": true, "DATA": { "T2": 25.1, "F2LF": 1, "PQT": 1807, "PWR": 3, "T4": 0, "FANLMINMAX": [0, 5, 0, 1, 0, 1], "FDR": 0, "F2V": 0, "MOD": 324, "DPT": 1632, "APLWDAY": 7, "MAC": "40:F3:85:71:23:45", "SETP": 21, "APLTS": "2024-01-01 01:23:45", "STATUS": 9, "T3": 45, "T1": 21.5, "PUMP": 0, "T5": 0, "F1RPM": 0, "OUT": 0, "F1V": 0, "SN": "AB012345678901234567890", "LSTATUS": 51, "EFLAGS": 1024, "F2L": 6, "CORE": 129, "DP": 111, "CHRSTATUS": 0, "IN": 13, "FWDATE": "2016-06-14", "VER": 46, "MBTYPE": 0 } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_ginger/GET_ALLS_larger_PQT.json000066400000000000000000000013271474077214600317500ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1728831839 }, "SUCCESS": true, "DATA": { "T2": 25.1, "F2LF": 1, "PQT": 2000, "PWR": 3, "T4": 0, "FANLMINMAX": [0, 5, 0, 1, 0, 1], "FDR": 0, "F2V": 0, "MOD": 324, "DPT": 1632, "APLWDAY": 7, "MAC": "40:F3:85:71:23:45", "SETP": 21, "APLTS": "2024-01-01 01:23:45", "STATUS": 9, "T3": 45, "T1": 21.5, "PUMP": 0, "T5": 0, "F1RPM": 0, "OUT": 0, "F1V": 0, "SN": "AB012345678901234567890", "LSTATUS": 51, "EFLAGS": 1024, "F2L": 6, "CORE": 129, "DP": 111, "CHRSTATUS": 0, "IN": 13, "FWDATE": "2016-06-14", "VER": 46, "MBTYPE": 0 } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_ginger/GET_ALLS_smaller_PQT.json000066400000000000000000000013271474077214600321330ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1728831839 }, "SUCCESS": true, "DATA": { "T2": 25.1, "F2LF": 1, "PQT": 1500, "PWR": 3, "T4": 0, "FANLMINMAX": [0, 5, 0, 1, 0, 1], "FDR": 0, "F2V": 0, "MOD": 324, "DPT": 1632, "APLWDAY": 7, "MAC": "40:F3:85:71:23:45", "SETP": 21, "APLTS": "2024-01-01 01:23:45", "STATUS": 9, "T3": 45, "T1": 21.5, "PUMP": 0, "T5": 0, "F1RPM": 0, "OUT": 0, "F1V": 0, "SN": "AB012345678901234567890", "LSTATUS": 51, "EFLAGS": 1024, "F2L": 6, "CORE": 129, "DP": 111, "CHRSTATUS": 0, "IN": 13, "FWDATE": "2016-06-14", "VER": 46, "MBTYPE": 0 } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_ginger/GET_STDT.json000066400000000000000000000027251474077214600277160ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET STDT", "TS": 1728830701 }, "SUCCESS": true, "DATA": { "GWDEVICE": "wlan0", "GATEWAY": "192.168.1.1", "PSENSLTSH": 0, "WMAC": "40:F3:85:71:23:45", "plzbridge": "2.2.1 2021-10-08 09:30:45", "HWTYPE": 6, "NOMINALPWR": 9, "AUTONOMYTYPE": 1, "WMODE": "sta", "STOVETYPE": 1, "CHRONOTYPE": 5, "SPLMIN": 6, "DSPFWVER": 31, "MOD": 324, "FAN2MODE": 3, "EGW": "0.0.0.0", "MAC": "40:F3:85:71:23:45", "FAN2TYPE": 2, "SPLMAX": 51, "ECBL": "down", "FLUID": 0, "WADR": "192.168.1.2", "CLOUD_ENABLED": true, "EPR": "dhcp", "PELLETTYPE": 2, "PSENSLMIN": 0, "SNCHK": 1, "PSENSLMAX": 0, "PSENSTYPE": 0, "CONFIG": 1, "LABEL": "Name", "EMSK": "0.0.0.0", "BLEMBMODE": 1, "WGW": "192.168.1.1", "UICONFIG": 1, "BLEDSPMODE": 1, "MAINTPROBE": 0, "ICONN": 1, "EBCST": "", "FWDATE": "2016-06-14", "WSSID": "my_wifi", "WPR": "dhcp", "VER": 46, "WPWR": "-48 dBm", "MBTYPE": 0, "WENC": "psk2", "CBTYPE": "miniembplug", "EADR": "0.0.0.0", "sendmsg": "2.1.2 2018-03-28 10:19:09", "WMSK": "255.255.255.0", "EMAC": "40:F3:85:11:22:33", "CORE": 129, "SN": "AB012345678901234567890", "APLCONN": 1, "DNS": ["192.168.1.1", "192.168.1.1", "127.0.0.1"], "SYSTEM": "2.5.3 2021-10-08 10:30:20 (657c8cf)", "WBCST": "192.168.1.255", "WCH": "6" } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_juliepro2/000077500000000000000000000000001474077214600262005ustar00rootroot00000000000000dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_juliepro2/GET_ALLS.json000066400000000000000000000013201474077214600303210ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET ALLS", "TS": 1733083380 }, "SUCCESS": true, "DATA": { "T2": 22.4, "F2LF": 2, "PQT": 1384, "PWR": 3, "CHRSTATUS": 0, "FANLMINMAX": [0, 5, 0, 5, 0, 5], "FDR": 0, "F2V": 150, "MOD": 1217, "DPT": 0, "APLWDAY": 1, "MAC": "--", "SETP": 20, "APLTS": "2024-12-02 15:39:47", "STATUS": 6, "T3": 220, "T1": 0, "PUMP": 0, "T5": 18.4, "F1RPM": 1524, "OUT": 6, "SN": "--", "F1V": 1524, "EFLAGS": 0, "LSTATUS": 6, "T4": 0, "F3L": 0, "F2L": 7, "CORE": 34, "F4L": 3, "IN": 11, "DP": 641, "FWDATE": "2020-11-23", "VER": 4, "MBTYPE": 0 } } dotvav-py-palazzetti-api-0334f95/tests/mock_json/palazzetti_juliepro2/GET_STDT.json000066400000000000000000000023411474077214600303500ustar00rootroot00000000000000{ "INFO": { "RSP": "OK", "CMD": "GET STDT", "TS": 1733085473 }, "SUCCESS": true, "DATA": { "GWDEVICE": "wlan0", "GATEWAY": "--", "PSENSLTSH": 0, "WMAC": "--", "plzbridge": "2.2.1 2021-12-06 06:48:51", "HWTYPE": 7, "NOMINALPWR": 9, "AUTONOMYTYPE": 1, "WMODE": "sta", "STOVETYPE": 1, "CHRONOTYPE": 5, "SPLMIN": 6, "DSPFWVER": 48, "MOD": 1217, "FAN2MODE": 3, "EGW": "--", "MAC": "--", "FAN2TYPE": 3, "SPLMAX": 51, "ECBL": "down", "FLUID": 0, "WADR": "--", "CLOUD_ENABLED": true, "EPR": "static", "PELLETTYPE": 2, "PSENSLMIN": 0, "SNCHK": 1, "PSENSLMAX": 0, "PSENSTYPE": 0, "CONFIG": 1, "LABEL": "Poele", "BLEMBMODE": 17, "WGW": "--", "UICONFIG": 1, "BLEDSPMODE": 17, "MAINTPROBE": 4, "ICONN": 0, "EBCST": "", "FWDATE": "2020-11-23", "WPR": "dhcp", "VER": 4, "WPWR": "-58 dBm", "MBTYPE": 0, "WENC": "psk2", "CBTYPE": "miniembplug", "EADR": "--", "sendmsg": "2.2.1 2021-10-12 12:22:53", "CORE": 34, "SN": "--", "APLCONN": 1, "DNS": "", "SYSTEM": "2.5.3 2021-12-06 06:54:35 (5a791b6)", "WBCST": "--", "WCH": "13" } } dotvav-py-palazzetti-api-0334f95/tests/test_client.py000066400000000000000000000144061474077214600227300ustar00rootroot00000000000000"""Test the PalazzettiClient class.""" from unittest.mock import patch import pytest from pypalazzetti.client import PalazzettiClient from pypalazzetti.config import PalazzettiClientConfig from pypalazzetti.state import _PalazzettiAPIData from pypalazzetti.temperature import TemperatureDescriptionKey def stdt_response(device: str = "palazzetti_ginger"): with open(f"./tests/mock_json/{device}/GET_STDT.json") as f: return f.read() def alls_response(device: str = "palazzetti_ginger", variant: str = None): variant_modifier = ("_" + variant) if variant else "" with open(f"./tests/mock_json/{device}/GET_ALLS{variant_modifier}.json") as f: return f.read() class MockResponse: def __init__(self, status, text): self.status = status self._text = text async def text(self): return self._text async def __aexit__(self, exc_type, exc, tb): pass async def __aenter__(self): return self @pytest.fixture def mock_stdt_response_ok(device: str = "palazzetti_ginger"): return MockResponse(status=200, text=stdt_response(device=device)) @pytest.fixture def mock_alls_response_ok(device: str = "palazzetti_ginger"): return MockResponse(status=200, text=alls_response(device=device)) @pytest.fixture def mock_alls_smaller_pqt(device: str = "palazzetti_ginger", variant: str = None): return MockResponse( status=200, text=alls_response(device=device, variant="smaller_PQT") ) @pytest.fixture def mock_alls_larger_pqt(device: str = "palazzetti_ginger", variant: str = None): return MockResponse( status=200, text=alls_response(device=device, variant="larger_PQT") ) async def mock_ready_client(device: str = "palazzetti_ginger", variant: str = None): client = PalazzettiClient("127.0.0.1") # Connect and set properties with ( patch( "aiohttp.ClientSession.get", return_value=MockResponse(status=200, text=stdt_response(device=device)), ), ): assert await client.connect() # Set state attributes with ( patch( "aiohttp.ClientSession.get", return_value=MockResponse(status=200, text=alls_response(device=device)), ), ): assert await client.update_state() return client async def test_connect(): """Test the connect function.""" client = PalazzettiClient("127.0.0.1") with patch( "pypalazzetti.client.PalazzettiClient._execute_command", return_value=_PalazzettiAPIData(stdt_response()), ) as exec: success = await client.connect() assert len(exec.mock_calls) == 1 assert success async def test_execute_command(mock_stdt_response_ok): """Test the _execute_command function""" client = PalazzettiClient("127.0.0.1") with ( patch("aiohttp.ClientSession.get", return_value=mock_stdt_response_ok) as get, ): success = await client._execute_command(command="GET STDT") assert len(get.mock_calls) == 1 assert success async def test_state_ginger(mock_stdt_response_ok, mock_alls_response_ok): """Test the functions that return the state.""" client = PalazzettiClient("127.0.0.1") # Connect and set properties with ( patch("aiohttp.ClientSession.get", return_value=mock_stdt_response_ok), ): assert await client.connect() # Set state attributes with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_response_ok), ): assert await client.update_state() assert client.is_on assert not client.is_heating assert client.target_temperature == 21 assert client.host == "127.0.0.1" assert client.mac == "40:F3:85:71:23:45" assert client.pellet_quantity == 1807 assert client.power_mode == 3 assert client.fan_speed == 6 assert client.status == 51 assert client.name == "Name" temperatures = { sensor.description_key: getattr(client, sensor.state_property) for sensor in client.list_temperatures() } assert len(temperatures) == 2 assert temperatures[TemperatureDescriptionKey.ROOM_TEMP] == 21.5 assert temperatures[TemperatureDescriptionKey.WOOD_COMBUSTION_TEMP] == 45 async def test_pellet_quantity_not_sanitize( mock_alls_response_ok, mock_alls_smaller_pqt, mock_alls_larger_pqt, ): client = PalazzettiClient( "127.0.0.1", config=PalazzettiClientConfig(pellet_quantity_sanitize=False) ) # Set state attributes with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_response_ok), ): assert await client.update_state() assert client.pellet_quantity == 1807 # Set a smaller PQT with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_smaller_pqt), ): assert await client.update_state() assert client.pellet_quantity == 1500 # Set a smaller PQT with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_larger_pqt), ): assert await client.update_state() assert client.pellet_quantity == 2000 async def test_pellet_quantity_sanitize( mock_alls_response_ok, mock_alls_smaller_pqt, mock_alls_larger_pqt, ): client = PalazzettiClient( "127.0.0.1", config=PalazzettiClientConfig(pellet_quantity_sanitize=True) ) # Set state attributes with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_response_ok), ): assert await client.update_state() assert client.pellet_quantity == 1807 # Set a smaller PQT with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_smaller_pqt), ): assert await client.update_state() assert client.pellet_quantity == 1807 # Set a smaller PQT with ( patch("aiohttp.ClientSession.get", return_value=mock_alls_larger_pqt), ): assert await client.update_state() assert client.pellet_quantity == 2000 @pytest.mark.parametrize( "device", [ "jotul_pf911s", "palazzetti_beatrice", "palazzetti_emily", "palazzetti_ginger", "palazzetti_juliepro2", ], ) async def test_snapshot(device, snapshot): """Perform snapshot tests""" client = await mock_ready_client(device=device) assert client == snapshot assert client._state == snapshot