Fix Prana fan percentage rounding so displayed values map to the same step (#180718)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Alex
2026-08-31 08:51:09 +00:00
committed by Franck Nijhof
co-authored by Claude Fable 5
parent 92663386bb
commit 3e274585db
2 changed files with 61 additions and 8 deletions
+8 -6
View File
@@ -3,7 +3,6 @@
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
import math
from typing import Any, override
from prana_local_api_client.models.prana_state import FanState
@@ -137,11 +136,14 @@ class PranaFan(PranaBaseEntity, FanEntity):
await self.async_turn_off()
return
await self.coordinator.api_client.set_speed(
math.ceil(
percentage_to_ranged_value(
self.entity_description.speed_range(self.coordinator),
percentage,
)
max(
1,
round(
percentage_to_ranged_value(
self.entity_description.speed_range(self.coordinator),
percentage,
)
),
)
* PRANA_SPEED_MULTIPLIER,
self._api_target_key,
+53 -2
View File
@@ -1,6 +1,5 @@
"""Integration-style tests for Prana fans."""
import math
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
@@ -153,7 +152,7 @@ async def test_fans_set_percentage(
blocking=True,
)
expected_speed = (
math.ceil(percentage_to_ranged_value((1, fan_mock_state.max_speed), 50))
round(percentage_to_ranged_value((1, fan_mock_state.max_speed), 50))
* PRANA_SPEED_MULTIPLIER
)
mock_prana_api.set_speed.assert_called_once_with(
@@ -171,6 +170,58 @@ async def test_fans_set_percentage(
mock_prana_api.set_speed_is_on.assert_called_with(False, expected_api_key)
@pytest.mark.parametrize(
("percentage", "expected_step"),
[
(1, 1), # any non-zero percentage turns the fan on at least at step 1
(16, 1),
(17, 1), # UI-displayed value for step 1 of 6 (16.67% rounded up)
(33, 2),
(50, 3),
(66, 4),
(67, 4), # UI-displayed value for step 4 of 6 (66.67% rounded up)
(83, 5),
(100, 6),
],
)
async def test_fans_percentage_maps_to_nearest_step(
hass: HomeAssistant,
mock_prana_api: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
percentage: int,
expected_step: int,
) -> None:
"""Test that displayed percentages map back to the same speed step.
A 6-step fan reports step 4 as 66.67%, which the UI displays as 67%.
Sending that displayed value back must select step 4 again, not step 5.
"""
mock_prana_api.get_state.return_value.supply.max_speed = 6
target, fan_mock_state = await _async_setup_fan_entity(
hass,
mock_prana_api,
mock_config_entry,
entity_registry,
"supply",
False,
)
fan_mock_state.is_on = True
await hass.async_block_till_done()
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_SET_PERCENTAGE,
{ATTR_ENTITY_ID: target, ATTR_PERCENTAGE: percentage},
blocking=True,
)
mock_prana_api.set_speed.assert_called_once_with(
expected_step * PRANA_SPEED_MULTIPLIER,
"supply",
)
@pytest.mark.parametrize(
("type_key", "is_bound_mode", "expected_api_key"),
FAN_TEST_CASES,