Skip to content

Commit

Permalink
Add support for the cgllc.airmonitor.b1 (#562)
Browse files Browse the repository at this point in the history
* Update airqualitymonitor.py

Added support for cgllc.airmonitor.b1

* changed default model

* Fixed formatting and constant

* Improve converage

* Changed typo info.model to self.model

* Added co2e value to output

* Autodetection for airmonitor B1

* Added device information

* Changed get properties

* changed model to _model

* Property getters

* return "on" or None

* states return

* Removed firmware/hardware/mac_address attributes

* Remove unwanted properties

* Removed whitespace and == None
  • Loading branch information
fwestenberg authored and rytilahti committed Oct 20, 2019
1 parent c7d2779 commit cd99dd3
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 40 deletions.
86 changes: 46 additions & 40 deletions miio/airqualitymonitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,19 @@
"sensor_state",
]

AVAILABLE_PROPERTIES_B1 = [
"co2e",
"humidity",
"pm25",
"temperature",
"tvoc"
]

AVAILABLE_PROPERTIES_S1 = ["battery", "co2", "humidity", "pm25", "temperature", "tvoc"]

AVAILABLE_PROPERTIES = {
MODEL_AIRQUALITYMONITOR_V1: AVAILABLE_PROPERTIES_COMMON,
MODEL_AIRQUALITYMONITOR_B1: AVAILABLE_PROPERTIES_COMMON,
MODEL_AIRQUALITYMONITOR_B1: AVAILABLE_PROPERTIES_B1,
MODEL_AIRQUALITYMONITOR_S1: AVAILABLE_PROPERTIES_S1,
}

Expand All @@ -49,7 +57,8 @@ def __init__(self, data):
Response of a Xiaomi Air Quality Monitor (cgllc.airmonitor.b1):
unknown.
{'co2e': 1466, 'humidity': 59.79999923706055, 'pm25': 2, 'temperature': 19.799999237060547,
'temperature_unit': 'c', 'tvoc': 1.3948699235916138, 'tvoc_unit': 'mg_m3'}
Response of a Xiaomi Air Quality Monitor (cgllc.airmonitor.s1):
Expand All @@ -61,9 +70,7 @@ def __init__(self, data):
@property
def power(self) -> Optional[str]:
"""Current power state."""
if "power" in self.data and self.data["power"] is not None:
return self.data["power"]
return None
return self.data.get("power", None)

@property
def is_on(self) -> bool:
Expand All @@ -80,14 +87,12 @@ def usb_power(self) -> Optional[bool]:
@property
def aqi(self) -> Optional[int]:
"""Air quality index value. (0...600)."""
if "aqi" in self.data and self.data["aqi"] is not None:
return self.data["aqi"]
return None
return self.data.get("aqi", None)

@property
def battery(self) -> int:
def battery(self) -> Optional[int]:
"""Current battery level (0...100)."""
return self.data["battery"]
return self.data.get("battery", None)

@property
def display_clock(self) -> Optional[bool]:
Expand All @@ -106,58 +111,47 @@ def night_mode(self) -> Optional[bool]:
@property
def night_time_begin(self) -> Optional[str]:
"""Return the begin of the night time."""
if "night_beg_time" in self.data and self.data["night_beg_time"] is not None:
return self.data["night_beg_time"]
return None
return self.data.get("night_beg_time", None)

@property
def night_time_end(self) -> Optional[str]:
"""Return the end of the night time."""
if "night_end_time" in self.data and self.data["night_end_time"] is not None:
return self.data["night_end_time"]
return None
return self.data.get("night_end_time", None)

@property
def sensor_state(self) -> Optional[str]:
"""Sensor state."""
if "sensor_state" in self.data and self.data["sensor_state"] is not None:
return self.data["sensor_state"]
return None
return self.data.get("sensor_state", None)

@property
def co2(self) -> Optional[int]:
"""Return co2 value (400...9999ppm)."""
if "co2" in self.data and self.data["co2"] is not None:
return self.data["co2"]
return None
return self.data.get("co2", None)

@property
def co2e(self) -> Optional[int]:
"""Return co2e value (400...9999ppm)."""
return self.data.get("co2e", None)

@property
def humidity(self) -> Optional[float]:
"""Return humidity value (0...100%)."""
if "humidity" in self.data and self.data["humidity"] is not None:
return self.data["humidity"]
return None
return self.data.get("humidity", None)

@property
def pm25(self) -> Optional[float]:
"""Return pm2.5 value (0...999μg/m³)."""
if "pm25" in self.data and self.data["pm25"] is not None:
return self.data["pm25"]
return None
return self.data.get("pm25", None)

@property
def temperature(self) -> Optional[float]:
"""Return temperature value (-10...50°C)."""
if "temperature" in self.data and self.data["temperature"] is not None:
return self.data["temperature"]
return None
return self.data.get("temperature", None)

@property
def tvoc(self) -> Optional[int]:
"""Return tvoc value."""
if "tvoc" in self.data and self.data["tvoc"] is not None:
return self.data["tvoc"]
return None
return self.data.get("tvoc", None)

def __repr__(self) -> str:
s = (
Expand All @@ -168,6 +162,7 @@ def __repr__(self) -> str:
"temperature=%s, "
"humidity=%s, "
"co2=%s, "
"co2e=%s, "
"pm2.5=%s, "
"tvoc=%s, "
"display_clock=%s>"
Expand All @@ -179,6 +174,7 @@ def __repr__(self) -> str:
self.temperature,
self.humidity,
self.co2,
self.co2e,
self.pm25,
self.tvoc,
self.display_clock,
Expand Down Expand Up @@ -206,11 +202,12 @@ def __init__(

if model in AVAILABLE_PROPERTIES:
self.model = model
else:
elif model is not None:
self.model = MODEL_AIRQUALITYMONITOR_V1
_LOGGER.error(
"Device model %s unsupported. Falling back to %s.", model, self.model
)
_LOGGER.error("Device model %s unsupported. Falling back to %s.", model, self.model)
else:
"""Force autodetection"""
self.model = None

@command(
default_output=format_output(
Expand All @@ -222,6 +219,7 @@ def __init__(
"Temperature: {result.temperature}\n"
"Humidity: {result.humidity}\n"
"CO2: {result.co2}\n"
"CO2e: {result.co2e}\n"
"PM2.5: {result.pm25}\n"
"TVOC: {result.tvoc}\n"
"Display clock: {result.display_clock}\n",
Expand All @@ -230,9 +228,17 @@ def __init__(
def status(self) -> AirQualityMonitorStatus:
"""Return device status."""

if self.model is None:
"""Autodetection"""
info = self.info()
self.model = info.model

properties = AVAILABLE_PROPERTIES[self.model]

values = self.send("get_prop", properties)
if self.model == MODEL_AIRQUALITYMONITOR_B1:
values = self.send("get_air_data")
else:
values = self.send("get_prop", properties)

properties_count = len(properties)
values_count = len(values)
Expand All @@ -244,7 +250,7 @@ def status(self) -> AirQualityMonitorStatus:
values_count,
)

if self.model == MODEL_AIRQUALITYMONITOR_S1:
if self.model == MODEL_AIRQUALITYMONITOR_S1 or self.model == MODEL_AIRQUALITYMONITOR_B1:
return AirQualityMonitorStatus(defaultdict(lambda: None, values))
else:
return AirQualityMonitorStatus(
Expand Down
52 changes: 52 additions & 0 deletions miio/tests/test_airqualitymonitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
AirQualityMonitorStatus,
MODEL_AIRQUALITYMONITOR_V1,
MODEL_AIRQUALITYMONITOR_S1,
MODEL_AIRQUALITYMONITOR_B1,
)
from .dummies import DummyDevice

Expand Down Expand Up @@ -128,3 +129,54 @@ def test_status(self):
assert self.state().usb_power is None
assert self.state().display_clock is None
assert self.state().night_mode is None


class DummyAirQualityMonitorB1(DummyDevice, AirQualityMonitor):
def __init__(self, *args, **kwargs):
self.model = MODEL_AIRQUALITYMONITOR_B1
self.state = {
"co2e": 1466,
"humidity": 59.79999923706055,
"pm25": 2,
"temperature": 19.799999237060547,
"temperature_unit": "c",
"tvoc": 1.3948699235916138,
"tvoc_unit": "mg_m3"}
self.return_values = {"get_air_data": self._get_state}
super().__init__(args, kwargs)

def _get_state(self, props):
"""Return wanted properties"""
return self.state


@pytest.fixture(scope="class")
def airqualitymonitorb1(request):
request.cls.device = DummyAirQualityMonitorB1()
# TODO add ability to test on a real device


@pytest.mark.usefixtures("airqualitymonitorb1")
class TestAirQualityMonitorB1(TestCase):
def state(self):
return self.device.status()

def test_status(self):
self.device._reset_state()

assert repr(self.state()) == repr(
AirQualityMonitorStatus(self.device.start_state)
)

assert self.state().power is None
assert self.state().usb_power is None
assert self.state().battery is None
assert self.state().aqi is None
assert self.state().temperature == self.device.start_state["temperature"]
assert self.state().humidity == self.device.start_state["humidity"]
assert self.state().co2 is None
assert self.state().co2e == self.device.start_state["co2e"]
assert self.state().pm25 == self.device.start_state["pm25"]
assert self.state().tvoc == self.device.start_state["tvoc"]
assert self.state().display_clock is None
assert self.state().night_mode is None

0 comments on commit cd99dd3

Please sign in to comment.