Skip to content

Commit

Permalink
Merge pull request #38 from RobertD502/fix_qual_endpoint
Browse files Browse the repository at this point in the history
Use new API endpoint for quality status, add Light select entity and Rapid preset mode for 250S purifiers.
  • Loading branch information
RobertD502 authored Apr 28, 2024
2 parents 12faddc + 8f8a864 commit a4877e3
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ Each purifier has the following entities:
| --- | --- | --- |
| `Purifier` | `Fan` | Ability of controlling power, speed, and preset mode (Auto Mode, Night Mode) |
| `Current timer` | `Select` | Ability to set timer to OFF, 1 hour, 2 hours, 4 hours, or 8 hours. `Setting a timer can only be done when a purifier is powered ON` |
| `Light` | `Switch` | Ability to turn light on and off. `Controlling the light can only be done when a purifier is powered ON` |
| `Light` | `Switch` | Only for non-250S purifiers. Ability to turn light on and off. `Controlling the light can only be done when a purifier is powered ON` |
| `Light` | `Select` | Only for 250S purifiers. Ability to set the light to Off, On, and AQI Off. `Controlling the light can only be done when a purifier is powered ON` |
| `AQI` | `Sensor` | Air Quality Index |
| `MAX2 Filter` | `Sensor` | Percentage of MAX2 filter life remaining |
| `Pre Filter` | `Sensor` | Percentage of Pre filter remaining |
Expand Down
10 changes: 10 additions & 0 deletions custom_components/coway/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging

from aiohttp.client_exceptions import ClientConnectionError
from cowayaio.constants import LightMode
from cowayaio.exceptions import AuthError, CowayError

from homeassistant.const import Platform
Expand Down Expand Up @@ -79,15 +80,24 @@
PRESET_MODE_AUTO = "Auto"
PRESET_MODE_NIGHT = "Night"
PRESET_MODE_ECO = "Eco"
PRESET_MODE_RAPID = "Rapid"

ORDERED_NAMED_FAN_SPEEDS = [IOCARE_FAN_LOW, IOCARE_FAN_MEDIUM, IOCARE_FAN_HIGH]
PRESET_MODES = [PRESET_MODE_AUTO, PRESET_MODE_NIGHT]
# Model AP-1512HHS uses Eco mode instead of Night mode
PRESET_MODES_AP = [PRESET_MODE_AUTO, PRESET_MODE_ECO]
PRESET_MODES_250 = [PRESET_MODE_AUTO, PRESET_MODE_NIGHT, PRESET_MODE_RAPID]

IOCARE_FAN_SPEED_TO_HASS = {
IOCARE_FAN_OFF: 0,
IOCARE_FAN_LOW: ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, IOCARE_FAN_LOW),
IOCARE_FAN_MEDIUM: ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, IOCARE_FAN_MEDIUM),
IOCARE_FAN_HIGH: ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, IOCARE_FAN_HIGH)
}

IOCARE_LIGHT_MODES = ["On", "Off", "AQI Off"]
IOCARE_LIGHT_MODES_TO_HASS = {
LightMode.ON: "On",
LightMode.AQI_OFF: "AQI Off",
LightMode.OFF: "Off"
}
3 changes: 3 additions & 0 deletions custom_components/coway/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from datetime import timedelta
import json

from cowayaio import CowayClient
from cowayaio.exceptions import AuthError, CowayError, PasswordExpired
Expand Down Expand Up @@ -51,6 +52,8 @@ async def _async_update_data(self) -> PurifierData:
except CowayError as error:
raise UpdateFailed(error) from error

nl = '\n'
LOGGER.debug(f'Found the following Coway devices: {nl}{json.dumps(data, default=vars, indent=4)}')
if not data.purifiers:
raise UpdateFailed("No Purifiers found")

Expand Down
25 changes: 25 additions & 0 deletions custom_components/coway/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
LOGGER,
ORDERED_NAMED_FAN_SPEEDS,
PRESET_MODES,
PRESET_MODES_250,
PRESET_MODES_AP,
PRESET_MODE_AUTO,
PRESET_MODE_ECO,
PRESET_MODE_NIGHT,
PRESET_MODE_RAPID,
)
from .coordinator import CowayDataUpdateCoordinator

Expand Down Expand Up @@ -103,6 +105,8 @@ def preset_modes(self) -> list:

if self.purifier_data.device_attr['model'] == "AIRMEGA AP-1512HHS":
return PRESET_MODES_AP
elif self.purifier_data.device_attr['product_name'] == 'COLUMBIA':
return PRESET_MODES_250
else:
return PRESET_MODES

Expand All @@ -115,6 +119,13 @@ def preset_mode(self) -> str:
return PRESET_MODE_AUTO
if self.purifier_data.eco_mode:
return PRESET_MODE_ECO
elif self.purifier_data.device_attr['product_name'] == 'COLUMBIA':
if self.purifier_data.auto_mode:
return PRESET_MODE_AUTO
if self.purifier_data.night_mode:
return PRESET_MODE_NIGHT
if self.purifier_data.rapid_mode:
return PRESET_MODE_RAPID
else:
if self.purifier_data.auto_eco_mode or self.purifier_data.auto_mode:
return PRESET_MODE_AUTO
Expand All @@ -127,6 +138,14 @@ def percentage(self) -> int:

if not self.is_on:
return 0
## 250S: When in smart(eco) mode, the fan speed is returned as 9.
## When fan speed of 5 is returned, the purifier is in Rapid mode.
## Neither of these speeds is a valid user-selectable speed so, in HA, we need to artifically
## show the speed as being 0, which is in line with the IoCare app showing no speed selected
## when in either of these two modes.
if self.purifier_data.device_attr['product_name'] == 'COLUMBIA':
if self.purifier_data.fan_speed in ['5', '9']:
return IOCARE_FAN_SPEED_TO_HASS.get(IOCARE_FAN_OFF)
return IOCARE_FAN_SPEED_TO_HASS.get(self.purifier_data.fan_speed)

@property
Expand Down Expand Up @@ -224,6 +243,12 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
self.purifier_data.night_mode = True
self.purifier_data.auto_mode = False
self.purifier_data.fan_speed = IOCARE_FAN_OFF
if preset_mode == PRESET_MODE_RAPID:
await self.coordinator.client.async_set_rapid_mode(self.purifier_data.device_attr)
self.purifier_data.auto_mode = False
self.purifier_data.night_mode = False
self.purifier_data.rapid_mode = True
self.purifier_data.fan_speed = IOCARE_FAN_OFF

self.async_write_ha_state()
await self.coordinator.async_request_refresh()
4 changes: 2 additions & 2 deletions custom_components/coway/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/RobertD502/home-assistant-iocare/issues",
"requirements": [
"cowayaio==0.1.8"
"cowayaio==0.1.9"
],
"version": "0.3.3"
"version": "0.4.0"
}
92 changes: 92 additions & 0 deletions custom_components/coway/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from .const import (
COWAY_COORDINATOR,
DOMAIN,
IOCARE_LIGHT_MODES,
IOCARE_LIGHT_MODES_TO_HASS,
IOCARE_PRE_FILTER_TO_HASS,
IOCARE_PRE_FILTER_WASH_FREQUENCIES,
IOCARE_SMART_SENSITIVITIES,
Expand All @@ -29,6 +31,7 @@
HASS_TIMER_TO_IOCARE = {v: k for (k, v) in IOCARE_TIMERS_TO_HASS.items()}
HASS_PRE_FILTER_TO_IOCARE = {v: k for (k, v) in IOCARE_PRE_FILTER_TO_HASS.items()}
HASS_SMART_SENSITIVITY_TO_IOCARE = {v: k for (k, v) in IOCARE_SMART_SENSITIVITY_TO_HASS.items()}
HASS_LIGHT_MODE_TO_IOCARE = {v: k for (k, v) in IOCARE_LIGHT_MODES_TO_HASS.items()}


async def async_setup_entry(
Expand All @@ -41,6 +44,10 @@ async def async_setup_entry(
selects = []

for purifier_id, purifier_data in coordinator.data.purifiers.items():
product_name = purifier_data.device_attr['product_name']
#250S purifier has multiple light modes
if product_name in ['COLUMBIA']:
selects.append(Light(coordinator, purifier_id))
selects.extend((
Timer(coordinator, purifier_id),
PreFilterFrequency(coordinator, purifier_id),
Expand Down Expand Up @@ -314,3 +321,88 @@ async def async_select_option(self, option: str) -> None:

self.async_write_ha_state()
await self.coordinator.async_request_refresh()


class Light(CoordinatorEntity, SelectEntity):
"""Representation of selecting light mode. Currently only used
by 250S purifiers.
"""

def __init__(self, coordinator, purifier_id):
super().__init__(coordinator)
self.purifier_id = purifier_id

@property
def purifier_data(self) -> CowayPurifier:
"""Handle coordinator purifier data."""

return self.coordinator.data.purifiers[self.purifier_id]

@property
def device_info(self) -> dict[str, Any]:
"""Return device registry information for this entity."""

return {
"identifiers": {(DOMAIN, self.purifier_data.device_attr['device_id'])},
"name": self.purifier_data.device_attr['name'],
"manufacturer": "Coway",
"model": self.purifier_data.device_attr['model'],
}

@property
def unique_id(self) -> str:
"""Sets unique ID for this entity."""

return self.purifier_data.device_attr['device_id'] + '_light'

@property
def name(self) -> str:
"""Return name of the entity."""

return "Light"

@property
def has_entity_name(self) -> bool:
"""Indicate that entity has name defined."""

return True

@property
def icon(self) -> str:
"""Set icon."""

return 'mdi:lightbulb'

@property
def current_option(self) -> str:
"""Returns current light mode."""

return IOCARE_LIGHT_MODES_TO_HASS.get(self.purifier_data.light_mode)

@property
def options(self) -> list:
"""Return list of all the available light modes."""

return IOCARE_LIGHT_MODES

@property
def available(self) -> bool:
"""Return true if purifier is connected to Coway servers."""

if self.purifier_data.network_status:
return True
else:
return False

async def async_select_option(self, option: str) -> None:
"""Change the selected option."""

if self.purifier_data.is_on:
mode = HASS_LIGHT_MODE_TO_IOCARE.get(option)
await self.coordinator.client.async_set_light_mode(self.purifier_data.device_attr, mode)
self.purifier_data.light_mode = mode
else:
LOGGER.error(f'Setting light mode for {self.purifier_data.device_attr["name"]} can only be done when the purifier is On.')

self.async_write_ha_state()
await self.coordinator.async_request_refresh()
1 change: 0 additions & 1 deletion custom_components/coway/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ async def async_setup_entry(
sensors = []

for purifier_id, purifier_data in coordinator.data.purifiers.items():
LOGGER.debug(f'PURIFIER DATA for {purifier_id}: {purifier_data}')
if purifier_data.mcu_version is None:
sensors.append(AirQualityIndex(coordinator, purifier_id))

Expand Down
2 changes: 2 additions & 0 deletions custom_components/coway/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ async def async_setup_entry(
switches = []

for purifier_id, purifier_data in coordinator.data.purifiers.items():
product_name = purifier_data.device_attr['product_name']
if product_name not in ['COLUMBIA']:
switches.extend((
PurifierLight(coordinator, purifier_id),
))
Expand Down

0 comments on commit a4877e3

Please sign in to comment.