Skip to content

Commit

Permalink
add child_lock, online, brightness, and light.
Browse files Browse the repository at this point in the history
  • Loading branch information
kai.tseng committed May 8, 2023
1 parent b43a07b commit 5e7b5dc
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 36 deletions.
4 changes: 2 additions & 2 deletions custom_components/blueair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["sensor", "fan"]
PLATFORMS = ["binary_sensor", "fan", "sensor", "light"]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand All @@ -36,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

devices = await hass.async_add_executor_job(lambda: client.get_devices())
hass.data[DOMAIN][entry.entry_id]["devices"] = [
BlueairDataUpdateCoordinator(hass, client, device["uuid"], device["name"])
BlueairDataUpdateCoordinator(hass, client, device["uuid"], device["name"], device["mac"])
for device in devices
]
_LOGGER.debug(f"BlueAir Devices {devices}")
Expand Down
82 changes: 82 additions & 0 deletions custom_components/blueair/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

from .const import DOMAIN
from .device import BlueairDataUpdateCoordinator
from .entity import BlueairEntity

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity
)

async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Blueair sensors from config entry."""
devices: list[BlueairDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]["devices"]
entities = []
for device in devices:
# Don't add sensors to classic models
if (
device.model.startswith("classic") and not device.model.endswith("i")
) or device.model == "foobot":
pass
else:
entities.extend(
[
BlueairFilterExpiredSensor(f"{device.device_name}_filter_expired", device),
BlueairChildLockSensor(f"{device.device_name}_child_lock", device),
BlueairOnlineSensor(f"{device.device_name}_online", device),
]
)
async_add_entities(entities)


class BlueairFilterExpiredSensor(BlueairEntity, BinarySensorEntity):
"""Monitors the status of the Filter"""

def __init__(self, name, device):
"""Initialize the filter_status sensor."""
super().__init__("filter_expired", name, device)
self._state: bool = None
self._attr_icon = "mdi:air-filter"
self._attr_device_class = BinarySensorDeviceClass.PROBLEM

@property
def is_on(self) -> bool | None:
"""Return the current filter_status."""
return self._device.filter_expired


class BlueairChildLockSensor(BlueairEntity, BinarySensorEntity):

def __init__(self, name, device):
super().__init__("child_Lock", name, device)
self._state: bool = None
self._attr_icon = "mdi:account-child-outline"

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self._device.child_lock


class BlueairOnlineSensor(BlueairEntity, BinarySensorEntity):
def __init__(self, name, device):
"""Initialize the online sensor."""
super().__init__("online", name, device)
self._state: bool = None
self._attr_icon = "mdi:wifi-check"
self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY,

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self._device.wifi_working

@property
def icon(self) -> str | None:
if self.is_on:
return self._attr_icon
else:
return "mdi:wifi-strength-outline"
20 changes: 20 additions & 0 deletions custom_components/blueair/blueair/blueair.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,26 @@ def set_fan_speed(self, device_uuid, new_speed):
},
)

def set_brightness(self, device_uuid, brightness):
"""
Set the brightness per @spikeyGG comment at https://community.home-assistant.io/t/blueair-purifier-addon/154456/14
"""
res = requests.post(
f"https://{self.home_host}/v2/device/{device_uuid}/attribute/brightness/",
headers={
"Content-Type": "application/json",
"X-API-KEY-TOKEN": API_KEY,
"X-AUTH-TOKEN": self.auth_token,
},
json={
"currentValue": brightness,
"scope": "device",
"defaultValue": brightness,
"name": "brightness",
"uuid": device_uuid,
},
)

def set_fan_mode(self, device_uuid, new_mode):
"""
Set the fan mode to automatic
Expand Down
57 changes: 43 additions & 14 deletions custom_components/blueair/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing import Any
from async_timeout import timeout


from . import blueair

API = blueair.BlueAir
Expand All @@ -20,13 +19,15 @@ class BlueairDataUpdateCoordinator(DataUpdateCoordinator):
"""Blueair device object."""

def __init__(
self, hass: HomeAssistant, api_client: API, uuid: str, device_name: str
self, hass: HomeAssistant, api_client: API, uuid: str, device_name: str,
mac: str = None,
) -> None:
"""Initialize the device."""
self.hass: HomeAssistant = hass
self.api_client: API = api_client
self._uuid: str = uuid
self._name: str = device_name
self.mac = mac
self._manufacturer: str = "BlueAir"
self._device_information: dict[str, Any] = {}
self._datapoint: dict[str, Any] = {}
Expand Down Expand Up @@ -68,63 +69,63 @@ def model(self) -> str:
return self._device_information.get("compatibility", self.id)

@property
def temperature(self) -> float:
def temperature(self) -> float | None:
"""Return the current temperature in degrees C."""
if "temperature" not in self._datapoint:
return None
return self._datapoint["temperature"]

@property
def humidity(self) -> float:
def humidity(self) -> float | None:
"""Return the current relative humidity percentage."""
if "humidity" not in self._datapoint:
return None
return self._datapoint["humidity"]

@property
def co2(self) -> float:
def co2(self) -> float | None:
"""Return the current co2."""
if "co2" not in self._datapoint:
return None
return self._datapoint["co2"]

@property
def voc(self) -> float:
def voc(self) -> float | None:
"""Return the current voc."""
if "voc" not in self._datapoint:
return None
return self._datapoint["voc"]

@property
def pm1(self) -> float:
def pm1(self) -> float | None:
"""Return the current pm1."""
if "pm1" not in self._datapoint:
return None
return self._datapoint["pm1"]

@property
def pm10(self) -> float:
def pm10(self) -> float | None:
"""Return the current pm10."""
if "pm10" not in self._datapoint:
return None
return self._datapoint["pm10"]

@property
def pm25(self) -> float:
def pm25(self) -> float | None:
"""Return the current pm25."""
if "pm25" not in self._datapoint:
return None
return self._datapoint["pm25"]

@property
def all_pollution(self) -> float:
def all_pollution(self) -> float | None:
"""Return all pollution"""
if "all_pollution" not in self._datapoint:
return None
return self._datapoint["all_pollution"]

@property
def fan_speed(self) -> int:
def fan_speed(self) -> int | None:
"""Return the current fan speed."""
if "fan_speed" not in self._attribute:
return None
Expand All @@ -140,7 +141,7 @@ def is_on(self) -> bool():
return True

@property
def fan_mode(self) -> str:
def fan_mode(self) -> str | None:
"""Return the current fan mode"""
if self._attribute["mode"] == "manual":
return None
Expand All @@ -155,11 +156,39 @@ def fan_mode_supported(self) -> bool():
return False

@property
def filter_status(self) -> str:
def filter_expired(self) -> bool | None:
"""Return the current filter status."""
if "filter_status" not in self._attribute:
return None
return self._attribute["filter_status"]
return self._attribute["filter_status"] != "OK"

@property
def child_lock(self) -> bool | None:
"""Return the current filter status."""
if "child_lock" not in self._attribute:
return None
return bool(self._attribute["child_lock"])

@property
def wifi_working(self) -> bool | None:
"""Return the current filter status."""
if "wifi_status" not in self._attribute:
return None
return self._attribute["wifi_status"] == "1"

@property
def brightness(self) -> int | None:
"""Return the current filter status."""
if "brightness" not in self._attribute:
return None
return int(self._attribute["brightness"])

async def set_brightness(self, brightness) -> None:
await self.hass.async_add_executor_job(
lambda: self.api_client.set_brightness(self.id, brightness)
)
self._attribute["brightness"] = brightness
await self.async_refresh()

async def set_fan_speed(self, new_speed) -> None:
await self.hass.async_add_executor_job(
Expand Down
3 changes: 3 additions & 0 deletions custom_components/blueair/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ def __init__(
@property
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
connections = {(CONNECTION_NETWORK_MAC, self._device.mac)}
return {
"connections": connections,
"identifiers": {(DOMAIN, self._device.id)},
"manufacturer": self._device.manufacturer,
"model": self._device.model,
Expand All @@ -41,6 +43,7 @@ def device_info(self) -> DeviceInfo:
async def async_update(self):
"""Update Blueair entity."""
await self._device.async_request_refresh()
self._attr_available = self._device.wifi_working

async def async_added_to_hass(self):
"""When entity is added to hass."""
Expand Down
60 changes: 60 additions & 0 deletions custom_components/blueair/light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from .const import DOMAIN
from .device import BlueairDataUpdateCoordinator
from .entity import BlueairEntity
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ColorMode,
LightEntity,
)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Blueair sensors from config entry."""
devices: list[BlueairDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]["devices"]
entities = []
for device in devices:
# Don't add sensors to classic models
if (
device.model.startswith("classic") and not device.model.endswith("i")
) or device.model == "foobot":
pass
else:
entities.extend(
[
BlueairLightEntity(f"{device.device_name}_light", device),
]
)
async_add_entities(entities)


class BlueairLightEntity(BlueairEntity, LightEntity):
_attr_color_mode = ColorMode.BRIGHTNESS
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}

def __init__(self, name, device):
super().__init__("LED Light", name, device)

@property
def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255."""
return round(self._device.brightness / 100 * 255.0, 0)

@property
def is_on(self) -> bool:
"""Return True if the entity is on."""
return self._device.brightness != 0

async def async_turn_on(self, **kwargs):
if ATTR_BRIGHTNESS in kwargs:
# Convert Home Assistant brightness (0-255) to Abode brightness (0-99)
# If 100 is sent to Abode, response is 99 causing an error
await self._device.set_brightness(
round(kwargs[ATTR_BRIGHTNESS] * 100 / 255.0)
)
else:
await self._device.set_brightness(100)

async def async_turn_off(self, **kwargs):
await self._device.set_brightness(0)
Loading

0 comments on commit 5e7b5dc

Please sign in to comment.