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: UBayouski/DysonPureLinkPlugin
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: daggelpop/DysonPureLinkPlugin
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 14 commits
  • 7 files changed
  • 2 contributors

Commits on Jun 9, 2018

  1. Copy the full SHA
    774fbc8 View commit details
  2. set utf-8 encoding

    daggelpop committed Jun 9, 2018
    Copy the full SHA
    b4b921b View commit details
  3. Copy the full SHA
    abe3303 View commit details

Commits on Jun 30, 2018

  1. add egg-info to .gitignore

    daggelpop committed Jun 30, 2018
    Copy the full SHA
    5ac24b8 View commit details
  2. Package code for reuse

    daggelpop committed Jun 30, 2018
    Copy the full SHA
    38e2112 View commit details
  3. Copy the full SHA
    cbf5ea2 View commit details

Commits on Sep 30, 2018

  1. Copy the full SHA
    ca0beb8 View commit details

Commits on Jun 1, 2019

  1. Copy the full SHA
    94715db View commit details

Commits on Jul 24, 2019

  1. Copy the full SHA
    dde94a0 View commit details

Commits on Aug 24, 2019

  1. Add TOGGLE mode to standby monitoring setter

    Gitea committed Aug 24, 2019
    Copy the full SHA
    4034f11 View commit details

Commits on Aug 25, 2019

  1. Copy the full SHA
    8a1cd32 View commit details

Commits on May 16, 2020

  1. Copy the full SHA
    aea0dc3 View commit details

Commits on Jul 16, 2022

  1. Add oscillation control

    daggelpop committed Jul 16, 2022
    Copy the full SHA
    41f1f05 View commit details
  2. Fix documentation

    daggelpop committed Jul 16, 2022
    Copy the full SHA
    f8b5459 View commit details
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -47,3 +47,8 @@ Temporary Items
.apdisk
dyson_pure_link.yaml
*.pyc
.idea
*.egg
*.EGG
*.egg-info
*.EGG-INFO
Empty file added DysonPureLinkPlugin/__init__.py
Empty file.
Original file line number Diff line number Diff line change
@@ -3,3 +3,4 @@ DYSON_SERIAL: 'Product SSID'
DYSON_IP: 'Device IP Address'
DYSON_PORT: 1883 # Default port
DYSON_TYPE: 455 # Usully one of 455, 465, 475
TEMPERATURE_UNIT: 'C' # either 'C' or 'F'
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-

"""Dyson Pure Link Device Logic"""

import base64, json, hashlib, os, time, yaml
@@ -21,6 +23,13 @@ def __init__(self):
self.state_data = None
self._is_connected = None

def __enter__(self):
self.parse_config()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.disconnect_device()

@property
def has_valid_data(self):
return self.sensor_data and self.sensor_data.has_data
@@ -45,6 +54,10 @@ def ip_address(self):
def port_number(self):
return self.config['DYSON_PORT']

@property
def temperature_unit(self):
return self.config['TEMPERATURE_UNIT']

@property
def device_command(self):
return '{0}/{1}/command'.format(self.device_type, self.serial_number)
@@ -73,8 +86,8 @@ def on_disconnect(client, userdata, return_code):

userdata.disconnected.put_nowait(True)

@staticmethod
def on_message(client, userdata, message):
# @staticmethod
def on_message(self, client, userdata, message):
"""Static callback to handle incoming messages"""
payload = message.payload.decode("utf-8")
json_message = json.loads(payload)
@@ -83,7 +96,7 @@ def on_message(client, userdata, message):
userdata.state_data_available.put_nowait(StateData(json_message))

if SensorsData.is_sensors_data(json_message):
userdata.sensor_data_available.put_nowait(SensorsData(json_message))
userdata.sensor_data_available.put_nowait(SensorsData(json_message, self.temperature_unit))

def _request_state(self):
"""Publishes request for current state message"""
@@ -154,16 +167,85 @@ def connect_device(self):
self.client = None
return False

def set_fan_mode(self, mode):
"""Changes fan mode: ON|OFF|AUTO"""
def set_fan_mode(self, mode, speed=0):
"""
Changes fan mode: FAN|OFF|AUTO
Change fan speed: 1 - 10
"""
data = {'fmod': mode}
if 1 <= speed <= 10:
data['fnsp'] = '{:04d}'.format(speed)

if self._is_connected:
self._change_state({'fmod': mode})
self._change_state(data)

def set_heating_mode(self, mode, max_temperature=None):
"""
Changes heating mode: HEAT|OFF
Change max temperature: 274K - 310K, 1C - 37C, ??F - ??F
"""

data = {'hmod': mode}

if max_temperature:
temperature, unit = int(max_temperature[:-1]), max_temperature[-1]
converters = {
'C': SensorsData.celsius_to_kelvin,
'F': SensorsData.fahrenheit_to_kelvin,
'K': lambda x: x,
}
func = converters.get(unit, lambda x: -1)
kelvins = func(temperature)
if 274 <= kelvins <= 310:
data['hmax'] = '{:04d}'.format(int(kelvins) * 10)

if self._is_connected:
self._change_state(data)

def set_oscillation(self, mode):
"""Changes oscillation: ON|OFF|TOGGLE"""
if self._is_connected:
if mode == 'TOGGLE':
oscillation = getattr(
self.state_data,
'oscillation',
None,
)
if oscillation:
mode = 'OFF' if oscillation == 'ON' else 'ON'
else:
return
self._change_state({'oson': mode})

def set_standby_monitoring(self, mode):
"""Changes standby monitoring: ON|OFF"""
"""Changes standby monitoring: ON|OFF|TOGGLE"""
if self._is_connected:
if mode == 'TOGGLE':
standby_monitoring = getattr(
self.state_data,
'standby_monitoring',
None,
)
if standby_monitoring:
mode = 'OFF' if standby_monitoring == 'ON' else 'ON'
else:
return
self._change_state({'rhtm': mode})

def set_timer(self, duration):
"""
Duration: OFF | 0-539
"""
data = None

if duration in ("OFF", 0):
data = {"sltm": "OFF"}
elif type(duration) == int and (0 < duration < 540):
data = {"sltm": '{:04d}'.format(duration)}

if data and self._is_connected:
self._change_state(data)

def get_data(self):
return (self.state_data, self.sensor_data) if self.has_valid_data else tuple()

8 changes: 7 additions & 1 deletion run_plugin.py → DysonPureLinkPlugin/run_plugin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function
import argparse

from dyson_pure_link_device import DysonPureLink
from value_types import FanMode, StandbyMonitoring

if __name__ == '__main__':

def main():
args_parser = argparse.ArgumentParser()
args_parser.add_argument('-fan')
args_parser.add_argument('-standby')
@@ -41,3 +43,7 @@

# Disconnect device (IMPORTANT) and print result
print('Disconnected: ', dyson_pure_link.disconnect_device())


if __name__ == '__main__':
main()
58 changes: 53 additions & 5 deletions value_types.py → DysonPureLinkPlugin/value_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-

"""Value types, enums and mappings package"""

# Map for connection return code and its meaning
@@ -48,21 +50,41 @@ def __init__(self, return_code, *args):
class SensorsData(object):
"""Value type for sensors data"""

def __init__(self, message):
def __init__(self, message, temperature_unit):
data = message['data']
humidity = data['hact']
temperature = data['tact']
volatile_compounds = data['vact']
timer = data['sltm']
self.temperature_unit = temperature_unit

self.humidity = None if humidity == 'OFF' else int(humidity)
self.temperature = None if temperature == 'OFF' else self.kelvin_to_fahrenheit(float(temperature) / 10)
if temperature == 'OFF':
self.temperature = None
else:
conversion_function = self.kelvin_to_fahrenheit if temperature_unit == 'F' else self.kelvin_to_celsius
self.temperature = conversion_function(float(temperature) / 10)
self.volatile_compounds = 0 if volatile_compounds == 'INIT' else int(volatile_compounds)
self.particles = int(data['pact'])
self.timer = 0 if timer == 'OFF' else int(timer)

def __repr__(self):
"""Return a String representation"""
return 'Temperature: {0} F, Humidity: {1} %, Volatile Compounds: {2}, Particles: {3}'.format(
self.temperature, self.humidity, self.volatile_compounds, self.particles)
if self.has_data:
return 'Temperature: {:.1f}°{}, Humidity: {} %, Humidex: {:.1f}, Volatile Compounds: {}, Particles: {}, Timer: {}'.format(
self.temperature,
self.temperature_unit,
self.humidity,
self.humidex,
self.volatile_compounds,
self.particles,
self.timer,
)
else:
return 'Volatile Compounds: {}, Particles: {}'.format(
self.volatile_compounds,
self.particles
)

@property
def has_data(self):
@@ -73,17 +95,43 @@ def is_sensors_data(message):
return message['msg'] in ['ENVIRONMENTAL-CURRENT-SENSOR-DATA']

@staticmethod
def kelvin_to_fahrenheit (kelvin_value):
def kelvin_to_fahrenheit(kelvin_value):
return kelvin_value * 9 / 5 - 459.67

@staticmethod
def kelvin_to_celsius(kelvin_value):
return kelvin_value - 273

@staticmethod
def fahrenheit_to_kelvin(fahrenheit_value):
return (fahrenheit_value + 459.67) * 5 / 9

@staticmethod
def celsius_to_kelvin(celsius_value):
return celsius_value + 273

@property
def humidex(self):
return self.temperature + 0.5555 * \
(
6.112 * 10.0 ** (7.5 * (self.temperature / (237.7 + self.temperature)))
* (self.humidity / 100.0)
- 10
)


class StateData(object):
"""Value type for state data"""

def __init__(self, message):
data = message['product-state']

self.fan_mode = self._get_field_value(data['fmod'])
self.fan_speed = self._get_field_value(data['fnsp']).replace('AUTO', '-1')
self.fan_state = self._get_field_value(data['fnst'])
self.heating_mode = self._get_field_value(data['hmod'])
self.heating_max_temp = self._get_field_value(data['hmax'])
self.heating_state = self._get_field_value(data['hsta'])
self.night_mode = self._get_field_value(data['nmod'])
self.speed = self._get_field_value(data['fnsp'])
self.oscillation = self._get_field_value(data['oson'])
21 changes: 21 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from distutils.core import setup

setup(
name='DysonPureLinkPlugin',
version='0.1',
packages=['DysonPureLinkPlugin'],
url='https://github.com/UBayouski/DysonPureLinkPlugin',
license='MIT',
author='UBayouski',
author_email='',
description='',
install_requires=[
'paho-mqtt',
'pyyaml',
],
entry_points={
'console_scripts': [
'dyson_run_plugin = DysonPureLinkPlugin.run_plugin:main'
]
},
)