Skip to content

Commit

Permalink
Beta (#31)
Browse files Browse the repository at this point in the history
* Infra/websocketlogging (#18)

* Improve WebSocket logging and error handling

* WebSocket Reconnect & Reload Flow

* Updated config constants. (#24)

Add some support for vertical oscillation
Return a Model attribute

* Feature/newfans (#25)

* Update Readme

* Beta integrate/betamike main (#26)

* Improve WebSocket logging and error handling (#15)

* Jeff steinbok patch 3 (#16)

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* add support for DR-HAF003S and basis for other air circulator models

* clean up formatting with black

* update readme with new model

---------

Co-authored-by: Mike Cugini <mike@betamike.com>

* Update pydreoaircirculatorfan.py

Fix baseclass for Air Circulator

* Fix typo

* Add Loggers

* Fix issue with loading wrong state values from AirCirculator.  And error handling.

* More State Fixes

* Bunch of PyLint fixes.  Found some bugs too.

* More forgiving of missing preset modes.
Bit more error logging.

---------

Co-authored-by: Mike Cugini <mike@betamike.com>
  • Loading branch information
JeffSteinbok and betamike authored Jul 19, 2023
1 parent 908b20e commit 0be0d16
Show file tree
Hide file tree
Showing 18 changed files with 714 additions and 301 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,5 @@ tmp
custom_components/aarlo/pyaarlo/main.py
.ycm_extra_conf.py

.vscode

# Stuff Jeff Added for now
ignore
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.pylintArgs": [
"--generated-members", "websockets.*"
]
}
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,36 @@ If you used the very early version of this that required editing `configuration.
- [Adding new Fans](#addingFans)
- [To Do](#todo)

<a name="compatability"></a>
## IMPORTANT NOTE ON CONFIGURATION
If you used the very early version of this that required editing `configuration.yaml`, you will need to do a 1-time reconfiguration. You can delete the configuration entries you added and go through the configuration flow within the HomeAssistant web site.

## Table of Contents
- [Compatability](#compatability)
- [Installation](#installation)
- [Debugging](#debugging)
- [Adding new Fans](#addingFans)
- [To Do](#todo)

<a name="compatability"></a>
## Compatability
Currently supported fans:

Tower fans:
- DR-HTF001S
- DR-HTF002S
- DR-HTF004S
- DR-HTF007S
- DR-HTF008S

Air circulators:
- DR-HAF001S
- DR-HAF003S
- DR-HAF004S

If you have a different model that you can try, please see instructions [below](#addingfans).

<a name="installation"></a>
<a name="installation"></a>
## Installation

Expand All @@ -39,6 +57,8 @@ If you have a different model that you can try, please see instructions [below](
### Manually
Copy the `dreo` directory into your `/config/custom_components` directory, then restart your HomeAssistant Core.

<a name="debugging"></a>
## Debugging
<a name="debugging"></a>
## Debugging
Idealy, use the Diagnostics feature in HomeAssistant to get diagnostics from the integration. Sensitive info should be redacted automatically.
Expand Down Expand Up @@ -75,6 +95,31 @@ Depending on answers, I may reach out and need you to pull some debug logs.
2023-06-29 01:02:25,312 - pydreo - DEBUG - Message: {'method': 'control-report', 'devicesn': 'XXX0393341964289-77f2977b24191a4a:001:0000000000b', 'messageid': 'bdf23a1f-c8e1-4e22-8ad3-dc0cd5dfdc7c', 'timestamp': 1688025746, 'reported': {'windtype': 1}}
```

<a name="todo"></a>
## To Do
<a name="addingFans"></a>
## Adding New Fans
Don't see your model listed above? Let me know in the Issues and I'll add it. Please make sure to include:

* Model - in the format above
* Number of speeds the fan supports (not including "off")
* Does the fan support oscilating?
* What preset modes are supported?
* Is temperature supported?

Depending on answers, I may reach out and need you to pull some debug logs.

### Debug Logs for New Fans

1. Turn on **debug** logging for **pydreo** and start up HA.
1. Go to your Dreo app on your phone, and perform the various commands you want to be able to use in HA. Dreo servers will send updates to the WebSocket i'm listening on.
1. Go look at the logs, you should see something like the below. Send me that, and if you can, how it maps to the actions you were performing.

```
2023-06-29 01:02:25,312 - pydreo - DEBUG - Received message for unknown or unsupported device. SN: XXX341964289-77f2977b24191a4a:001:0000000000b
2023-06-29 01:02:25,312 - pydreo - DEBUG - Message: {'method': 'control-report', 'devicesn': 'XXX0393341964289-77f2977b24191a4a:001:0000000000b', 'messageid': 'bdf23a1f-c8e1-4e22-8ad3-dc0cd5dfdc7c', 'timestamp': 1688025746, 'reported': {'windtype': 1}}
```

<a name="todo"></a>
## To Do
This is my first HA plugin and a bit slow going; bunch of stuff left to do:
Expand Down
13 changes: 5 additions & 8 deletions custom_components/dreo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import (
DOMAIN,
DREO_FANS,
DREO_MANAGER
)
from .const import DOMAIN, DREO_FANS, DREO_MANAGER

_LOGGER = logging.getLogger("dreo")

DOMAIN = "dreo"

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
_LOGGER.debug("async_setup")

_LOGGER.debug(config_entry.data.get(CONF_USERNAME))
Expand All @@ -29,14 +25,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
region = "us"

from .pydreo import PyDreo

manager = PyDreo(username, password, region)

login = await hass.async_add_executor_job(manager.login)

if not login:
_LOGGER.error("Unable to login to the dreo server")
return False

load_devices = await hass.async_add_executor_job(manager.load_devices)

if not load_devices:
Expand Down Expand Up @@ -72,4 +69,4 @@ def process_devices(manager) -> dict:
# Expose fan sensors separately
_LOGGER.info("%d Dreo fans found", len(manager.fans))

return devices
return devices
8 changes: 4 additions & 4 deletions custom_components/dreo/basedevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from typing import Any

#from .pydreo.pydreobasedevice import PyDreoBaseDevice
# from .pydreo.pydreobasedevice import PyDreoBaseDevice

from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity
from homeassistant.core import HomeAssistant, callback
Expand All @@ -13,10 +13,11 @@

from .pydreo.pydreobasedevice import PyDreoBaseDevice


class DreoBaseDeviceHA(Entity):
"""Base class for Dreo Entity Representations."""

def __init__(self, pyDreoBaseDevice : PyDreoBaseDevice) -> None:
def __init__(self, pyDreoBaseDevice: PyDreoBaseDevice) -> None:
"""Initialize the Dreo device."""
self.device = pyDreoBaseDevice
self._attr_unique_id = self.device.sn
Expand All @@ -25,10 +26,9 @@ def __init__(self, pyDreoBaseDevice : PyDreoBaseDevice) -> None:
@property
def available(self) -> bool:
"""Return True if device is available."""
#return self.device.connection_status == "online"
# return self.device.connection_status == "online"
return True

@property
def should_poll(self):
return False

9 changes: 4 additions & 5 deletions custom_components/dreo/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_registry import (
async_entries_for_config_entry
)
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
Expand All @@ -22,6 +20,7 @@

_LOGGER = logging.getLogger("dreo")


class DreoFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Dreo Custom config flow."""

Expand All @@ -45,7 +44,7 @@ def _show_form(self, errors=None):
data_schema=vol.Schema(self.data_schema),
errors=errors if errors else {},
)

async def async_step_user(self, user_input=None):
"""Handle a flow start."""
if self._async_current_entries():
Expand All @@ -65,4 +64,4 @@ async def async_step_user(self, user_input=None):
return self.async_create_entry(
title=self._username,
data={CONF_USERNAME: self._username, CONF_PASSWORD: self._password},
)
)
11 changes: 7 additions & 4 deletions custom_components/dreo/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
KEYS_TO_REDACT = {"sn", "_sn", "wifi_ssid", "module_hardware_mac"}


async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: ConfigEntry) -> dict[str, Any]:
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
manager: PyDreo = hass.data[DOMAIN][DREO_MANAGER]

data = {
DOMAIN: {
"fan_count": len(manager.fans),
"raw_devicelist": _redact_values(manager.raw_response)
"raw_devicelist": _redact_values(manager.raw_response),
},
"devices": {
"fans": [_redact_values(device.__dict__) for device in manager.fans],
Expand All @@ -32,6 +34,7 @@ async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: ConfigE

return data


def _redact_values(data: dict) -> dict:
"""Rebuild and redact values of a dictionary, recursively"""

Expand All @@ -44,6 +47,6 @@ def _redact_values(data: dict) -> dict:
else:
new_data[key] = item
else:
new_data[key] = REDACTED
new_data[key] = REDACTED

return new_data
return new_data
55 changes: 28 additions & 27 deletions custom_components/dreo/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
)

from .basedevice import DreoBaseDeviceHA
from .const import (DOMAIN, DREO_DISCOVERY, DREO_FANS, DREO_MANAGER)
from .pydreo.constant import *
from .const import DOMAIN, DREO_DISCOVERY, DREO_FANS, DREO_MANAGER
from .pydreo.constant import *
from .pydreo.pydreofan import PyDreoFan

_LOGGER = logging.getLogger("dreo")


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
_discovery_info=None
_discovery_info=None,
) -> None:
"""Set up the Dreo fan platform."""
_LOGGER.info("Starting Dreo Fan Platform")
Expand All @@ -45,31 +46,32 @@ async def async_setup_entry(
class DreoFanHA(DreoBaseDeviceHA, FanEntity):
"""Representation of a Dreo fan."""

def __init__(self, pyDreoFan : PyDreoFan):
def __init__(self, pyDreoFan: PyDreoFan):
"""Initialize the Dreo fan device."""
super().__init__(pyDreoFan)
self.device = pyDreoFan

@property
def percentage(self) -> int | None:
"""Return the current speed."""
return ranged_value_to_percentage(self.device.speed_range, self.device.fan_speed)
return ranged_value_to_percentage(
self.device.speed_range, self.device.fan_speed
)

@property
def is_on(self) -> bool:
"""Return True if device is on."""
return self.device.is_on

@property
def oscillating(self) -> bool:
"""This represents horizontal oscillation only"""
return self.device.oscillating

@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
return int_states_in_range(
self.device.speed_range
)
return int_states_in_range(self.device.speed_range)

@property
def preset_modes(self) -> list[str]:
Expand All @@ -79,24 +81,30 @@ def preset_modes(self) -> list[str]:
@property
def preset_mode(self) -> str | None:
"""Get the current preset mode."""
return self.device.preset_mode
if (self.device.supports_preset_modes):
return self.device.preset_mode
else:
return None

@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes of the fan."""
attr = {
'temperature': self.device.temperature
}
attr = {"temperature": self.device.temperature,
'model': self.device.model,
'sn': self.device.sn}
return attr

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return (
FanEntityFeature.SET_SPEED |
FanEntityFeature.OSCILLATE |
FanEntityFeature.PRESET_MODE
)
supported_features = FanEntityFeature.SET_SPEED

if (self.device.supports_preset_modes):
supported_features = supported_features | FanEntityFeature.PRESET_MODE
if (self.device.supports_oscillation):
supported_features = supported_features | FanEntityFeature.OSCILLATE

return supported_features

def turn_on(
self,
Expand All @@ -106,7 +114,7 @@ def turn_on(
) -> None:
"""Turn the device on."""
_LOGGER.debug("DreoFanHA:turn_on")
self.device.set_power(True)
self.device.set_power(True)

def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
Expand All @@ -123,11 +131,7 @@ def set_percentage(self, percentage: int) -> None:
self.device.set_power(True)

self.device.change_fan_speed(
math.ceil(
percentage_to_ranged_value(
self.device.speed_range, percentage
)
)
math.ceil(percentage_to_ranged_value(self.device.speed_range, percentage))
)
self.schedule_update_ha_state()

Expand All @@ -146,13 +150,11 @@ def set_preset_mode(self, preset_mode: str) -> None:

self.schedule_update_ha_state()


def oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
self.device.oscillate(oscillating)
self.schedule_update_ha_state()


async def async_added_to_hass(self):
"""Register callbacks."""

Expand All @@ -164,4 +166,3 @@ def update_state():

_LOGGER.debug("DreoBaseDeviceHA: %s registering callbacks", self._attr_name)
self.device.add_attr_callback(update_state)

1 change: 1 addition & 0 deletions custom_components/dreo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"documentation": "https://github.com/jeffsteinbok/hass-dreo/blob/master/README.md",
"iot_class": "cloud_push",
"issue_tracker": "https://github.com/jeffsteinbok/hass-dreo/issues",
"loggers": ["dreo", "pydreo"],
"requirements": ["websockets"],
"version": "0.0.2"
}
Loading

0 comments on commit 0be0d16

Please sign in to comment.