overwrite sucks init to show changes

This commit is contained in:
bittles
2023-01-02 18:30:02 -05:00
parent 9a4481ae5f
commit 32ede1c978
+225 -29
View File
@@ -83,9 +83,10 @@ CLEAN_MODE_FROM_ECOVACS = {
'border': CLEAN_MODE_EDGE, 'border': CLEAN_MODE_EDGE,
'spot': CLEAN_MODE_SPOT, 'spot': CLEAN_MODE_SPOT,
'spot_area': CLEAN_MODE_SPOT_AREA, 'spot_area': CLEAN_MODE_SPOT_AREA,
'SpotArea': CLEAN_MODE_SPOT_AREA,
'singleroom': CLEAN_MODE_SINGLE_ROOM, 'singleroom': CLEAN_MODE_SINGLE_ROOM,
'stop': CLEAN_MODE_STOP, 'stop': CLEAN_MODE_STOP,
'going': CHARGE_MODE_RETURNING 'going': CHARGE_MODE_RETURNING,
} }
FAN_SPEED_TO_ECOVACS = { FAN_SPEED_TO_ECOVACS = {
@@ -95,32 +96,38 @@ FAN_SPEED_TO_ECOVACS = {
FAN_SPEED_FROM_ECOVACS = { FAN_SPEED_FROM_ECOVACS = {
'standard': FAN_SPEED_NORMAL, 'standard': FAN_SPEED_NORMAL,
'strong': FAN_SPEED_HIGH 'strong': FAN_SPEED_HIGH,
} }
CHARGE_MODE_TO_ECOVACS = { CHARGE_MODE_TO_ECOVACS = {
CHARGE_MODE_RETURN: 'go', CHARGE_MODE_RETURN: 'go',
CHARGE_MODE_RETURNING: 'Going', CHARGE_MODE_RETURNING: 'Going',
CHARGE_MODE_CHARGING: 'SlotCharging', CHARGE_MODE_CHARGING: 'SlotCharging',
CHARGE_MODE_IDLE: 'Idle' CHARGE_MODE_IDLE: 'Idle',
} }
CHARGE_MODE_FROM_ECOVACS = { CHARGE_MODE_FROM_ECOVACS = {
'going': CHARGE_MODE_RETURNING, 'going': CHARGE_MODE_RETURNING,
'Going': CHARGE_MODE_RETURNING,
'slot_charging': CHARGE_MODE_CHARGING, 'slot_charging': CHARGE_MODE_CHARGING,
'idle': CHARGE_MODE_IDLE 'SlotCharging': CHARGE_MODE_CHARGING,
'idle': CHARGE_MODE_IDLE,
'Idle': CHARGE_MODE_IDLE,
} }
COMPONENT_TO_ECOVACS = { COMPONENT_TO_ECOVACS = {
COMPONENT_MAIN_BRUSH: 'Brush', COMPONENT_MAIN_BRUSH: 'Brush',
COMPONENT_SIDE_BRUSH: 'SideBrush', COMPONENT_SIDE_BRUSH: 'SideBrush',
COMPONENT_FILTER: 'DustCaseHeap' COMPONENT_FILTER: 'DustCaseHeap',
} }
COMPONENT_FROM_ECOVACS = { COMPONENT_FROM_ECOVACS = {
'brush': COMPONENT_MAIN_BRUSH, 'brush': COMPONENT_MAIN_BRUSH,
'Brush': COMPONENT_MAIN_BRUSH,
'side_brush': COMPONENT_SIDE_BRUSH, 'side_brush': COMPONENT_SIDE_BRUSH,
'dust_case_heap': COMPONENT_FILTER 'SideBrush': COMPONENT_SIDE_BRUSH,
'dust_case_heap': COMPONENT_FILTER,
'DustCaseHeap': COMPONENT_FILTER,
} }
def str_to_bool_or_cert(s): def str_to_bool_or_cert(s):
@@ -376,10 +383,13 @@ class EventListener(object):
self._emitter.unsubscribe(self) self._emitter.unsubscribe(self)
class VacBot(): class VacBot():
def __init__(self, user, domain, resource, secret, vacuum, continent, server_address=None, monitor=False, verify_ssl=True): # switched verify and monitor just to be consistent
def __init__(self, user, domain, resource, secret, vacuum, continent, server_address=None, verify_ssl=True, monitor=False):
self.vacuum = vacuum self.vacuum = vacuum
self.server_address = server_address
# If True, the VacBot object will handle keeping track of all statuses, # If True, the VacBot object will handle keeping track of all statuses,
# including the initial request for statuses, and new requests after the # including the initial request for statuses, and new requests after the
# VacBot returns from being offline. It will also cause it to regularly # VacBot returns from being offline. It will also cause it to regularly
@@ -410,10 +420,18 @@ class VacBot():
self.iotmq = None self.iotmq = None
if not vacuum['iotmq']: if not vacuum['iotmq']:
self.xmpp = EcoVacsXMPP(user, domain, resource, secret, continent, vacuum, server_address) # if server is defined then use bmartins init example for using sucks library in his docs; couldnt get this to work in hass with code he had here though, maybe not referencing everything right in component init
#Uncomment line to allow unencrypted plain auth if self.server_address is not None:
#self.xmpp['feature_mechanisms'].unencrypted_plain = True vacuum = {"did": "none", "class": "none"}
self.xmpp.subscribe_to_ctls(self._handle_ctl) # super().__init__("sucks", "ecouser.net", "", "", vacuum, "")
self.xmpp = EcoVacsXMPP("sucks", "ecouser.net", "", "", "", vacuum, server_address)
self.xmpp.subscribe_to_ctls(self._handle_ctl)
# should work with ecovacs servers but 1) havent tested with my changes and 2) havent tested with bmartins changes
else:
self.xmpp = EcoVacsXMPP(user, domain, resource, secret, continent, vacuum, server_address)
#Uncomment line to allow unencrypted plain auth
#self.xmpp['feature_mechanisms'].unencrypted_plain = True
self.xmpp.subscribe_to_ctls(self._handle_ctl)
else: else:
self.iotmq = EcoVacsIOTMQ(user, domain, resource, secret, continent, vacuum, server_address, verify_ssl=verify_ssl) self.iotmq = EcoVacsIOTMQ(user, domain, resource, secret, continent, vacuum, server_address, verify_ssl=verify_ssl)
@@ -426,24 +444,37 @@ class VacBot():
#self.xmpp.subscribe_to_ctls(self._handle_ctl) #self.xmpp.subscribe_to_ctls(self._handle_ctl)
def connect_and_wait_until_ready(self): def connect_and_wait_until_ready(self):
if not self.vacuum['iotmq']: # use bmartins exmaple if defining our own server, couldn't get this to work without defining, probably xmpp port but idk
if self.server_address:
logging.info("connecting")
# self.xmpp.connect(self.server_address)
# self.xmpp.process()
self.xmpp.connect_and_wait_until_ready() self.xmpp.connect_and_wait_until_ready()
self.xmpp.schedule('Ping', 30, lambda: self.send_ping(), repeat=True) self.xmpp.schedule('Ping', 30, lambda: self.send_ping(), repeat=True)
# keep rest of bmartins fork intact
else: else:
self.iotmq.connect_and_wait_until_ready() if not self.vacuum['iotmq']:
self.iotmq.schedule(30, self.send_ping) self.xmpp.connect_and_wait_until_ready()
#self.xmpp.connect_and_wait_until_ready() #Leaving in case xmpp is given to iotmq in the future self.xmpp.schedule('Ping', 30, lambda: self.send_ping(), repeat=True)
if self._monitor:
# Do a first ping, which will also fetch initial statuses if the ping succeeds
self.send_ping()
if not self.vacuum['iotmq']:
self.xmpp.schedule('Components', 3600, lambda: self.refresh_components(), repeat=True)
else: else:
self.iotmq.schedule(3600,self.refresh_components) self.iotmq.connect_and_wait_until_ready()
self.iotmq.schedule(30, self.send_ping)
#self.xmpp.connect_and_wait_until_ready() #Leaving in case xmpp is given to iotmq in the future
if self._monitor:
# Do a first ping, which will also fetch initial statuses if the ping succeeds
self.send_ping()
if not self.vacuum['iotmq']:
self.xmpp.schedule('Components', 3600, lambda: self.refresh_components(), repeat=True)
else:
self.iotmq.schedule(3600,self.refresh_components)
def _handle_ctl(self, ctl): def _handle_ctl(self, ctl):
# _LOGGER.debug("super handle_ctl called with ctl:")
# _LOGGER.debug(ctl)
method = '_handle_' + ctl['event'] method = '_handle_' + ctl['event']
# _LOGGER.debug("method assigned:")
# _LOGGER.debug(method)
if hasattr(self, method): if hasattr(self, method):
getattr(self, method)(ctl) getattr(self, method)(ctl)
@@ -458,7 +489,17 @@ class VacBot():
_LOGGER.debug("*** error = " + error) _LOGGER.debug("*** error = " + error)
def _handle_life_span(self, event): def _handle_life_span(self, event):
# _LOGGER.debug("_handle_life_span called, event is: ")
# _LOGGER.debug(event)
# _LOGGER.debug("event shown now continue with handle life span")
type = event['type'] type = event['type']
# _LOGGER.debug("type in handle life span: ")
# _LOGGER.debug(type)
# _LOGGER.debug("type shown now continue with handle life span")
try: try:
type = COMPONENT_FROM_ECOVACS[type] type = COMPONENT_FROM_ECOVACS[type]
except KeyError: except KeyError:
@@ -466,6 +507,7 @@ class VacBot():
if 'val' in event: if 'val' in event:
lifespan = int(event['val']) / 100 lifespan = int(event['val']) / 100
_LOGGER.debug("**********Component " + type + " has lifespan of " + str(lifespan) + ".")
else: else:
lifespan = int(event['left']) / 60 #This works for a D901 lifespan = int(event['left']) / 60 #This works for a D901
self.components[type] = lifespan self.components[type] = lifespan
@@ -868,7 +910,7 @@ class EcoVacsIOTMQ(ClientMQTT):
if not RepresentsInt(result[key]) and ',' not in result[key]: if not RepresentsInt(result[key]) and ',' not in result[key]:
result[key] = stringcase.snakecase(result[key]) result[key] = stringcase.snakecase(result[key])
return result return result
class EcoVacsXMPP(ClientXMPP): class EcoVacsXMPP(ClientXMPP):
@@ -904,27 +946,180 @@ class EcoVacsXMPP(ClientXMPP):
self.ctl_subscribers.append(function) self.ctl_subscribers.append(function)
def _handle_ctl(self, message): def _handle_ctl(self, message):
# _LOGGER.debug("message in handle_ctl is:")
# _LOGGER.debug(message)
# the_good_part = str(message.payload.decode("utf-8"))
# the_good_part = message.get_payload()[0][0]
the_good_part = message.get_payload()[0][0] the_good_part = message.get_payload()[0][0]
# _LOGGER.debug("the_good_part in handle_ctl is :")
# _LOGGER.debug(the_good_part)
# the_other_part = None
# try:
# the_other_part = message.get_payload()[0][0][0]
# _LOGGER.debug("Other payload found:")
# _LOGGER.debug(the_other_part)
# except IndexError:
# _LOGGER.debug("No extra payload")
as_dict = self._ctl_to_dict(the_good_part) as_dict = self._ctl_to_dict(the_good_part)
# _LOGGER.debug("handle ctl called with as_dict:")
# _LOGGER.debug(as_dict)
if as_dict is not None: if as_dict is not None:
for s in self.ctl_subscribers: for s in self.ctl_subscribers:
s(as_dict) s(as_dict)
# if as_dict is None:
# try:
# other_part = message.get_payload()[0][0][0]
# # _LOGGER.debug("handle_ctl called with get_payload()[0][0][0], the other part:")
# #_LOGGER.debug(other_part)
# other_dict = self._ctl_to_dict(other_part)
# #_LOGGER.debug("other dict in query:")
# #_LOGGER.debug(other_dict)
# if other_dict is not None:
# for s in self.ctl_subscribers:
# s(other_dict)
# except IndexError:
# _LOGGER.debug("No extra payload")
def _ctl_to_dict(self, xml): def _ctl_to_dict(self, xml):
#Including changes from jasonarends @ 28da7c2 below
result = xml.attrib.copy() result = xml.attrib.copy()
# _LOGGER.debug("result is:")
# _LOGGER.debug(result)
# if other_xml is not None:
# other_result = other_xml.attrib.copy()
# _LOGGER.debug("other result:")
# _LOGGER.debug(other_result)
# _LOGGER.debug(xml[0].tag)
if 'td' not in result: if 'td' not in result:
# _LOGGER.debug("td not in result:")
# _LOGGER.debug(result)
# This happens for commands with no response data, such as PlaySound # This happens for commands with no response data, such as PlaySound
return # Handle response data with no 'td'
result['event'] = result.pop('td') if 'type' in result: # single element with type and val
if xml: # _LOGGER.debug("type detected in result, result before event handling:")
result.update(xml[0].attrib) # _LOGGER.debug(result)
result['event'] = "LifeSpan" # seems to always be LifeSpan type
# result['event'] = "life_span" # seems to always be LifeSpan type
# _LOGGER.debug("result after event LifeSpan handling:")
# _LOGGER.debug(result)
else:
if xml[0] is not None:
# if other_xml is not None: # case where there is child element
# _LOGGER.debug("child xml detected, [0] tag is")
# _LOGGER.debug(xml[0].tag)
if 'clean' in xml[0].tag:
# _LOGGER.debug("clean detected in xml[0].tag, result before event handling:")
# _LOGGER.debug(result)
result['event'] = "CleanReport"
# result['event'] = "clean_report"
# _LOGGER.debug("result after event clean handling:")
# _LOGGER.debug(result)
elif 'charge' in xml[0].tag:
# _LOGGER.debug("charge detected in xml[0].tag, result before event handling:")
# _LOGGER.debug(result)
result['event'] = "ChargeState"
# result['event'] = "charge_state"
# _LOGGER.debug("result after event charge handling:")
# _LOGGER.debug(result)
elif 'battery' in xml[0].tag:
# _LOGGER.debug("battery detected in xml[0].tag, result before event handling:")
# _LOGGER.debug(result)
result['event'] = "BatteryInfo"
# result['event'] = "battery_info"
# _LOGGER.debug("result after event battery handling:")
# _LOGGER.debug(result)
else:
# _LOGGER.warning("other payload detected but didn't catch on any checks, result is: ")
# _LOGGER.debug(result)
return
result.update(xml[0].attrib)
# _LOGGER.debug("result after xml update attrib:")
# _LOGGER.debug(result)
else: # for non-'type' result with no child element, e.g., result of PlaySound
# _LOGGER.warning("payload didn't catch on any checks, result is: ")
# _LOGGER.debug(result)
return
else: # response includes 'td'
# _LOGGER.debug("td detected in result, result before event handling:")
# _LOGGER.debug(result)
result['event'] = result.pop('td')
# _LOGGER.debug("result after event td handling:")
# _LOGGER.debug(result)
if xml:
result.update(xml[0].attrib)
# _LOGGER.debug("IF XML sub-check result after xml update attrib:")
# _LOGGER.debug(result)
for key in result: for key in result:
if not RepresentsInt(result[key]): #Fix to handle negative int values #Check for RepresentInt to handle negative int values, and ',' for ignoring position updates
if not RepresentsInt(result[key]) and ',' not in result[key]:
result[key] = stringcase.snakecase(result[key]) result[key] = stringcase.snakecase(result[key])
return result return result
#
# result = xml.attrib.copy()
# other_result = other_xml.attrib.copy()
# _LOGGER.debug("result from xml is :")
# _LOGGER.debug(result)
# _LOGGER.debug("end of result")
# _LOGGER.debug("other_result from xml is :")
# _LOGGER.debug(other_result)
# _LOGGER.debug("end of other_result")
# if 'td' in result:
# result['event'] = result.pop('td')
# if xml:
# result.update(xml[0].attrib)
# for key in result:
# if not RepresentsInt(result[key]): #Fix to handle negative int values
# result[key] = stringcase.snakecase(result[key])
# _LOGGER.debug("td detected in result and result is:")
# _LOGGER.debug(result)
# _LOGGER.debug("end of td detect result")
# return result
# elif 'type' in result:
# result['event'] = result.pop('type')
# if 'errno' in result:
# if result['errno'] == '':
# result['errno'] = 'life_span'
# result['event'] = result.pop('errno')
# if xml:
# result.update(xml[0].attrib)
# else:
# result['event'] = result.pop('type')
# if xml:
# result.update(xml[0].attrib)
# for key in result:
# if not RepresentsInt(result[key]): #Fix to handle negative int values
# result[key] = stringcase.snakecase(result[key])
# _LOGGER.debug("type detected in result and result is:")
# _LOGGER.debug(result)
# _LOGGER.debug("end of type detect result")
# return result
# elif 'type' in other_result:
# if len(xmlchild) > 0:
# result = xmlchild[0].attrib.copy()
# #Fix for difference in XMPP vs API response
# #Depending on the report will use the tag and add "report" to fit the mold of sucks library
# if xmlchild[0].tag == "clean":
# result['event'] = "CleanReport"
# elif xmlchild[0].tag == "charge":
# result['event'] = "ChargeState"
# elif xmlchild[0].tag == "battery":
# result['event'] = "BatteryInfo"
# else:
# # This happens for commands with no response data, such as PlaySound
# _LOGGER.debug("neither type nor td in result:")
# _LOGGER.debug(result)
# _LOGGER.debug("end of no td or type detect result")
# return
def register_callback(self, userdata, message): def register_callback(self, userdata, message):
@@ -1088,6 +1283,7 @@ class GetBatteryState(VacBotCommand):
class GetLifeSpan(VacBotCommand): class GetLifeSpan(VacBotCommand):
def __init__(self, component): def __init__(self, component):
# _LOGGER.debug("GetLifeSpan called by VacBot**************")
super().__init__('GetLifeSpan', {'type': COMPONENT_TO_ECOVACS[component]}) super().__init__('GetLifeSpan', {'type': COMPONENT_TO_ECOVACS[component]})