Skip to content

Commit

Permalink
VeraThermostat adapts to two setpoints thermostats (#159)
Browse files Browse the repository at this point in the history
* VeraThermostat adapts to two setpoints thermostats

* Improve test coverage

* isort fixes

* isort fixes

---------

Co-authored-by: Max Velitchko <13109133+maximvelichko@users.noreply.github.com>
  • Loading branch information
maximvelichko and maximvelichko committed Jul 3, 2024
1 parent c3fcc06 commit 9a6a329
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 32 deletions.
54 changes: 51 additions & 3 deletions pyvera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,17 +1258,17 @@ def set_temperature(self, temp: float) -> None:
"""Set current goal temperature / setpoint."""

self.set_service_value(
self.thermostat_setpoint, "CurrentSetpoint", "NewCurrentSetpoint", temp
self._thermostat_setpoint, "CurrentSetpoint", "NewCurrentSetpoint", temp
)

self.set_cache_value("setpoint", temp)
self.set_cache_value(self._setpoint_cache_value_name, temp)

def get_current_goal_temperature(self, refresh: bool = False) -> Optional[float]:
"""Get current goal temperature / setpoint."""
if refresh:
self.refresh()
try:
return float(self.get_value("setpoint"))
return float(self.get_value(self._setpoint_cache_value_name))
except (TypeError, ValueError):
return None

Expand Down Expand Up @@ -1343,6 +1343,54 @@ def fan_cycle(self) -> None:
"""Set fan to cycle."""
self.set_fan_mode("PeriodicOn")

def _has_double_setpoints(self) -> bool:
"""Determines if a thermostate has two setpoints"""
if self.get_value("setpoint"):
return False

if self.get_value("heatsp") and self.get_value("coolsp"):
return True

return False

def _is_heating_recommended(self) -> bool:
mode = self.get_value("mode")
state = self.get_value("hvacstate")

if mode == "HeatOn":
return True

if mode == "CoolOn":
return False

if state == "Heating":
return True

if state == "Cooling":
return False

return True

@property
def _setpoint_cache_value_name(self) -> str:
if self._has_double_setpoints():
if self._is_heating_recommended():
return "heatsp"
else:
return "coolsp"
else:
return "setpoint"

@property
def _thermostat_setpoint(self) -> str:
if self._has_double_setpoints():
if self._is_heating_recommended():
return self.thermostat_heat_setpoint
else:
return self.thermostat_cool_setpoint
else:
return self.thermostat_setpoint


class VeraSceneController(VeraDevice):
"""Class to represent a scene controller."""
Expand Down
34 changes: 34 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ def callback(req: requests.PreparedRequest) -> ResponsesResponse:
DEVICE_GARAGE_DOOR_ID = 47
DEVICE_LOCK_ID = 10
DEVICE_THERMOSTAT_ID = 11
DEVICE_THERMOSTAT2_ID = 18
DEVICE_CURTAIN_ID = 12
DEVICE_SCENE_CONTROLLER_ID = 13
DEVICE_LIGHT_SENSOR_ID = 14
Expand Down Expand Up @@ -566,6 +567,31 @@ def callback(req: requests.PreparedRequest) -> ResponsesResponse:
"watts": 23,
"comment": "",
},
{
"name": "Thermostat 2",
"altid": "5",
"id": DEVICE_THERMOSTAT2_ID,
"category": CATEGORY_THERMOSTAT,
"subcategory": 0,
"room": 0,
"parent": 1,
"configured": "1",
"commFailure": "0",
"armedtripped": "1",
"lasttrip": "1561049427",
"tripped": "1",
"armed": "0",
"status": "0",
"state": -1,
"mode": "Off",
"fanmode": "Off",
"hvacstate": "Off",
"heatsp": 7,
"coolsp": 17,
"temperature": 9,
"watts": 23,
"comment": "",
},
{
"name": "Curtain 1",
"altid": "5",
Expand Down Expand Up @@ -816,6 +842,14 @@ def callback(req: requests.PreparedRequest) -> ResponsesResponse:
"tooltip": {"display": 0},
"status": -1,
},
{
"id": DEVICE_THERMOSTAT2_ID,
"states": [],
"Jobs": [],
"PendingJobs": 0,
"tooltip": {"display": 0},
"status": -1,
},
{
"id": DEVICE_CURTAIN_ID,
"states": [],
Expand Down
99 changes: 70 additions & 29 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
DEVICE_SWITCH2_ID,
DEVICE_SWITCH_ID,
DEVICE_TEMP_SENSOR_ID,
DEVICE_THERMOSTAT2_ID,
DEVICE_THERMOSTAT_ID,
DEVICE_UV_SENSOR_ID,
VeraControllerData,
Expand Down Expand Up @@ -109,6 +110,16 @@ def test__event_device_for_vera_lock_status() -> None:
mock_lock.update.assert_called_once_with(device_json)


def test_refresh_data(vera_controller_data: VeraControllerData) -> None:
"""Test function."""
controller = vera_controller_data.controller
data = controller.refresh_data()
assert len(data) == 20

services = controller.map_services()
assert services is None


def test_polling(vera_controller_data: VeraControllerData) -> None:
"""Test function."""
controller = vera_controller_data.controller
Expand Down Expand Up @@ -257,47 +268,77 @@ def test_lock(vera_controller_data: VeraControllerData) -> None:
assert device.clear_slot_pin(slot=1).status_code == 200


# pylint: disable=protected-access
def test_thermostat(vera_controller_data: VeraControllerData) -> None:
"""Test function."""
controller = vera_controller_data.controller
device = cast(VeraThermostat, controller.get_device_by_id(DEVICE_THERMOSTAT_ID))
controller.register(device, lambda device: None)
device1 = cast(VeraThermostat, controller.get_device_by_id(DEVICE_THERMOSTAT_ID))
device2 = cast(VeraThermostat, controller.get_device_by_id(DEVICE_THERMOSTAT2_ID))
controller.register(device1, lambda device: None)
controller.register(device2, lambda device: None)

all_devices = (device1, device2)

assert device1.get_current_goal_temperature(refresh=True) == 8.0
assert device2.get_current_goal_temperature(refresh=True) == 7.0

assert device.get_current_goal_temperature(refresh=True) == 8.0
assert device.get_current_temperature(refresh=True) == 9.0
assert device.get_hvac_mode(refresh=True) == "Off"
assert device.get_fan_mode(refresh=True) == "Off"
assert device.get_hvac_state(refresh=True) == "Off"
for device in all_devices:
assert device.get_current_temperature(refresh=True) == 9.0
assert device.get_hvac_mode(refresh=True) == "Off"
assert device.get_fan_mode(refresh=True) == "Off"
assert device.get_hvac_state(refresh=True) == "Off"

device.set_temperature(72)
assert device.get_current_goal_temperature() == 72
assert device1._has_double_setpoints() is False
assert device2._has_double_setpoints() is True

update_device(
controller_data=vera_controller_data,
device_id=DEVICE_THERMOSTAT_ID,
key="temperature",
value=65,
)
assert device.get_current_temperature() == 65

device.turn_auto_on()
assert device.get_hvac_mode() == "AutoChangeOver"
device.turn_heat_on()
assert device.get_hvac_mode() == "HeatOn"
device.turn_cool_on()
assert device.get_hvac_mode() == "CoolOn"

device.fan_on()
assert device.get_fan_mode() == "ContinuousOn"
device.fan_auto()
assert device.get_fan_mode() == "Auto"
device.fan_cycle()
assert device.get_fan_mode() == "PeriodicOn"
device.fan_off()
assert device.get_fan_mode() == "Off"

device.turn_off()
assert device.get_hvac_mode() == "Off"
assert device1.get_current_temperature() == 65

for device in all_devices:
device.set_temperature(72)
assert device.get_current_goal_temperature() == 72

device.turn_auto_on()
assert device.get_hvac_mode() == "AutoChangeOver"
device.turn_heat_on()
assert device.get_hvac_mode() == "HeatOn"
device.turn_cool_on()
assert device.get_hvac_mode() == "CoolOn"

device.fan_on()
assert device.get_fan_mode() == "ContinuousOn"
device.fan_auto()
assert device.get_fan_mode() == "Auto"
device.fan_cycle()
assert device.get_fan_mode() == "PeriodicOn"
device.fan_off()
assert device.get_fan_mode() == "Off"

device.turn_off()
assert device.get_hvac_mode() == "Off"

device2.turn_heat_on()
device2.set_temperature(75)
assert device2.get_current_goal_temperature() == 75
assert device2.get_value("heatsp") == "75"

device2.turn_cool_on()
device2.set_temperature(60)
assert device2.get_current_goal_temperature() == 60
assert device2.get_value("coolsp") == "60"

device2.turn_heat_on()
assert device2.get_current_goal_temperature() == 75
assert device2.get_value("heatsp") == "75"

device2.turn_cool_on()
assert device2.get_current_goal_temperature() == 60
assert device2.get_value("coolsp") == "60"


def test_curtain(vera_controller_data: VeraControllerData) -> None:
Expand Down

0 comments on commit 9a6a329

Please sign in to comment.