diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..5d10eb0 --- /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: 'ChildLock', + 1: 'ReduceStatic', + 2: 'EasyIron', + 3: 'DampDrySingal', + 4: 'WrinkleCare', + 7: '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..4b1be67 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,38 @@ 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']: 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 +345,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 +352,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 +369,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: