Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: libdyson-wg/ha-dyson
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.3.8
Choose a base ref
...
head repository: libdyson-wg/ha-dyson
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Jan 10, 2024

  1. Update Timer services so that YAML configuration is not required (#114)

    * Update Timer services so that YAML configuration is not required
    
    * Update services.yaml to fix linting error
    
    * Update services.yaml to fix second linting issue
    codyc1515 authored Jan 10, 2024
    Copy the full SHA
    d67e4f5 View commit details
  2. version bump

    dotvezz committed Jan 10, 2024
    Copy the full SHA
    1e4224a View commit details
  3. Perform checks and warnings before adjusting target temperature (#117)

    Co-authored-by: Cody Cooper <Cody.Cooper@terracat.co.nz>
    codyc1515 and Cody Cooper authored Jan 10, 2024
    Copy the full SHA
    2371ac9 View commit details

Commits on Jan 21, 2024

  1. Copy the full SHA
    faa2b8b View commit details

Commits on Feb 20, 2024

  1. Add dependency and remove requirement from manifest.json (#129)

    We have a dependency on MQTT that needs to be initialised before we start reaching out to the device. We also don't need to explictly call out paho-mqtt as a dependency as it is required by HA more generally and is not specific to Dyson.
    codyc1515 authored Feb 20, 2024
    Copy the full SHA
    ec7d467 View commit details

Commits on Feb 27, 2024

  1. Adds new climate entity support flags, resolving some logs (#136)

    Co-authored-by: Ben Vezzani <ben@vez.email>
    dotvezz and Ben Vezzani authored Feb 27, 2024
    Copy the full SHA
    4bc754c View commit details

Commits on Mar 2, 2024

  1. Update manifest.json for import_executor (#139)

    * Update manifest.json
    
    as per-discussion with @bdraco, this will limit/eliminate the time that `dyson_local` blocks the HA event loop.
    
    fixes/solves #138
    
    * Update custom_components/dyson_local/manifest.json
    
    * Update custom_components/dyson_local/manifest.json
    
    Co-authored-by: J. Nick Koston <nick@koston.org>
    
    ---------
    
    Co-authored-by: Ben Vezzani <ben+github@vez.email>
    Co-authored-by: J. Nick Koston <nick@koston.org>
    3 people authored Mar 2, 2024
    Copy the full SHA
    02eff0a View commit details
  2. version bump

    dotvezz authored Mar 2, 2024
    Copy the full SHA
    8f15eec View commit details

Commits on Mar 29, 2024

  1. Implements turn_on and turn_off for the Climate entity (#145)

    Co-authored-by: Ben Vezzani <ben+github@vez.email>
    dotvezz and dotvezz committed Mar 29, 2024
    Copy the full SHA
    6f07933 View commit details
  2. version bump

    dotvezz committed Mar 29, 2024
    Copy the full SHA
    1985911 View commit details

Commits on Apr 1, 2024

  1. Copy the full SHA
    b509ccc View commit details
  2. Copy the full SHA
    e61637f View commit details

Commits on Apr 5, 2024

  1. Task/vis nav (#150)

    * Vis Nav Fan Speeds and bug fix
    
    * Adds handling for MACHINE_OFF state from Vis Nav vacuum
    dotvezz committed Apr 5, 2024
    Copy the full SHA
    d723fba View commit details

Commits on Apr 30, 2024

  1. Fixes the vis nav power state enum set (#154)

    Co-authored-by: Ben Vezzani <ben@vez.email>
    dotvezz and Ben Vezzani authored Apr 30, 2024
    Copy the full SHA
    4de4575 View commit details

Commits on Aug 8, 2024

  1. Copy the full SHA
    412d9b2 View commit details
  2. Update README.md

    dotvezz authored Aug 8, 2024
    Copy the full SHA
    9d184a2 View commit details

Commits on Aug 18, 2024

  1. Copy the full SHA
    428c7ae View commit details

Commits on Aug 25, 2024

  1. Copy the full SHA
    460e90b View commit details

Commits on Aug 29, 2024

  1. Update hacs.json (#189)

    dotvezz authored Aug 29, 2024
    Copy the full SHA
    1428038 View commit details

Commits on Sep 16, 2024

  1. remove duplicate object key (#190)

    * remove duplicate object key
    
    * Update alphabetical order in manifest.json
    
    ---------
    
    Co-authored-by: Cody C <50791984+codyc1515@users.noreply.github.com>
    mikeMTOL and codyc1515 authored Sep 16, 2024
    Copy the full SHA
    1f39d14 View commit details
  2. Fix reloading the integration from hanging (#192)

    * fix reloading entities
    
    * bump
    
    * Fix conflict from main merge
    
    ---------
    
    Co-authored-by: Ben Vezzani <dotvezz@gmail.com>
    knightzac19 and dotvezz authored Sep 16, 2024
    Copy the full SHA
    cefd4f5 View commit details

Commits on Nov 2, 2024

  1. Update README.md

    dotvezz authored Nov 2, 2024
    Copy the full SHA
    bb59094 View commit details
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ Once you have installed the integration, navigate to `Settings > Devices` tab, p

### Setup using device Wi-Fi information

Note: Some new models released after 2020 do not ship with a Wi-Fi information sticker. They are still supported by this integration, but can only be configured via your MyDyson account. After setting up your devices, your account can be deleted from Home Assistant if you prefer to stay offline.
Note: New models released after 2020 do not ship with a Wi-Fi information sticker. These models are still supported by this integration, but can only be configured via your MyDyson account or with the manual setup described below. After setting up your devices, your account can be deleted from Home Assistant if you prefer to stay offline.

Find your device Wi-Fi SSID and password on the sticker on your device body or user's manual. Don't fill in your home Wi-Fi information. Note that this method only uses SSID and password to calculate serial, credential, and device type so you still need to setup your device on the official mobile app first.

@@ -74,7 +74,7 @@ Note: When setting up your MyDyson account, please make sure you check your emai

### Setup manually

If you want to manually set up a Dyson device, you need to get credentials first. Clone or download https://github.com/libdyson-wg/libdyson-neon, then use `python3 get_devices.py` to do that. You may need to install some dependencies using `pip3 install -r requirements.txt`.
If you want to manually set up a Dyson device, you need to get credentials first. You can use [opendyson](https://github.com/libdyson-wg/opendyson) to connect to the Dyson API and fetch the required device credentials, then use those credentials in the manual setup for your device on Home Assistant.

## FAQ

@@ -85,7 +85,15 @@ This is a **custom integration** not a **custom add-on**. You need to install [H

### How do I migrate from [shenxn/ha-dyson](https://github.com/shenxn/ha-dyson)?

If you used Dyson Local from shenxn, you can migrate fairly easily:
#### Before we look into the migration options, a note on sensors for Volatile Organic Compounts and Nitrogen Dioxide

There's a LOT of reading in https://github.com/libdyson-wg/ha-dyson/issues/4 if you want the full context, and some additional summaries in https://github.com/libdyson-wg/ha-dyson/issues/89 and https://github.com/libdyson-wg/ha-dyson/issues/127.

The short answer here is: while we're really grateful to Shen and the original set of contributors whose shoulders we're standing on here, the original VOC and NO2 sensors were incorrectly reporting µg/m³, when the sensor data from the device is actually an index. Unfortunately, the fan devices do not give us any raw sensor numbers, and we don't know the proprietary calculations that Dyson uses to turn their raw sensor data into the index value.

As a result, this integration has removed the old sensor.device_volatile_organic_compounds entities. We've implemented new sensor.device_volatile_organic_compounds_index entities that correctly reflect the data.

You can delete the old entities, which should now be unavailable, from your Home Assistant instance.

#### Experimental no-reconfiguration migration

39 changes: 15 additions & 24 deletions custom_components/dyson_local/__init__.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
from .vendor.libdyson import (
Dyson360Eye,
Dyson360Heurist,
Dyson360VisNav,
DysonPureHotCool,
DysonPureHotCoolLink,
DysonPurifierHumidifyCool,
@@ -94,11 +95,8 @@ async def async_setup_account(hass: HomeAssistant, entry: ConfigEntry) -> bool:
DATA_ACCOUNT: account,
DATA_DEVICES: devices,
}
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


@@ -113,7 +111,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.data[CONF_DEVICE_TYPE],
)

if not isinstance(device, Dyson360Eye) and not isinstance(device, Dyson360Heurist):
if (not isinstance(device, Dyson360Eye)
and not isinstance(device, Dyson360Heurist)
and not isinstance(device, Dyson360VisNav)):
# Set up coordinator
async def async_update_data():
"""Poll environmental data from the device."""
@@ -132,12 +132,6 @@ async def async_update_data():
else:
coordinator = None

async def _async_forward_entry_setup():
for component in _async_get_platforms(device):
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

def setup_entry(host: str, is_discovery: bool = True) -> bool:
try:
device.connect(host)
@@ -153,7 +147,7 @@ def setup_entry(host: str, is_discovery: bool = True) -> bool:
hass.data[DOMAIN][DATA_DEVICES][entry.entry_id] = device
hass.data[DOMAIN][DATA_COORDINATORS][entry.entry_id] = coordinator
asyncio.run_coroutine_threadsafe(
_async_forward_entry_setup(), hass.loop
hass.config_entries.async_forward_entry_setups(entry, _async_get_platforms(device)), hass.loop
).result()

host = entry.data.get(CONF_HOST)
@@ -184,26 +178,23 @@ def stop_discovery(_):

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Dyson local."""
device = hass.data[DOMAIN][DATA_DEVICES][entry.entry_id]
ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in _async_get_platforms(device)
]
)
)
if ok:
device: DysonDevice = hass.data[DOMAIN][DATA_DEVICES][entry.entry_id]

unload_ok = await hass.config_entries.async_unload_platforms(entry, _async_get_platforms(device))

if unload_ok:
hass.data[DOMAIN][DATA_DEVICES].pop(entry.entry_id)
hass.data[DOMAIN][DATA_COORDINATORS].pop(entry.entry_id)
await hass.async_add_executor_job(device.disconnect)
# TODO: stop discovery
return ok
return unload_ok


@callback
def _async_get_platforms(device: DysonDevice) -> List[str]:
if isinstance(device, Dyson360Eye) or isinstance(device, Dyson360Heurist):
if (isinstance(device, Dyson360Eye)
or isinstance(device, Dyson360Heurist)
or isinstance(device, Dyson360VisNav)):
return ["binary_sensor", "sensor", "vacuum"]
platforms = ["fan", "select", "sensor", "switch"]
if isinstance(device, DysonPureHotCool):
40 changes: 39 additions & 1 deletion custom_components/dyson_local/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,12 @@

from typing import Callable

from .vendor.libdyson import Dyson360Eye, Dyson360Heurist, DysonPureHotCoolLink
from .vendor.libdyson import (
Dyson360Eye,
Dyson360Heurist,
Dyson360VisNav,
DysonPureHotCoolLink,
)

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@@ -29,6 +34,13 @@ async def async_setup_entry(
if isinstance(device, Dyson360Eye):
entities.append(DysonVacuumBatteryChargingSensor(device, name))
if isinstance(device, Dyson360Heurist):
entities.extend(
[
DysonVacuumBatteryChargingSensor(device, name),
Dyson360VisNavBinFullSensor(device, name),
]
)
if isinstance(device, Dyson360VisNav):
entities.extend(
[
DysonVacuumBatteryChargingSensor(device, name),
@@ -92,6 +104,32 @@ def sub_unique_id(self):
return "bin_full"


class Dyson360VisNavBinFullSensor(DysonEntity, BinarySensorEntity):
"""Dyson 360 VisNav bin full sensor."""

_attr_entity_category = EntityCategory.DIAGNOSTIC

@property
def is_on(self) -> bool:
"""Return if the sensor is on."""
return self._device.is_bin_full

@property
def icon(self) -> str:
"""Return the sensor icon."""
return ICON_BIN_FULL

@property
def sub_name(self) -> str:
"""Return the name of the sensor."""
return "Bin Full"

@property
def sub_unique_id(self):
"""Return the sensor's unique id."""
return "bin_full"


class DysonPureHotCoolLinkTiltSensor(DysonEntity, BinarySensorEntity):
"""Dyson Pure Hot+Cool Link tilt sensor."""

19 changes: 15 additions & 4 deletions custom_components/dyson_local/climate.py
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@

HVAC_MODES = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
FAN_MODES = [FAN_FOCUS, FAN_DIFFUSE]
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
SUPPORT_FLAGS_LINK = SUPPORT_FLAGS | ClimateEntityFeature.FAN_MODE


@@ -46,6 +46,8 @@ async def async_setup_entry(
class DysonClimateEntity(DysonEntity, ClimateEntity):
"""Dyson climate entity base class."""

_enable_turn_on_off_backwards_compatibility = False

@property
def hvac_mode(self) -> str:
"""Return hvac operation."""
@@ -76,6 +78,12 @@ def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_FLAGS

def turn_on(self) -> None:
self._device.turn_on()

def turn_off(self) -> None:
self._device.turn_off()

@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
@@ -117,13 +125,16 @@ def max_temp(self):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
# Check if a temperature was sent
if target_temp is None:
_LOGGER.error("Missing target temperature %s", kwargs)
return
# Limit the target temperature into acceptable range
if target_temp < self.min_temp or target_temp > self.max_temp:
_LOGGER.warning('Temperature requested is outside min/max range, adjusting')
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
_LOGGER.debug("Set %s temperature %s", self.name, target_temp)
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
self._device.set_heat_target(target_temp + 273)

def set_hvac_mode(self, hvac_mode: str):
10 changes: 9 additions & 1 deletion custom_components/dyson_local/fan.py
Original file line number Diff line number Diff line change
@@ -52,7 +52,13 @@

SPEED_RANGE = (1, 10)

COMMON_FEATURES = FanEntityFeature.OSCILLATE | FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE
COMMON_FEATURES = (
FanEntityFeature.OSCILLATE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)


async def async_setup_entry(
@@ -82,6 +88,8 @@ async def async_setup_entry(
class DysonFanEntity(DysonEntity, FanEntity):
"""Dyson fan entity base class."""

_enable_turn_on_off_backwards_compatibility = False

_MESSAGE_TYPE = MessageType.STATE

@property
4 changes: 2 additions & 2 deletions custom_components/dyson_local/humidifier.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from .vendor.libdyson import MessageType

from homeassistant.components.humidifier import (
DEVICE_CLASS_HUMIDIFIER,
HumidifierDeviceClass,
HumidifierEntityFeature,
HumidifierEntity,
)
@@ -36,7 +36,7 @@ class DysonHumidifierEntity(DysonEntity, HumidifierEntity):

_MESSAGE_TYPE = MessageType.STATE

_attr_device_class = DEVICE_CLASS_HUMIDIFIER
_attr_device_class = HumidifierDeviceClass.HUMIDIFIER
_attr_available_modes = AVAILABLE_MODES
_attr_max_humidity = 70
_attr_min_humidity = 30
6 changes: 3 additions & 3 deletions custom_components/dyson_local/manifest.json
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@
"name": "Dyson",
"codeowners": ["@libdyson-wg", "@dotvezz"],
"config_flow": true,
"dependencies": ["zeroconf"],
"dependencies": ["mqtt", "zeroconf"],
"documentation": "https://github.com/libdyson-wg/ha-dyson",
"import_executor": true,
"iot_class": "local_push",
"issue_tracker": "https://github.com/libdyson-wg/ha-dyson/issues",
"requirements": ["paho-mqtt"],
"version": "1.3.8"
"version": "1.4.2"
}
3 changes: 2 additions & 1 deletion custom_components/dyson_local/sensor.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
from .vendor.libdyson import (
Dyson360Eye,
Dyson360Heurist,
Dyson360VisNav,
DysonDevice,
DysonPureCoolLink,
DysonPurifierHumidifyCool,
@@ -44,7 +45,7 @@ async def async_setup_entry(
"""Set up Dyson sensor from a config entry."""
device = hass.data[DOMAIN][DATA_DEVICES][config_entry.entry_id]
name = config_entry.data[CONF_NAME]
if isinstance(device, Dyson360Eye) or isinstance(device, Dyson360Heurist):
if isinstance(device, Dyson360Eye) or isinstance(device, Dyson360Heurist) or isinstance(device, Dyson360VisNav):
entities = [DysonBatterySensor(device, name)]
else:
coordinator = hass.data[DOMAIN][DATA_COORDINATORS][config_entry.entry_id]
21 changes: 13 additions & 8 deletions custom_components/dyson_local/services.yaml
Original file line number Diff line number Diff line change
@@ -17,13 +17,18 @@ set_angle:

set_timer:
name: Set Timer
description: Set the sleep timer.
description: Sets, or clears, the timer of a fan (in minutes).
target:
entity:
domain: fan
fields:
entity_id:
name: Entity ID
description: Name(s) of the entities to set the sleep timer for
example: "fan.living_room"
timer:
name: Timer
description: The value in minutes to set the timer to, 0 to disable it
example: 30
name: Minutes
description: How many minutes the timer should run for. Set to 0 to disable timer.
selector:
number:
min: 0
max: 540
step: 1
unit_of_measurement: "minutes"
required: true
Loading