Browse Source

fix: drop out-of-range CO2 readings from Meter Pro CO2 (#491)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bluetooh Devices Bot 16 hours ago
parent
commit
9aa5f50e86
2 changed files with 37 additions and 1 deletions
  1. 7 1
      switchbot/adv_parsers/meter.py
  2. 30 0
      tests/test_adv_parser.py

+ 7 - 1
switchbot/adv_parsers/meter.py

@@ -9,6 +9,10 @@ from ._sensor_th import decode_temp_humidity
 
 
 CO2_UNPACK = struct.Struct(">H").unpack_from
 CO2_UNPACK = struct.Struct(">H").unpack_from
 
 
+# Meter Pro CO2 sensor spec range is 400-9999 ppm. Higher values are
+# transient parsing artifacts and surface as huge spikes downstream.
+CO2_MAX_PPM = 9999
+
 
 
 def process_wosensorth(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
 def process_wosensorth(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
     """Process woSensorTH/Temp sensor services data."""
     """Process woSensorTH/Temp sensor services data."""
@@ -34,5 +38,7 @@ def process_wosensorth_c(data: bytes | None, mfr_data: bytes | None) -> dict[str
     _wosensorth_data = process_wosensorth(data, mfr_data)
     _wosensorth_data = process_wosensorth(data, mfr_data)
     if _wosensorth_data and mfr_data and len(mfr_data) >= 15:
     if _wosensorth_data and mfr_data and len(mfr_data) >= 15:
         co2_data = mfr_data[13:15]
         co2_data = mfr_data[13:15]
-        _wosensorth_data["co2"] = CO2_UNPACK(co2_data)[0]
+        co2 = CO2_UNPACK(co2_data)[0]
+        if co2 <= CO2_MAX_PPM:
+            _wosensorth_data["co2"] = co2
     return _wosensorth_data
     return _wosensorth_data

+ 30 - 0
tests/test_adv_parser.py

@@ -1417,6 +1417,36 @@ def test_meter_pro_c_passive() -> None:
     )
     )
 
 
 
 
+def test_meter_pro_c_co2_out_of_range_dropped() -> None:
+    """CO2 readings above sensor spec range (9999 ppm) are dropped as spurious."""
+    ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={
+            2409: b"\xb0\xe9\xfeT2\x15\xb7\xe4\x07\x9b\xa4\x007\x9c\x40\x00"
+        },
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"5\x00d"},
+        rssi=-67,
+    )
+    result = parse_advertisement_data(ble_device, adv_data)
+    assert "co2" not in result.data["data"]
+    assert result.data["data"]["temperature"] == 27.7
+    assert result.data["data"]["humidity"] == 36
+
+
+def test_meter_pro_c_co2_boundary_accepted() -> None:
+    """CO2 at the upper bound (9999 ppm) is accepted."""
+    ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={
+            2409: b"\xb0\xe9\xfeT2\x15\xb7\xe4\x07\x9b\xa4\x007\x27\x0f\x00"
+        },
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"5\x00d"},
+        rssi=-67,
+    )
+    result = parse_advertisement_data(ble_device, adv_data)
+    assert result.data["data"]["co2"] == 9999
+
+
 def test_parse_advertisement_data_keypad():
 def test_parse_advertisement_data_keypad():
     """Test parse_advertisement_data for the keypad."""
     """Test parse_advertisement_data for the keypad."""
     ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
     ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")