Skip to content

Commit

Permalink
Implemented dishwasher APiv2
Browse files Browse the repository at this point in the history
- Implemented dishwasher APiv2
- Revised (again) model information parsing
- Added missing states
  • Loading branch information
ollo69 committed May 3, 2020
1 parent 8c2ce8c commit 719b9d2
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 158 deletions.
3 changes: 2 additions & 1 deletion custom_components/smartthinq_sensors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ def __init__(self, device, name):

self._device = device
self._name = name
self._state = device.status
self._device_id = device.device_info.id
self._type = device.device_info.type
self._mac = device.device_info.macaddress
Expand All @@ -228,6 +227,7 @@ def __init__(self, device, name):
self._model = f"{device.device_info.model_name}"
self._id = f"{self._type.name}:{self._device_id}"

self._state = None
self._retry_count = 0
self._disconnected = True
self._not_logged = False
Expand Down Expand Up @@ -276,6 +276,7 @@ def device_info(self):

def init_device(self):
self._device.init_device_info()
self._state = self._device.status
self._model = f"{self._model}-{self._device.model_info.model_type}"

def _restart_monitor(self):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/smartthinq_sensors/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Support to interface with LGE ThinQ Devices.
"""

__version__ = "0.3.9"
__version__ = "0.4.2"
PROJECT_URL = "https://github.com/ollo69/ha-smartthinq-sensors/"
ISSUE_URL = "{}issues".format(PROJECT_URL)

Expand Down
68 changes: 38 additions & 30 deletions custom_components/smartthinq_sensors/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
ATTR_ICON: None,
ATTR_UNIT_FN: lambda x: None,
ATTR_DEVICE_CLASS: DEVICE_CLASS_OPENING,
ATTR_VALUE_FN: lambda x: x._door_open_state,
ATTR_VALUE_FN: lambda x: x._dooropen_state,
ATTR_ENABLED_FN: lambda x: True,
},
}
Expand Down Expand Up @@ -754,20 +754,21 @@ def device_state_attributes(self):
ATTR_RUN_COMPLETED: self._run_completed,
ATTR_ERROR_STATE: self._error_state,
ATTR_ERROR_MSG: self._error_msg,
ATTR_DOOROPEN_STATE: self._dooropen_state,
ATTR_RINSEREFILL_STATE: self._rinserefill_state,
ATTR_SALTREFILL_STATE: self._saltrefill_state,
ATTR_RUN_STATE: self._current_run_state,
ATTR_PROCESS_STATE: self._process_state,
ATTR_CURRENT_COURSE: self._current_course,
ATTR_TUBCLEAN_COUNT: self._tubclean_count,
ATTR_REMAIN_TIME: self._remain_time,
ATTR_INITIAL_TIME: self._initial_time,
ATTR_RESERVE_TIME: self._reserve_time,
ATTR_DOORLOCK_MODE: self._doorlock_mode,
ATTR_HALFLOAD_MODE: self._halfload_mode,
ATTR_CHILDLOCK_MODE: self._childlock_mode,
ATTR_DELAYSTART_MODE: self._delaystart_mode,
ATTR_ENERGYSAVER_MODE: self._energysaver_mode,
ATTR_DUALZONE_MODE: self._dualzone_mode,
ATTR_HALFLOAD_MODE: self._halfload_mode,
ATTR_RINSEREFILL_STATE: self._rinserefill_state,
ATTR_SALTREFILL_STATE: self._saltrefill_state,
}
return data

Expand Down Expand Up @@ -849,9 +850,37 @@ def _error_msg(self):
return "-"

@property
def _doorlock_mode(self):
def _tubclean_count(self):
if self._api.state:
mode = self._api.state.doorlock_state
tubclean_count = self._api.state.tubclean_count
return tubclean_count
return "N/A"

@property
def _dooropen_state(self):
if self._api.state:
state = self._api.state.door_opened_state
return STATE_LOOKUP.get(state, STATE_OFF)
return None

@property
def _rinserefill_state(self):
if self._api.state:
state = self._api.state.rinserefill_state
return STATE_LOOKUP.get(state, STATE_OFF)
return STATE_OFF

@property
def _saltrefill_state(self):
if self._api.state:
state = self._api.state.saltrefill_state
return STATE_LOOKUP.get(state, STATE_OFF)
return STATE_OFF

@property
def _halfload_mode(self):
if self._api.state:
mode = self._api.state.halfload_state
return mode
return None

Expand Down Expand Up @@ -883,27 +912,6 @@ def _dualzone_mode(self):
return mode
return None

@property
def _halfload_mode(self):
if self._api.state:
mode = self._api.state.halfload_state
return mode
return None

@property
def _rinserefill_state(self):
if self._api.state:
state = self._api.state.rinserefill_state
return STATE_LOOKUP.get(state, STATE_OFF)
return STATE_OFF

@property
def _saltrefill_state(self):
if self._api.state:
state = self._api.state.saltrefill_state
return STATE_LOOKUP.get(state, STATE_OFF)
return STATE_OFF


class LGERefrigeratorSensor(LGESensor):
"""A sensor to monitor LGE Refrigerator devices"""
Expand All @@ -918,7 +926,7 @@ def device_state_attributes(self):
ATTR_REFRIGERATOR_TEMP: self._temp_refrigerator,
ATTR_FREEZER_TEMP: self._temp_freezer,
ATTR_TEMP_UNIT: self._temp_unit,
ATTR_DOOROPEN_STATE: self._door_open_state,
ATTR_DOOROPEN_STATE: self._dooropen_state,
ATTR_SMARTSAVING_MODE: self._smart_saving_mode,
ATTR_SMARTSAVING_STATE: self._smart_saving_state,
ATTR_ECOFRIENDLY_STATE: self._eco_friendly_state,
Expand Down Expand Up @@ -948,7 +956,7 @@ def _temp_unit(self):
return TEMP_CELSIUS

@property
def _door_open_state(self):
def _dooropen_state(self):
if self._api.state:
state = self._api.state.door_opened_state
return STATE_LOOKUP.get(state, STATE_OFF)
Expand Down
123 changes: 70 additions & 53 deletions custom_components/smartthinq_sensors/wideq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

from .core_exceptions import MonitorError

BIT_OFF_THINQ2 = "@CP_OFF_EN_W"
BIT_ON_THINQ2 = "@CP_ON_EN_W"
LABEL_BIT_OFF = "@CP_OFF_EN_W"
LABEL_BIT_ON = "@CP_ON_EN_W"

DEFAULT_TIMEOUT = 10 # seconds
DEFAULT_REFRESH_TIMEOUT = 20 # seconds
Expand Down Expand Up @@ -250,7 +250,7 @@ def __init__(self, data):
self._bit_keys = {}

@property
def is_api_v2(self):
def is_info_v2(self):
return False

@property
Expand Down Expand Up @@ -320,7 +320,7 @@ def enum_name(self, key, value):
if not hasattr(values, "options"):
return None
options = values.options
return options.get(value)
return options.get(value, "")

def range_name(self, key):
"""Look up the value of a RangeValue. Not very useful other than for comprehension
Expand Down Expand Up @@ -355,11 +355,13 @@ def search_bit_key(key, data):
for opt in option.get("option", []):
if key == opt.get("value", ""):
start_bit = opt.get("startbit")
length = opt.get("length", 1)
if start_bit is None:
return {}
return {
"option": opt_key,
"startbit": start_bit,
"length": length,
}
return {}

Expand All @@ -379,15 +381,16 @@ def bit_value(self, key, values):
return None
value = None if not values else values.get(bit_key["option"])
if not value:
return False
return "0"
bit_value = int(value)
start_bit = bit_key["startbit"]
bit_index = 2 ** start_bit
mode = bin(bit_value & bit_index)
if mode == bin(0):
return False
else:
return True
length = bit_key["length"]
val = 0
for i in range(0, length):
bit_index = 2 ** (start_bit + i)
bit = 1 if bit_value & bit_index else 0
val += bit * (2 ** i)
return str(val)

def reference_name(self, key, value, get_comment=True):
"""Look up the friendly name for an encoded reference value
Expand Down Expand Up @@ -440,7 +443,19 @@ def decode_monitor(self, data):

def decode_snapshot(self, data, key):
"""Decode status data."""
return {}
decoded = {}
if self._data["Monitoring"]["type"] != "THINQ2":
return decoded
info = data.get(key)
if not info:
return decoded
protocol = self._data["Monitoring"]["protocol"]
for data_key, value_key in protocol.items():
value = info.get(data_key, "")
if value is not None and isinstance(value, Number):
value = int(value)
decoded[value_key] = str(value)
return decoded


class ModelInfoV2(object):
Expand All @@ -451,7 +466,7 @@ def __init__(self, data):
self._data = data

@property
def is_api_v2(self):
def is_info_v2(self):
return True

@property
Expand Down Expand Up @@ -535,7 +550,7 @@ def enum_name(self, key, value):

options = self.value(data)
item = options.get(value, {})
return item.get("label")
return item.get("label", "")

def range_name(self, key):
"""Look up the value of a RangeValue. Not very useful other than for comprehension
Expand All @@ -549,19 +564,8 @@ def bit_name(self, key, bit_index, value):

def bit_value(self, key, value):
"""Look up the bit value for an specific key
"""
data = self.data_root(key)
if not data:
return None
if not value:
return False

bit_val = self.value(data)[value].get("label", "")
if bit_val == BIT_OFF_THINQ2:
return False
elif bit_val == BIT_ON_THINQ2:
return True

Not used in model V2
"""
return None

def reference_name(self, key, value, get_comment=True):
Expand Down Expand Up @@ -636,6 +640,7 @@ def __init__(self, client, device, status=None):
self._client = client
self._device_info = device
self._status = status
self._model_data = None
self._model_info = None
self._should_poll = device.platform_type == PlatformType.THINQ1

Expand Down Expand Up @@ -691,11 +696,16 @@ def _get_control(self, key):

def init_device_info(self):
if self._model_info is None:
model_data = self._client.model_info(self._device_info)
if self._device_info.platform_type == PlatformType.THINQ2:
self._model_info = ModelInfoV2(model_data)
else:
if self._model_data is None:
self._model_data = self._client.model_info(self._device_info)

model_data = self._model_data
if model_data.get("Monitoring") and model_data.get("Value"):
self._model_info = ModelInfo(model_data)
elif model_data.get("MonitoringValue"):
self._model_info = ModelInfoV2(model_data)

return self._model_info is not None

def monitor_start(self):
"""Start monitoring the device's status."""
Expand Down Expand Up @@ -725,7 +735,8 @@ def device_poll(self, snapshot_key=""):
"""

# load device info at first call if not loaded before
self.init_device_info()
if not self.init_device_info():
return None

# ThinQ V2 - Monitor data is with device info
if not self._should_poll:
Expand Down Expand Up @@ -781,8 +792,8 @@ def has_data(self):
return self._data is not None

@property
def is_api_v2(self):
return self._device.model_info.is_api_v2
def is_info_v2(self):
return self._device.model_info.is_info_v2

def _get_data_key(self, keys):
if not self._data:
Expand Down Expand Up @@ -826,27 +837,33 @@ def lookup_reference(self, key, get_comment=True):
curr_key, self._data[curr_key], get_comment
)

def lookup_bit(self, key):
if self.is_api_v2:
def lookup_bit_enum(self, key):
if not self._data:
str_val = ""
else:
str_val = self._data.get(key)
if not str_val:
str_val = self._device.model_info.bit_value(
key, self._data
)

if str_val is None:
return None
ret_val = self._device.model_info.enum_name(key, str_val)

result = self._device.model_info.bit_value(
key, self._data
)
if result is None:
return STATE_OPTIONITEM_NONE
return STATE_OPTIONITEM_ON if result else STATE_OPTIONITEM_OFF
# exception because doorlock bit
# is not inside the model enum
if key == "DoorLock" and ret_val is None:
if str_val == "1":
return LABEL_BIT_ON
return LABEL_BIT_OFF

def lookup_bit_v2(self, key):
if not self.is_api_v2:
return None
return ret_val

value_key = self._get_data_key(key)
if not value_key:
return STATE_OPTIONITEM_NONE
result = self._device.model_info.bit_value(
value_key, self._data[value_key]
)
if result is None:
def lookup_bit(self, key):
enum_val = self.lookup_bit_enum(key)
if enum_val is None:
return STATE_OPTIONITEM_NONE
return STATE_OPTIONITEM_ON if result else STATE_OPTIONITEM_OFF
if enum_val == LABEL_BIT_ON:
return STATE_OPTIONITEM_ON
return STATE_OPTIONITEM_OFF
Loading

0 comments on commit 719b9d2

Please sign in to comment.