Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions miio/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
class ValidSettingRange:
"""Describes a valid input range for a property."""

min_value: int
max_value: int
step: int = 1
min_value: float
max_value: float
step: float = 1


class AccessFlags(Flag):
Expand Down Expand Up @@ -177,11 +177,11 @@ class RangeDescriptor(PropertyDescriptor):
"""

#: Minimum value for the property.
min_value: int
min_value: float
#: Maximum value for the property.
max_value: int
max_value: float
#: Step size for the property.
step: int
step: float
#: Name of the attribute in the device class that returns the range.
#: If set, this will override the individual min/max/step values.
range_attribute: str | None = attr.ib(default=None)
Expand Down
9 changes: 8 additions & 1 deletion miio/miot_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class MiotProperty(MiotBaseModel):
access: list[MiotAccess] = Field(default=[MiotAccess.Read])
unit: str | None = None

range: list[int] | None = Field(default=None, alias="value-range")
range: list[float] | None = Field(default=None, alias="value-range")
choices: list[MiotEnumValue] | None = Field(default=None, alias="value-list")
gatt_access: list[Any] | None = Field(default=None, alias="gatt-access")

Expand All @@ -254,6 +254,13 @@ class MiotProperty(MiotBaseModel):
# there must be a better way to do this..
value: Any | None = None

@model_validator(mode="after")
def coerce_range_to_format_type(self) -> Self:
"""Coerce range values to the property's numeric type."""
if self.range is not None and self.format in (int, float):
self.range = [self.format(v) for v in self.range]
return self

@property
def pretty_value(self):
value = self.value
Expand Down
25 changes: 25 additions & 0 deletions miio/tests/test_miot_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,31 @@ def test_get_descriptor_ranged_property(read_only, expected):
assert desc.constraint == PropertyConstraint.Range


@pytest.mark.parametrize(
("format", "range_values", "expected_type"),
[
("float", [-30, 100, 1e-05], float),
("uint8", [0, 100, 1], int),
],
)
def test_get_descriptor_ranged_property_type_coercion(
format, range_values, expected_type
):
"""Test that range values are coerced to the property's format type."""
ranged_prop = load_fixture("ranged_property.json")
ranged_prop["format"] = format
ranged_prop["value-range"] = range_values

prop = MiotProperty.model_validate(ranged_prop)
desc = prop.get_descriptor()

assert isinstance(desc, RangeDescriptor)
assert all(type(v) is expected_type for v in prop.range)
assert desc.min_value == range_values[0]
assert desc.max_value == range_values[1]
assert desc.step == range_values[2]


def test_get_descriptor_ranged_property_none_format():
"""Test that a ranged property with format=none raises ValueError."""
ranged_prop = load_fixture("ranged_property.json")
Expand Down
Loading