diff --git a/nexia/zone.py b/nexia/zone.py index 874cd99..3135d73 100644 --- a/nexia/zone.py +++ b/nexia/zone.py @@ -558,12 +558,18 @@ async def set_heat_cool_temp( self.round_temp(heat_temperature) + deadband, ) else: - cool_temperature = self.round_temp(set_temperature) + math.ceil( - deadband / 2, - ) - heat_temperature = self.round_temp(set_temperature) - math.ceil( - deadband / 2, - ) + # Split the deadband symmetrically around the target. The + # offset must land on the unit's setpoint grid (0.5° on + # Celsius, 1° on Fahrenheit) and be wide enough that the + # resulting band still satisfies the deadband, so round the + # half-deadband up to the next grid step. Using math.ceil on + # the raw half over-widened the band on Celsius systems + # (deadband 1.0 yielded ±1.0 instead of the minimal ±0.5). + step = 0.5 if self.thermostat.get_unit() == UNIT_CELSIUS else 1.0 + half_deadband = math.ceil(deadband / 2 / step) * step + target = self.round_temp(set_temperature) + cool_temperature = target + half_deadband + heat_temperature = target - half_deadband await self._set_setpoints(cool_temperature, heat_temperature) diff --git a/tests/test_home.py b/tests/test_home.py index 029a3d4..5086646 100644 --- a/tests/test_home.py +++ b/tests/test_home.py @@ -2490,3 +2490,50 @@ async def test_check_heat_cool_setpoints_accepts_in_range( zone.check_heat_cool_setpoints(heat_temperature=69, cool_temperature=78) # Boundary values (exactly at the limits) are allowed. zone.check_heat_cool_setpoints(heat_temperature=55, cool_temperature=99) + + +async def test_set_heat_cool_temp_auto_mode_fahrenheit_band( + aiohttp_session: aiohttp.ClientSession, +) -> None: + """Auto-mode set_temperature splits the deadband on the Fahrenheit grid. + + The zone is in AUTO mode, so a single set_temperature is expanded into a + heat/cool band centered on the target. On Fahrenheit (deadband 3) the + half-deadband rounds up to 2, giving a ±2° band. + """ + nexia = NexiaHome(aiohttp_session) + devices_json = json.loads(await load_fixture("single_zone_xl1050.json")) + nexia.update_from_json(devices_json) + zone = nexia.get_thermostat_by_id(345678).get_zone_by_id(234567) + assert zone.get_current_mode() == "AUTO" + + with patch.object(zone, "_set_setpoints", new=AsyncMock()) as set_setpoints: + await zone.set_heat_cool_temp(set_temperature=70) + + cool, heat = set_setpoints.call_args.args + assert (cool, heat) == (72, 68) + + +async def test_set_heat_cool_temp_auto_mode_celsius_half_degree_grid( + aiohttp_session: aiohttp.ClientSession, +) -> None: + """Auto-mode set_temperature stays on the 0.5° grid for Celsius systems. + + With a 1.0° Celsius deadband the half-deadband is exactly 0.5°, so the band + is the minimal ±0.5° around the target rather than the over-wide ±1.0° that + integer ceil produced. + """ + nexia = NexiaHome(aiohttp_session) + devices_json = json.loads(await load_fixture("single_zone_xl1050.json")) + nexia.update_from_json(devices_json) + zone = nexia.get_thermostat_by_id(345678).get_zone_by_id(234567) + + with ( + patch.object(zone.thermostat, "get_unit", return_value="C"), + patch.object(zone.thermostat, "get_deadband", return_value=1.0), + patch.object(zone, "_set_setpoints", new=AsyncMock()) as set_setpoints, + ): + await zone.set_heat_cool_temp(set_temperature=21.5) + + cool, heat = set_setpoints.call_args.args + assert (cool, heat) == (22.0, 21.0)