Skip to content

Commit

Permalink
Reformat code and release version 4.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
jvitkauskas committed Dec 20, 2023
1 parent 53d2b6e commit 17f0274
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 63 deletions.
4 changes: 3 additions & 1 deletion demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ def help():
print("syntax: demo.py [options]")
print("options:")
print(" --host <hvac_ip> ... network address of your HVAC device")
print(" --port [hvac_port] ... optional TCP port if device is behind the proxy")
print(
" --port [hvac_port] ... optional TCP port if device is behind the proxy"
)
print()
print("examples:")
print(" demo.py --host 192.168.0.125 --port 502")
Expand Down
71 changes: 51 additions & 20 deletions pybls21/client.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
from threading import Lock
from typing import Callable, List, Optional

from pymodbus.client import AsyncModbusTcpClient

from .constants import *
from .exceptions import *
from .models import ClimateDevice, ClimateEntityFeature, HVACAction, HVACMode, TEMP_CELSIUS

from typing import Callable, List, Optional
from threading import Lock
from .models import (
TEMP_CELSIUS,
ClimateDevice,
ClimateEntityFeature,
HVACAction,
HVACMode,
)


def _parse_firmware_version(firmware_info: List[int]) -> str:
major, minor = firmware_info[0].to_bytes(2, 'big')
major, minor = firmware_info[0].to_bytes(2, "big")

day, month = firmware_info[1].to_bytes(2, 'big')
day, month = firmware_info[1].to_bytes(2, "big")
year: int = firmware_info[2]

return f'{major}.{minor} ({year}-{month:02d}-{day:02d})'
return f"{major}.{minor} ({year}-{month:02d}-{day:02d})"


class S21Client:
Expand All @@ -41,7 +47,9 @@ async def set_fan_mode(self, mode: int) -> None:
await self._do_with_connection(lambda: self._set_fan_mode(mode))

async def set_manual_fan_speed_percent(self, speed_percent: int) -> None:
await self._do_with_connection(lambda: self._set_manual_fan_speed_percent(speed_percent))
await self._do_with_connection(
lambda: self._set_manual_fan_speed_percent(speed_percent)
)

async def set_temperature(self, temp_celsius: int) -> None:
await self._do_with_connection(lambda: self._set_temperature(temp_celsius))
Expand Down Expand Up @@ -81,14 +89,16 @@ async def _poll(self) -> ClimateDevice:
current_fan_level: int = holding_registers[HR_SPEED_MODE] # 255 - manual
temp_before_heating_x10: int = input_registers[IR_CurTEMP_SuAirIn]
temp_after_heating_x10: int = input_registers[IR_CurTEMP_SuAirOut]
firmware_info: List[int] = input_registers[IR_VerMAIN_FMW_start:IR_VerMAIN_FMW_end+1]
firmware_info: List[int] = input_registers[
IR_VerMAIN_FMW_start : IR_VerMAIN_FMW_end + 1
]
operation_mode: int = holding_registers[HR_OPERATION_MODE]
manual_fan_speed_percent: int = holding_registers[HR_ManualSPEED]

self.device = ClimateDevice(
available=True,
name="Blauberg S21",
unique_id=f'S21_{self.host}_{self.port}',
unique_id=f"S21_{self.host}_{self.port}",
temperature_unit=TEMP_CELSIUS, # Seems like no Fahrenheit option is available
precision=1,
current_temperature=temp_after_heating_x10 / 10,
Expand All @@ -97,20 +107,41 @@ async def _poll(self) -> ClimateDevice:
min_temp=15,
max_temp=30,
current_humidity=None if current_humidity == 0 else current_humidity,
hvac_mode=HVACMode.OFF if not is_on
else HVACMode.FAN_ONLY if operation_mode == 0
else HVACMode.HEAT if operation_mode == 1
else HVACMode.COOL if operation_mode == 2
hvac_mode=HVACMode.OFF
if not is_on
else HVACMode.FAN_ONLY
if operation_mode == 0
else HVACMode.HEAT
if operation_mode == 1
else HVACMode.COOL
if operation_mode == 2
else HVACMode.AUTO,
hvac_action=HVACAction.OFF if not is_on
else HVACAction.FAN if operation_mode == 0
else HVACAction.HEATING if (operation_mode == 1 or (temp_before_heating_x10 < temp_after_heating_x10))
else HVACAction.COOLING if (operation_mode == 2 or (temp_before_heating_x10 > temp_after_heating_x10))
hvac_action=HVACAction.OFF
if not is_on
else HVACAction.FAN
if operation_mode == 0
else HVACAction.HEATING
if (
operation_mode == 1
or (temp_before_heating_x10 < temp_after_heating_x10)
)
else HVACAction.COOLING
if (
operation_mode == 2
or (temp_before_heating_x10 > temp_after_heating_x10)
)
else HVACAction.IDLE,
hvac_modes=[HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO, HVACMode.FAN_ONLY],
hvac_modes=[
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.AUTO,
HVACMode.FAN_ONLY,
],
fan_mode=current_fan_level,
fan_modes=[x + 1 for x in range(max_fan_level)] + [255],
supported_features=ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
supported_features=ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.FAN_MODE,
manufacturer="Blauberg",
model="S21",
sw_version=_parse_firmware_version(firmware_info),
Expand Down
4 changes: 1 addition & 3 deletions pybls21/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import List, NamedTuple, Optional

from enum import Enum

from typing import List, NamedTuple, Optional

TEMP_CELSIUS: str = "°C"

Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@

setuptools.setup(
name="pybls21",
version="4.0.0",
version="4.0.1",
author="Julius Vitkauskas",
author_email="[email protected]",
description="An api allowing control of AC state (temperature, on/off, speed) of an Blauberg S21 device locally over TCP",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/jvitkauskas/pybls21",
packages=setuptools.find_packages(exclude=["tests"]),
install_requires=['pymodbus>=3.5.4,<4.0'],
install_requires=["pymodbus>=3.5.4,<4.0"],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.7',
python_requires=">=3.7",
)
94 changes: 58 additions & 36 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import unittest

from pyModbusTCP.server import ModbusServer, DataBank
from pyModbusTCP.server import DataBank, ModbusServer

from pybls21.client import S21Client
from pybls21.constants import *
from pybls21.exceptions import *
from pybls21.models import HVACAction, HVACMode, ClimateDevice, ClimateEntityFeature
from pybls21.models import ClimateDevice, ClimateEntityFeature, HVACAction, HVACMode


class TestClient(unittest.IsolatedAsyncioTestCase):
@classmethod
def setUpClass(cls):
cls.server = ModbusServer(host='localhost', port=5502, no_block=True, data_bank=TestDataBank())
cls.server = ModbusServer(
host="localhost", port=5502, no_block=True, data_bank=TestDataBank()
)
cls.server.start()

@classmethod
Expand Down Expand Up @@ -41,39 +43,51 @@ async def test_poll(self):
self.server.data_bank.set_input_registers(IR_ALARM, [2])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirIn, [108])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirOut, [192])
self.server.data_bank.set_input_registers(IR_VerMAIN_FMW_start, [36, 2053, 2019])
self.server.data_bank.set_input_registers(
IR_VerMAIN_FMW_start, [36, 2053, 2019]
)

client = S21Client(host=self.server.host, port=self.server.port)
device = await client.poll()

self.assertEqual(device, ClimateDevice(
available=True,
name="Blauberg S21",
unique_id=f'S21_{self.server.host}_{self.server.port}',
temperature_unit="°C",
precision=1,
current_temperature=19.2,
target_temperature=15,
target_temperature_step=1,
min_temp=15,
max_temp=30,
current_humidity=None,
hvac_mode=HVACMode.FAN_ONLY,
hvac_action=HVACAction.FAN,
hvac_modes=[HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO, HVACMode.FAN_ONLY],
fan_mode=2,
fan_modes=[1, 2, 3, 255],
supported_features=ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
manufacturer="Blauberg",
model="S21",
sw_version="0.36 (2019-05-08)",
is_boosting=False,
current_intake_temperature=10.8,
manual_fan_speed_percent=100,
max_fan_level=3,
filter_state=3,
alarm_state=2
))
self.assertEqual(
device,
ClimateDevice(
available=True,
name="Blauberg S21",
unique_id=f"S21_{self.server.host}_{self.server.port}",
temperature_unit="°C",
precision=1,
current_temperature=19.2,
target_temperature=15,
target_temperature_step=1,
min_temp=15,
max_temp=30,
current_humidity=None,
hvac_mode=HVACMode.FAN_ONLY,
hvac_action=HVACAction.FAN,
hvac_modes=[
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.AUTO,
HVACMode.FAN_ONLY,
],
fan_mode=2,
fan_modes=[1, 2, 3, 255],
supported_features=ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.FAN_MODE,
manufacturer="Blauberg",
model="S21",
sw_version="0.36 (2019-05-08)",
is_boosting=False,
current_intake_temperature=10.8,
manual_fan_speed_percent=100,
max_fan_level=3,
filter_state=3,
alarm_state=2,
),
)

async def test_poll_when_device_is_off(self):
self.server.data_bank.set_coils(CL_POWER, [False])
Expand Down Expand Up @@ -152,7 +166,9 @@ async def test_poll_when_auto_mode_is_set_and_output_temperature_is_lower(self):
self.assertEqual(device.hvac_mode, HVACMode.AUTO)
self.assertEqual(device.hvac_action, HVACAction.COOLING)

async def test_poll_when_auto_mode_is_set_and_in_temperature_matches_out_temperature(self):
async def test_poll_when_auto_mode_is_set_and_in_temperature_matches_out_temperature(
self,
):
self.server.data_bank.set_holding_registers(HR_OPERATION_MODE, [3])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirIn, [10])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirOut, [10])
Expand All @@ -163,7 +179,9 @@ async def test_poll_when_auto_mode_is_set_and_in_temperature_matches_out_tempera
self.assertEqual(device.hvac_mode, HVACMode.AUTO)
self.assertEqual(device.hvac_action, HVACAction.IDLE)

async def test_poll_when_auto_mode_is_set_and_in_temperature_is_cooler_than_out_temperature(self):
async def test_poll_when_auto_mode_is_set_and_in_temperature_is_cooler_than_out_temperature(
self,
):
self.server.data_bank.set_holding_registers(HR_OPERATION_MODE, [3])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirIn, [5])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirOut, [10])
Expand All @@ -174,7 +192,9 @@ async def test_poll_when_auto_mode_is_set_and_in_temperature_is_cooler_than_out_
self.assertEqual(device.hvac_mode, HVACMode.AUTO)
self.assertEqual(device.hvac_action, HVACAction.HEATING)

async def test_poll_when_auto_mode_is_set_and_in_temperature_is_hotter_than_out_temperature(self):
async def test_poll_when_auto_mode_is_set_and_in_temperature_is_hotter_than_out_temperature(
self,
):
self.server.data_bank.set_holding_registers(HR_OPERATION_MODE, [3])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirIn, [10])
self.server.data_bank.set_input_registers(IR_CurTEMP_SuAirOut, [5])
Expand Down Expand Up @@ -315,7 +335,9 @@ class TestDataBank(DataBank):
__test__ = False

def __init__(self):
super().__init__(coils_size=25, d_inputs_size=72, h_regs_size=182, i_regs_size=51)
super().__init__(
coils_size=25, d_inputs_size=72, h_regs_size=182, i_regs_size=51
)
self.reset()

def reset(self):
Expand Down

0 comments on commit 17f0274

Please sign in to comment.