From 556943cbfb653493231a371f5f76deea1a369a34 Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Sun, 30 Jun 2019 17:08:50 -0700 Subject: [PATCH 1/2] Adds additional types returned when monitoring an LG dryer. --- tests/test_client.py | 110 +++++++++++++++++++++++++++++++++++++++++++ wideq/client.py | 31 +++++++----- 2 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 tests/test_client.py diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..0e219c5 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,110 @@ +import unittest + +from wideq.client import ( + BitValue, EnumValue, ModelInfo, RangeValue, ReferenceValue) + + +DATA = { + 'Value': { + 'AntiBacterial': { + 'default': '0', + 'label': '@WM_DRY27_BUTTON_ANTI_BACTERIAL_W', + 'option': { + '0': '@CP_OFF_EN_W', + '1': '@CP_ON_EN_W' + }, + 'type': 'Enum' + }, + 'Course': { + 'option': ['Course'], + 'type': 'Reference', + }, + 'Initial_Time_H': { + 'default': 0, + 'option': {'max': 24, 'min': 0}, + 'type': 'Range' + }, + 'Option1': { + 'default': '0', + 'option': [ + { + 'default': '0', + 'length': 1, + 'startbit': 0, + 'value': 'ChildLock' + }, + { + 'default': '0', + 'length': 1, + 'startbit': 1, + 'value': 'ReduceStatic' + }, + { + 'default': '0', + 'length': 1, + 'startbit': 2, + 'value': 'EasyIron' + }, + { + 'default': '0', + 'length': 1, + 'startbit': 3, + 'value': 'DampDrySingal' + }, + { + 'default': '0', + 'length': 1, + 'startbit': 4, + 'value': 'WrinkleCare' + }, + { + 'default': '0', + 'length': 1, + 'startbit': 7, + 'value': 'AntiBacterial' + } + ], + 'type': 'Bit' + }, + 'Unexpected': {'type': 'Unexpected'}, + }, +} + + +class ModelInfoTest(unittest.TestCase): + + def setUp(self): + super().setUp() + self.model_info = ModelInfo(DATA) + + def test_value_enum(self): + actual = self.model_info.value('AntiBacterial') + expected = EnumValue({'0': '@CP_OFF_EN_W', '1': '@CP_ON_EN_W'}) + self.assertEqual(expected, actual) + + def test_value_range(self): + actual = self.model_info.value('Initial_Time_H') + expected = RangeValue(min=0, max=24, step=1) + self.assertEqual(expected, actual) + + def test_value_bit(self): + actual = self.model_info.value('Option1') + expected = BitValue({ + 0: {'length': 1, 'value': 'ChildLock'}, + 1: {'length': 1, 'value': 'ReduceStatic'}, + 2: {'length': 1, 'value': 'EasyIron'}, + 3: {'length': 1, 'value': 'DampDrySingal'}, + 4: {'length': 1, 'value': 'WrinkleCare'}, + 7: {'length': 1, 'value': 'AntiBacterial'}, + }) + self.assertEqual(expected, actual) + + def test_value_reference(self): + actual = self.model_info.value('Course') + expected = ReferenceValue('Course') + self.assertEqual(expected, actual) + + def test_value_unsupported(self): + with self.assertRaisesRegex( + ValueError, 'unsupported value type Unexpected'): + self.model_info.value('Unexpected') diff --git a/wideq/client.py b/wideq/client.py index 23ea607..2751588 100644 --- a/wideq/client.py +++ b/wideq/client.py @@ -293,8 +293,10 @@ class DeviceInfo(object): return requests.get(self.model_info_url).json() +BitValue = namedtuple('BitValue', ['options']) EnumValue = namedtuple('EnumValue', ['options']) RangeValue = namedtuple('RangeValue', ['min', 'max', 'step']) +ReferenceValue = namedtuple('ReferenceValue', ['reference']) class ModelInfo(object): @@ -304,31 +306,43 @@ class ModelInfo(object): def __init__(self, data): self.data = data - def value(self, name): + def value(self, name: str): """Look up information about a value. - Return either an `EnumValue` or a `RangeValue`. + :param name: The name to look up. + :returns: One of (`BitValue`, `EnumValue`, `RangeValue`, + `ReferenceValue`). + :raises ValueError: If an unsupported type is encountered. """ d = self.data['Value'][name] if d['type'] in ('Enum', 'enum'): return EnumValue(d['option']) elif d['type'] == 'Range': return RangeValue( - d['option']['min'], d['option']['max'], d['option']['step'] + d['option']['min'], d['option']['max'], + d['option'].get('step', 1) ) + elif d['type'].lower() == 'bit': + bit_values = { + opt['startbit']: { + 'length': opt['length'], + 'value': opt['value'], + } for opt in d['option'] + } + return BitValue(bit_values) + elif d['type'].lower() == 'reference': + return ReferenceValue(d['option'][0]) else: - assert False, "unsupported value type {}".format(d['type']) + raise ValueError("unsupported value type {}".format(d['type'])) def default(self, name): """Get the default value, if it exists, for a given value. """ - return self.data['Value'][name]['default'] def enum_value(self, key, name): """Look up the encoded value for a friendly enum name. """ - options = self.value(key).options options_inv = {v: k for k, v in options.items()} # Invert the map. return options_inv[name] @@ -336,7 +350,6 @@ class ModelInfo(object): def enum_name(self, key, value): """Look up the friendly enum name for an encoded value. """ - options = self.value(key).options return options[value] @@ -344,13 +357,11 @@ class ModelInfo(object): def binary_monitor_data(self): """Check that type of monitoring is BINARY(BYTE). """ - return self.data['Monitoring']['type'] == 'BINARY(BYTE)' def decode_monitor_binary(self, data): """Decode binary encoded status data. """ - decoded = {} for item in self.data['Monitoring']['protocol']: key = item['value'] @@ -363,12 +374,10 @@ class ModelInfo(object): def decode_monitor_json(self, data): """Decode a bytestring that encodes JSON status data.""" - return json.loads(data.decode('utf8')) def decode_monitor(self, data): """Decode status data.""" - if self.binary_monitor_data: return self.decode_monitor_binary(data) else: From 82dbf607c19fcd0b3b85882b936afa67b6334e95 Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Sun, 30 Jun 2019 22:32:48 -0700 Subject: [PATCH 2/2] Removed length for BitValue namedtuples. --- tests/test_client.py | 12 ++++++------ wideq/client.py | 7 +------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 0e219c5..5d10eb0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -90,12 +90,12 @@ class ModelInfoTest(unittest.TestCase): def test_value_bit(self): actual = self.model_info.value('Option1') expected = BitValue({ - 0: {'length': 1, 'value': 'ChildLock'}, - 1: {'length': 1, 'value': 'ReduceStatic'}, - 2: {'length': 1, 'value': 'EasyIron'}, - 3: {'length': 1, 'value': 'DampDrySingal'}, - 4: {'length': 1, 'value': 'WrinkleCare'}, - 7: {'length': 1, 'value': 'AntiBacterial'}, + 0: 'ChildLock', + 1: 'ReduceStatic', + 2: 'EasyIron', + 3: 'DampDrySingal', + 4: 'WrinkleCare', + 7: 'AntiBacterial', }) self.assertEqual(expected, actual) diff --git a/wideq/client.py b/wideq/client.py index 2751588..4b1be67 100644 --- a/wideq/client.py +++ b/wideq/client.py @@ -323,12 +323,7 @@ class ModelInfo(object): d['option'].get('step', 1) ) elif d['type'].lower() == 'bit': - bit_values = { - opt['startbit']: { - 'length': opt['length'], - 'value': opt['value'], - } for opt in d['option'] - } + bit_values = {opt['startbit']: opt['value'] for opt in d['option']} return BitValue(bit_values) elif d['type'].lower() == 'reference': return ReferenceValue(d['option'][0])