Skip to content

Commit

Permalink
Adds initial support for Dyson Big+Quiet devices (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
dotvezz committed Sep 7, 2023
1 parent b7ab8b6 commit 6fc0ac1
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 1 deletion.
13 changes: 13 additions & 0 deletions debug/check_humidity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ..libdyson import DysonPureHotCool

dev = DysonPureHotCool(
serial='X3V-US-RKA0414A',
credential='8qno2lc/IRy2xAFUem4u6AwBmk8YzWiDesTCS37VcSuphygAry+LDukJWfS1y93iYqGvWIaJ4xOWxu5r4OS+3g==',
device_type="527K"
)

dev.connect('192.168.1.137')

print(dev.humidity)

print(dev.is_on)
53 changes: 53 additions & 0 deletions debug/devices
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
192.168.1.137: DysonDeviceInfo(active=None, serial='X3V-US-RKA0414A', name='Dyson Purifier Hot+Cool™', version='ECG2PF.34.00.003.0042', credential='8qno2lc/IRy2xAFUem4u6AwBmk8YzWiDesTCS37VcSuphygAry+LDukJWfS1y93iYqGvWIaJ4xOWxu5r4OS+3g==', auto_update=False, new_version_available=True, product_type='527K')
DysonDeviceInfo(active=None, serial='N9R-US-PJB7094A', name='', version='ECG2PF.30.06.003.0002', credential='8xPhRU9+SwTOTyU1Fni0VLpjd/hrDCPb2oyqp1eOGM78TmGnd7pae6jNsYTH4qwMnpOOFt2C/6z+20FDfGibhw==', auto_update=False, new_version_available=False, product_type='527'),
DysonDeviceInfo(active=None, serial='NM7-US-REA2128A', name='Downstairs', version='21.04.03', credential='bRSU6+ck9EVZ4PtCUaqJp/XtbEzxEBgGLvPwg3XFJWRQvGMJYKKY8SjFF+153WQ3/YLmy3mUeB2glO6efEHZIQ==', auto_update=True, new_version_available=False, product_type='475'),
DysonDeviceInfo(active=None, serial='NM7-US-REA2583A', name='Dyson Fan', version='21.04.03', credential='qQF4gMdA6Pg8OvY0OKmjpSOq/PAdcnD/Qv3KW8ecrLV3P82iMf9fK/TJBBpw0/2r7uovH0AwXL/WLHJYWpkNvg==', auto_update=False, new_version_available=False, product_type='475'),
]
f
Environmental:
{
'hact': '0053', humidity
'hcho': 'NONE', Formaldehyde
'hchr': 'NONE', High_Res Formaldehyde
'noxl': '0002', Nitrogen Dioxide
'p10r': '0000', "High_res" pm10
'p25r': '0000', "High_res" pm25
'pm10': '0000',
'pm25': '0000',
'sltm': 'OFF',
'tact': '2964', Temp
'va10': '0008', Gen2 VOC
}
{"msg":"ENVIRONMENTAL-CURRENT-SENSOR-DATA","time":"2023-06-11T01:59:08.000Z","data":{"tact":"2958","hact":"0052","pm25":"0002","pm10":"0001","va10":"0008","noxl":"0003","p25r":"0003","p10r":"0003","hcho":"NONE","hchr":"NONE","sltm":"OFF"}}

State:
{
'fpwr': 'ON', Fan Power
'auto': 'OFF', Auto
'oscs': 'OFF', Oscillation something?
'oson': 'OFF', Oscillation something again
'nmod': 'OFF', ???
'rhtm': 'ON', Continuous Monitoring
'fnst': 'FAN', Fan State
'ercd': '32U2', Error Code - 32U2 is probably filter(s)
'wacd': 'NONE', Warning Code
'nmdv': '0004', Night Mode Speed
'fnsp': '0008', Fan Speed
'bril': '0002', ???
'corf': 'OFF',
'cflr': 'INV', Carbon Filter Life
'hflr': '0073', Hepa Filter Life
'cflt': 'NONE', ???
'hflt': 'GCOM', ???
'sltm': 'OFF', Sleep Timer
'osal': '0135', Oscillation Angle Low
'osau': '0225', Oscillation Angle High
'ancp': 'CUST', Oscillation Mode
'hmod': 'OFF', Heat Mode
'hmax': '2936', Heat Target
'tilt': 'OK', Tilt
'hsta': 'OFF', Heat State
'psta': 'OFF',
'fdir': 'ON', Front Airflow
}
10 changes: 10 additions & 0 deletions debug/gpm3 to ppb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

ppb = 28
ppm = ppb / 1000

air = 24.45

# Mol weight
no2 = 46.005

print((ppm * no2) / air)
50 changes: 50 additions & 0 deletions debug/mqtt-listen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import signal
import sys
import time

import paho.mqtt.client as mqtt


def on_connect(client, userdata, flags, rc):
print("Connected to MQTT broker. Press Ctrl+C to exit.")
client.subscribe("#")


def on_message(client, userdata, msg):
print("Received message on topic: {}".format(msg.topic))
print("Message: {}".format(msg.payload.decode()))
print("---------------------")


def on_signal(signum, frame):
print("\nReceived SIGTERM signal. Disconnecting from MQTT broker...")
client.disconnect()
sys.exit(0)


# Set up MQTT client
client = mqtt.Client(client_id="mqtt-subscriber")
client.username_pw_set("X3V-US-RKA0414A", "8qno2lc/IRy2xAFUem4u6AwBmk8YzWiDesTCS37VcSuphygAry+LDukJWfS1y93iYqGvWIaJ4xOWxu5r4OS+3g==")

# Set up signal handler for SIGTERM
signal.signal(signal.SIGTERM, on_signal)

# Set up MQTT client callbacks
client.on_connect = on_connect
client.on_message = on_message

# Connect to MQTT broker
client.connect("192.168.1.137", 1883, 60)

# Start the MQTT loop
client.loop_start()

try:
# Keep the script running until SIGTERM is received
while True:
time.sleep(1)
except KeyboardInterrupt:
# Handle Ctrl+C
print("\nInterrupted by user. Disconnecting from MQTT broker...")
client.disconnect()
sys.exit(0)
9 changes: 9 additions & 0 deletions debug/ppm-to-gpm3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

ppb = 18
ppm = ppb / 1000
mol = 46.005

air = 24.45


print((ppm * mol) / air)
6 changes: 6 additions & 0 deletions libdyson/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
DEVICE_TYPE_PURE_HUMIDIFY_COOL,
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_E,
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_K,
DEVICE_TYPE_PURIFIER_BIG_QUIET,
)

from .const import CleaningMode # noqa: F401
Expand All @@ -37,6 +38,7 @@
from .dyson_pure_hot_cool import DysonPureHotCool
from .dyson_pure_hot_cool_link import DysonPureHotCoolLink
from .dyson_pure_humidify_cool import DysonPurifierHumidifyCool
from .dyson_purifier_big_quiet import DysonBigQuiet
from .utils import get_mqtt_info_from_wifi_info # noqa: F401


Expand Down Expand Up @@ -72,4 +74,8 @@ def get_device(serial: str, credential: str, device_type: str) -> Optional[Dyson
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_E,
]:
return DysonPurifierHumidifyCool(serial, credential, device_type)
if device_type in {
DEVICE_TYPE_PURIFIER_BIG_QUIET,
}:
return DysonBigQuiet(serial, credential, device_type)
return None
2 changes: 2 additions & 0 deletions libdyson/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DEVICE_TYPE_PURE_HOT_COOL = "527" # HP04
DEVICE_TYPE_PURIFIER_HOT_COOL_E = "527E" # HP07 AND HP09
DEVICE_TYPE_PURIFIER_HOT_COOL_K = "527K" # HP07 AND HP09
DEVICE_TYPE_PURIFIER_BIG_QUIET = "664" # BP02, BP03, and BP04

DEVICE_TYPE_NAMES = {
DEVICE_TYPE_360_EYE: "360 Eye robot vacuum",
Expand All @@ -33,6 +34,7 @@
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_K: "Purifier Humidify+Cool",
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_E: "Purifier Humidify+Cool",
DEVICE_TYPE_PURIFIER_HOT_COOL_K: "Purifier Hot+Cool",
DEVICE_TYPE_PURIFIER_BIG_QUIET: "Purifier Big+Quiet Series"
}

ENVIRONMENTAL_OFF = -1
Expand Down
104 changes: 104 additions & 0 deletions libdyson/dyson_purifier_big_quiet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Dyson Pure Cool fan."""

from abc import abstractmethod
from typing import Optional

from .dyson_device import DysonFanDevice


class DysonBigQuiet(DysonFanDevice):
"""Dyson Pure Cool series base class."""

@property
def is_on(self) -> bool:
"""Return if the device is on."""
return self._get_field_value(self._status, "fpwr") == "ON"

@property
def auto_mode(self) -> bool:
"""Return auto mode status."""
return self._get_field_value(self._status, "auto") == "ON"

@property
def front_airflow(self) -> bool:
"""Return if airflow from front is on."""
return self._get_field_value(self._status, "fdir") == "ON"

@property
def night_mode_speed(self) -> int:
"""Return speed in night mode."""
return int(self._get_field_value(self._status, "nmdv"))

@property
def carbon_filter_life(self) -> Optional[int]:
"""Return carbon filter life in percentage."""
filter_life = self._get_field_value(self._status, "cflr")
if filter_life == "INV":
return None
return int(filter_life)

@property
def hepa_filter_life(self) -> Optional[int]:
"""Return HEPA filter life in percentage."""
return int(self._get_field_value(self._status, "hflr"))

@property
def particulate_matter_2_5(self):
"""Return PM 2.5 in micro grams per cubic meter."""
return int(self._get_environmental_field_value("pm25"))

@property
def particulate_matter_10(self):
"""Return PM 2.5 in micro grams per cubic meter."""
return int(self._get_environmental_field_value("pm10"))

@property
def volatile_organic_compounds(self) -> float:
"""Return the index value for VOC"""
return self._get_environmental_field_value("va10", divisor=10)

@property
def nitrogen_dioxide(self) -> float:
"""Return the index value for nitrogen."""
return self._get_environmental_field_value("noxl", divisor=10)

def turn_on(self) -> None:
"""Turn on the device."""
self._set_configuration(fpwr="ON")

def turn_off(self) -> None:
"""Turn off the device."""
self._set_configuration(fpwr="OFF")

def _set_speed(self, speed: int) -> None:
self._set_configuration(fpwr="ON", fnsp=f"{speed:04d}")

def enable_auto_mode(self) -> None:
"""Turn on auto mode."""
self._set_configuration(auto="ON")

def disable_auto_mode(self) -> None:
"""Turn off auto mode."""
self._set_configuration(auto="OFF")

def enable_continuous_monitoring(self) -> None:
"""Turn on continuous monitoring."""
self._set_configuration(
fpwr="ON" if self.is_on else "OFF", # Not sure about this
rhtm="ON",
)

def disable_continuous_monitoring(self) -> None:
"""Turn off continuous monitoring."""
self._set_configuration(
fpwr="ON" if self.is_on else "OFF",
rhtm="OFF",
)

def enable_front_airflow(self) -> None:
"""Turn on front airflow."""
self._set_configuration(fdir="ON")

def disable_front_airflow(self) -> None:
"""Turn off front airflow."""
self._set_configuration(fdir="OFF")
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = libdyson-neon
version = 1.1.0
version = 1.2.0
author = The libdyson Working Group
author_email = [email protected]
license = MIT License
Expand Down
3 changes: 3 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
DEVICE_TYPE_PURE_HUMIDIFY_COOL,
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_E,
DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_K,
DEVICE_TYPE_PURIFIER_BIG_QUIET,
Dyson360Eye,
Dyson360Heurist,
DysonDevice,
Expand All @@ -27,6 +28,7 @@
DysonPureHotCool,
DysonPureHotCoolLink,
DysonPurifierHumidifyCool,
DysonBigQuiet,
get_device,
)

Expand All @@ -51,6 +53,7 @@
(DEVICE_TYPE_PURE_HUMIDIFY_COOL, DysonPurifierHumidifyCool),
(DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_E, DysonPurifierHumidifyCool),
(DEVICE_TYPE_PURIFIER_HUMIDIFY_COOL_K, DysonPurifierHumidifyCool),
(DEVICE_TYPE_PURIFIER_BIG_QUIET, DysonBigQuiet),
],
)
def test_get_device(device_type: str, class_type: Type[DysonDevice]):
Expand Down
Loading

0 comments on commit 6fc0ac1

Please sign in to comment.