Skip to content
Open
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
122 changes: 122 additions & 0 deletions tests/test_xiaomi_airm_fhac01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Test Aqara air quality monitor (lumi.airm.fhac01) quirk."""

from zigpy.zcl.clusters.general import DeviceTemperature
from zigpy.zcl.clusters.measurement import CarbonDioxideConcentration

from zhaquirks.xiaomi.aqara.airm_fhac01 import (
CarbonDioxideConcentrationCluster,
CustomDeviceTemperature,
)


def test_co2_concentration_cluster_scaling():
"""Test CO2 concentration cluster scaling functionality."""
cluster = CarbonDioxideConcentrationCluster(None, None)

# Test normal CO2 value with 6 extra zeros
test_value = 400_000_000 # Should represent 400 ppm
expected_value = 400.0 # After scaling

cluster._update_attribute(
CarbonDioxideConcentration.AttributeDefs.measured_value.id, test_value
)

# Check that the value was correctly scaled
actual_value = cluster.get("measured_value")
assert actual_value == expected_value


def test_co2_concentration_cluster_edge_cases():
"""Test CO2 concentration cluster with edge cases."""
cluster = CarbonDioxideConcentrationCluster(None, None)

test_cases = [
(0, 0.0), # Zero value
(1_000_000, 1.0), # 1 ppm
(2000_000_000, 2000.0), # 2000 ppm (high but reasonable)
]

for test_value, expected_value in test_cases:
cluster._update_attribute(
CarbonDioxideConcentration.AttributeDefs.measured_value.id, test_value
)
actual_value = cluster.get("measured_value")
assert actual_value == expected_value


def test_co2_concentration_other_attributes_unchanged():
"""Test that other CO2 cluster attributes are not affected by scaling."""
cluster = CarbonDioxideConcentrationCluster(None, None)

# Test min_measured_value attribute (should not be scaled)
test_value = 1000
cluster._update_attribute(
CarbonDioxideConcentration.AttributeDefs.min_measured_value.id, test_value
)

actual_value = cluster.get("min_measured_value")
assert actual_value == test_value # No scaling


def test_device_temperature_cluster_scaling():
"""Test device temperature cluster scaling functionality."""
cluster = CustomDeviceTemperature(None, None)

# Test normal temperature value divided by 100
test_value = 25 # Should represent 25°C
expected_value = 2500 # After scaling (25°C * 100)

cluster._update_attribute(
DeviceTemperature.AttributeDefs.current_temperature.id, test_value
)

# Check that the value was correctly scaled
actual_value = cluster.get("current_temperature")
assert actual_value == expected_value


def test_device_temperature_cluster_edge_cases():
"""Test device temperature cluster with edge cases."""
cluster = CustomDeviceTemperature(None, None)

test_cases = [
(0, 0), # 0°C
(-10, -1000), # -10°C
(100, 10000), # 100°C
(1, 100), # 1°C
]

for test_value, expected_value in test_cases:
cluster._update_attribute(
DeviceTemperature.AttributeDefs.current_temperature.id, test_value
)
actual_value = cluster.get("current_temperature")
assert actual_value == expected_value


def test_device_temperature_other_attributes_unchanged():
"""Test that other device temperature cluster attributes are not affected by scaling."""
cluster = CustomDeviceTemperature(None, None)

# Test min_temp_experienced attribute (should not be scaled)
test_value = 20
cluster._update_attribute(
DeviceTemperature.AttributeDefs.min_temp_experienced.id, test_value
)

actual_value = cluster.get("min_temp_experienced")
assert actual_value == test_value # No scaling


def test_cluster_inheritance():
"""Test that clusters properly inherit from their base classes."""
co2_cluster = CarbonDioxideConcentrationCluster(None, None)
temp_cluster = CustomDeviceTemperature(None, None)

# Check that they are instances of the correct base classes
assert isinstance(co2_cluster, CarbonDioxideConcentration)
assert isinstance(temp_cluster, DeviceTemperature)

# Check that they have the expected cluster IDs
assert co2_cluster.cluster_id == CarbonDioxideConcentration.cluster_id
assert temp_cluster.cluster_id == DeviceTemperature.cluster_id
44 changes: 44 additions & 0 deletions zhaquirks/xiaomi/aqara/airm_fhac01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Quirk for LUMI lumi.airm.fhac01 air quality monitor."""

from zigpy.quirks import CustomCluster
from zigpy.quirks.v2 import QuirkBuilder
from zigpy.zcl.clusters.general import DeviceTemperature
from zigpy.zcl.clusters.measurement import (
CarbonDioxideConcentration,
RelativeHumidity,
TemperatureMeasurement,
)

from zhaquirks.xiaomi import LUMI


class CarbonDioxideConcentrationCluster(CustomCluster, CarbonDioxideConcentration):
"""Carbon Dioxide concentration cluster that fixes the scaling issue."""

def _update_attribute(self, attrid, value):
"""Fix CO2 concentration scaling by dividing by 1e6."""
if attrid == CarbonDioxideConcentration.AttributeDefs.measured_value.id:
# The device reports values with 6 extra zeros, so divide by 1e6
value = value / 1_000_000
super()._update_attribute(attrid, value)


class CustomDeviceTemperature(CustomCluster, DeviceTemperature):
"""Temperature measurement cluster that fixes the scaling issue."""

def _update_attribute(self, attrid, value):
"""Fix temperature scaling by multiplying by 100."""
if attrid == DeviceTemperature.AttributeDefs.current_temperature.id:
# The device reports temperature divided by 100, so multiply by 100
value = value * 100
super()._update_attribute(attrid, value)


(
QuirkBuilder(LUMI, "lumi.airm.fhac01")
.adds(TemperatureMeasurement)
.adds(RelativeHumidity)
.replaces(CarbonDioxideConcentrationCluster)
.replaces(CustomDeviceTemperature)
.add_to_registry()
)
Loading