diff --git a/tests/lite.py b/tests/lite.py index 051839f..98de5cf 100644 --- a/tests/lite.py +++ b/tests/lite.py @@ -23,13 +23,13 @@ _LOGGER.debug("Result is %s " % result) -_LOGGER.info("Initial state: device is %s, light is %s, sound is %s, heater is %s, fan_speed is %d, target_temp is %d", +_LOGGER.info("Initial state: device is %s, light is %s, sound is %s, heater is %s, fan_speed is %d, heater_temp is %d", device.state, device.light, device.sound, device.heater, device.fan_speed, - device.target_temp + device.heater_temp ) diff --git a/tests/s4.py b/tests/s4.py index 86408d2..8a34da2 100644 --- a/tests/s4.py +++ b/tests/s4.py @@ -15,7 +15,7 @@ def read_state(tion: S4): tion.sound, tion.heater, tion.fan_speed, - tion.target_temp) + tion.heater_temp) def pause(interval: int = 10): diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/tion.py b/tests/unit/tion.py new file mode 100644 index 0000000..e928df5 --- /dev/null +++ b/tests/unit/tion.py @@ -0,0 +1,37 @@ +import sys +import unittest +import unittest.mock as mock +from unittest.mock import MagicMock +sys.modules['bluepy'] = MagicMock() +sys.modules['bluepy.btle'] = MagicMock() +import tion_btle.tion # noqa: E402 + + +class TionTests(unittest.TestCase): + def setUp(self): + self.patch = mock.patch('tion_btle.tion.tion.__init__', return_value=None) + self.patch.start() + self._tion = tion_btle.tion.tion("") + + def tearDown(self): + self.patch.stop() + + def test___detect_heating_state(self): + # state, in, out, target, heater + _variants = [ + ["on", -2, 21, 20, "on"], + ["on", 15, 21, 20, "on"], + ["off", 15, 21, 16, "on"], + ["off", 15, 21, 20, "off"], + ] + + for expect, in_temp, out_temp, heater_temp, heater in _variants: + with self.subTest(expect=expect, + in_temp=in_temp, out_temp=out_temp, heater_temp=heater_temp, heater=heater): + # call private __detect_heating_state from tion class + self._tion._tion__detect_heating_state(in_temp, out_temp, heater_temp, heater) + self.assertEqual(self._tion.heating, expect) + + +if __name__ == '__main__': + unittest.main() diff --git a/tion_btle/lite.py b/tion_btle/lite.py index a414608..3fee785 100644 --- a/tion_btle/lite.py +++ b/tion_btle/lite.py @@ -78,7 +78,7 @@ def _decode_response(self, response: bytearray): self._have_heater = response[0] >> 7 & 1 self._mode = response[2] - self._target_temp = response[3] + self._heater_temp = response[3] self._fan_speed = response[4] self._in_temp = self.decode_temperature(response[5]) self._out_temp = self.decode_temperature(response[6]) @@ -122,7 +122,7 @@ def encode_state(): return result sb = 0x00 # ?? - tb = 0x02 if (self.target_temp > 0 or self.fan_speed > 0) else 0x01 + tb = 0x02 if (self.heater_temp > 0 or self.fan_speed > 0) else 0x01 lb = [0x60, 0x00] if sb == 0 else [0x00, 0x00] return bytearray( diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 3e2f176..2fbf6c9 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -97,7 +97,7 @@ def _decode_response(self, response: bytearray): self._mode = int(list("{:02x}".format(response[2]))[0]) self._heater = response[4] & 1 self._state = response[4] >> 1 & 1 - self._target_temp = response[3] + self._heater_temp = response[3] self._sound = response[4] >> 3 & 1 self._out_temp = self.decode_temperature(response[7]) self._in_temp = self.decode_temperature(response[8]) diff --git a/tion_btle/s4.py b/tion_btle/s4.py index ff205e4..a84bdd0 100644 --- a/tion_btle/s4.py +++ b/tion_btle/s4.py @@ -37,7 +37,7 @@ def _decode_response(self, response: bytearray): _LOGGER.debug("Data is %s", bytes(response).hex()) try: self._mode = response[2] - self._target_temp = response[3] + self._heater_temp = response[3] self._fan_speed = response[4] self._in_temp = self.decode_temperature(response[5]) self._out_temp = self.decode_temperature(response[6]) diff --git a/tion_btle/tion.py b/tion_btle/tion.py index 993e5a8..81577f4 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -108,7 +108,7 @@ def __init__(self, mac: str): # states self._in_temp: int = 0 self._out_temp: int = 0 - self._target_temp: int = 0 + self._heater_temp: int = 0 self._fan_speed: int = 0 self._mode: int = 0 self._state: bool = False @@ -184,7 +184,7 @@ def __generate_common_json(self) -> dict: "mode": self.mode, "out_temp": self.out_temp, "in_temp": self.in_temp, - "heater_temp": self.target_temp, + "heater_temp": self._heater_temp, "fan_speed": self.fan_speed, "filter_remain": self.filter_remain, "time": strftime("%H:%M", localtime()), @@ -195,13 +195,13 @@ def __generate_common_json(self) -> dict: def __detect_heating_state(self, in_temp: int = None, out_temp: int = None, - target_temp: int = None, + heater_temp: int = None, heater: str = None) -> None: """ Tries to guess is heater working right now :param in_temp: air intake temperature :param out_temp: ait outtake temperature - :param target_temp: target temperature for heater + :param heater_temp: target temperature for heater :param heater: heater state :return: None """ @@ -209,25 +209,24 @@ def __detect_heating_state(self, in_temp = self.in_temp if out_temp is None: out_temp = self.out_temp - if target_temp is None: - target_temp = self.target_temp + if heater_temp is None: + heater_temp = self.heater_temp if heater is None: heater = self.heater if heater == "off": self.heating = "off" else: - if in_temp < target_temp and out_temp - target_temp < 3: + if heater_temp - in_temp > 3 and out_temp > in_temp: self.heating = "on" else: self.heating = "off" - def get(self, keep_connection: bool = False) -> dict: + def get_state_from_breezer(self, keep_connection: bool = False) -> None: """ - Get current device state + Get current state from breezer :param keep_connection: should we keep connection to device or disconnect after getting data - :return: - dictionary with device state + :return: None """ try: self.connect() @@ -242,11 +241,39 @@ def get(self, keep_connection: bool = False) -> dict: self._decode_response(response) self.__detect_heating_state() + + def get(self, keep_connection: bool = False, skip_update: bool = False) -> dict: + """ + Report current breezer state + :param skip_update: may we skip requesting data from breezer or not + :param keep_connection: should we keep connection to device or disconnect after getting data + :return: + dictionary with device state + """ + if skip_update and self.have_breezer_state: + _LOGGER.debug(f"Skipping getting state from breezer because skip_update={skip_update} and " + f"have_breezer_state={self.have_breezer_state}") + else: + self.get_state_from_breezer(keep_connection) common = self.__generate_common_json() model_specific_data = self._generate_model_specific_json() return {**common, **model_specific_data} + def _set_internal_state_from_request(self, request: dict) -> None: + """ + Set internal parameters based on user request + :param request: changed breezer parameter from set request + :return: None + """ + for p in ['fan_speed', 'heater_temp', 'heater', 'sound', 'mode', 'state']: + # ToDo: lite have additional parameters to set: "light" and "co2_auto_control", so we should get this + # list from class + try: + setattr(self, p, request[p]) + except KeyError: + pass + def set(self, new_settings=None) -> None: """ Set new breezer state @@ -265,13 +292,14 @@ def set(self, new_settings=None) -> None: try: self.connect() - current_settings = self.get() + current_settings = self.get(skip_update=True) merged_settings = {**current_settings, **new_settings} encoded_request = self._encode_request(merged_settings) _LOGGER.debug("Will write %s", encoded_request) self._send_request(encoded_request) + self._set_internal_state_from_request(new_settings) finally: self.disconnect() @@ -431,13 +459,21 @@ def heater(self) -> str: def heater(self, new_state: str): self._heater = self._encode_state(new_state) + @property + def heater_temp(self) -> int: + return self._heater_temp + + @heater_temp.setter + def heater_temp(self, new_temp: int): + self._heater_temp = new_temp + @property def target_temp(self) -> int: - return self._target_temp + return self.heater_temp @target_temp.setter def target_temp(self, new_temp: int): - self._target_temp = new_temp + self.heater_temp = new_temp @property def in_temp(self): @@ -473,6 +509,10 @@ def heating(self, new_state: str): def mode(self): return self._process_mode(self._mode) + @mode.setter + def mode(self, new_state: str): + self._mode = self._encode_mode(new_state) + @property def model(self) -> str: return self._model @@ -542,6 +582,7 @@ def connect(self): self.__connections_count = 0 if self.__connections_count == 0: + self.have_breezer_state = False self._connect() self.__connections_count += 1 @@ -550,6 +591,7 @@ def disconnect(self): self.__connections_count -= 1 if self.__connections_count <= 0: self._disconnect() + self.have_breezer_state = False @property @abc.abstractmethod