mirror of
https://github.com/no2chem/wideq.git
synced 2025-05-18 08:10:17 -07:00
Merge pull request #26 from boralyl/feature/add-additional-types
Adds additional types returned when monitoring an LG dryer.
This commit is contained in:
commit
1e8e51c334
110
tests/test_client.py
Normal file
110
tests/test_client.py
Normal file
@ -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')
|
@ -293,8 +293,10 @@ class DeviceInfo(object):
|
|||||||
return requests.get(self.model_info_url).json()
|
return requests.get(self.model_info_url).json()
|
||||||
|
|
||||||
|
|
||||||
|
BitValue = namedtuple('BitValue', ['options'])
|
||||||
EnumValue = namedtuple('EnumValue', ['options'])
|
EnumValue = namedtuple('EnumValue', ['options'])
|
||||||
RangeValue = namedtuple('RangeValue', ['min', 'max', 'step'])
|
RangeValue = namedtuple('RangeValue', ['min', 'max', 'step'])
|
||||||
|
ReferenceValue = namedtuple('ReferenceValue', ['reference'])
|
||||||
|
|
||||||
|
|
||||||
class ModelInfo(object):
|
class ModelInfo(object):
|
||||||
@ -304,31 +306,38 @@ class ModelInfo(object):
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def value(self, name):
|
def value(self, name: str):
|
||||||
"""Look up information about a value.
|
"""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]
|
d = self.data['Value'][name]
|
||||||
if d['type'] in ('Enum', 'enum'):
|
if d['type'] in ('Enum', 'enum'):
|
||||||
return EnumValue(d['option'])
|
return EnumValue(d['option'])
|
||||||
elif d['type'] == 'Range':
|
elif d['type'] == 'Range':
|
||||||
return RangeValue(
|
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:
|
else:
|
||||||
assert False, "unsupported value type {}".format(d['type'])
|
raise ValueError("unsupported value type {}".format(d['type']))
|
||||||
|
|
||||||
def default(self, name):
|
def default(self, name):
|
||||||
"""Get the default value, if it exists, for a given value.
|
"""Get the default value, if it exists, for a given value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.data['Value'][name]['default']
|
return self.data['Value'][name]['default']
|
||||||
|
|
||||||
def enum_value(self, key, name):
|
def enum_value(self, key, name):
|
||||||
"""Look up the encoded value for a friendly enum name.
|
"""Look up the encoded value for a friendly enum name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
options = self.value(key).options
|
options = self.value(key).options
|
||||||
options_inv = {v: k for k, v in options.items()} # Invert the map.
|
options_inv = {v: k for k, v in options.items()} # Invert the map.
|
||||||
return options_inv[name]
|
return options_inv[name]
|
||||||
@ -336,7 +345,6 @@ class ModelInfo(object):
|
|||||||
def enum_name(self, key, value):
|
def enum_name(self, key, value):
|
||||||
"""Look up the friendly enum name for an encoded value.
|
"""Look up the friendly enum name for an encoded value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
options = self.value(key).options
|
options = self.value(key).options
|
||||||
return options[value]
|
return options[value]
|
||||||
|
|
||||||
@ -344,13 +352,11 @@ class ModelInfo(object):
|
|||||||
def binary_monitor_data(self):
|
def binary_monitor_data(self):
|
||||||
"""Check that type of monitoring is BINARY(BYTE).
|
"""Check that type of monitoring is BINARY(BYTE).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.data['Monitoring']['type'] == 'BINARY(BYTE)'
|
return self.data['Monitoring']['type'] == 'BINARY(BYTE)'
|
||||||
|
|
||||||
def decode_monitor_binary(self, data):
|
def decode_monitor_binary(self, data):
|
||||||
"""Decode binary encoded status data.
|
"""Decode binary encoded status data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
decoded = {}
|
decoded = {}
|
||||||
for item in self.data['Monitoring']['protocol']:
|
for item in self.data['Monitoring']['protocol']:
|
||||||
key = item['value']
|
key = item['value']
|
||||||
@ -363,12 +369,10 @@ class ModelInfo(object):
|
|||||||
|
|
||||||
def decode_monitor_json(self, data):
|
def decode_monitor_json(self, data):
|
||||||
"""Decode a bytestring that encodes JSON status data."""
|
"""Decode a bytestring that encodes JSON status data."""
|
||||||
|
|
||||||
return json.loads(data.decode('utf8'))
|
return json.loads(data.decode('utf8'))
|
||||||
|
|
||||||
def decode_monitor(self, data):
|
def decode_monitor(self, data):
|
||||||
"""Decode status data."""
|
"""Decode status data."""
|
||||||
|
|
||||||
if self.binary_monitor_data:
|
if self.binary_monitor_data:
|
||||||
return self.decode_monitor_binary(data)
|
return self.decode_monitor_binary(data)
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user