Skip to content

Commit 90ba856

Browse files
authored
Merge pull request #22 from ElMoselYEE/main
2 parents 83c21d7 + 4c5f587 commit 90ba856

File tree

2 files changed

+65
-10
lines changed

2 files changed

+65
-10
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Class for interacting with the Ayla Networks Device API underlying the Shark IQ
8585
* `sign_out()`/`async_sign_out()` Sign out
8686

8787

88-
### `class SharkIqRobot(ayla_api, device_dct)`
88+
### `class SharkIqVacuum(ayla_api, device_dct)`
8989
Primary API for interacting with Shark IQ vacuums
9090
* `ayla_api: AylaApi` An `AylaApi` with an authenticated connection
9191
* `device_dct: Dict` A `dict` describing the device, usually obtained from `AylaApi.list_devices()`
@@ -110,6 +110,8 @@ Primary API for interacting with Shark IQ vacuums
110110
* `update()`/`async_update(property_list=None)` Fetch the updated robot state from the remote api
111111
* `property_list: Optional[Interable[str]]` An optional iterable of property names. If specified, only those
112112
properties will be updated.
113+
* `get_room_list()` Get a list of known room `str`s
114+
* `clean_rooms(List[str])` Start cleaning a subset of rooms
113115

114116
#### Properties
115117
* `ayla_api` The underlying `AylaApi` object
@@ -134,9 +136,9 @@ Primary API for interacting with Shark IQ vacuums
134136

135137

136138
### TODOs:
137-
* Add support for mapping and room selection
139+
* Add support for mapping
138140
* Once we have mapping, it may be possible to use the RSSI property combined with an increased update frequency
139141
to generate a wifi strength heatmap. Kind of orthogonal to the main purpose, but I really want to do this.
140142

141143
## License
142-
[MIT](https://choosealicense.com/licenses/mit/)
144+
[MIT](https://choosealicense.com/licenses/mit/)

sharkiq/sharkiq.py

+60-7
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class OperatingModes(enum.IntEnum):
5151
@enum.unique
5252
class Properties(enum.Enum):
5353
"""Useful properties"""
54+
AREAS_TO_CLEAN = "Areas_To_Clean"
5455
BATTERY_CAPACITY = "Battery_Capacity"
5556
CHARGING_STATUS = "Charging_Status"
5657
CLEAN_COMPLETE = "CleanComplete"
@@ -66,6 +67,7 @@ class Properties(enum.Enum):
6667
RECHARGE_RESUME = "Recharge_Resume"
6768
RECHARGING_TO_RESUME = "Recharging_To_Resume"
6869
ROBOT_FIRMWARE_VERSION = "Robot_Firmware_Version"
70+
ROBOT_ROOM_LIST = "Robot_Room_List"
6971
RSSI = "RSSI"
7072

7173

@@ -341,16 +343,67 @@ async def async_get_file_property(self, property_name: PropertyName) -> bytes:
341343
def _encode_room_list(self, rooms: List[str]):
342344
"""Base64 encode the list of rooms to clean"""
343345
if not rooms:
344-
raise ValueError('Room list must not be empty')
345-
if len(rooms) > 3:
346-
raise ValueError('At most three rooms may be given')
347-
# These are a mystery to me, but they seem constant
348-
header = b'\x80\x01\x0b\xca\x02'
349-
footer = b'\x1a\x08155B43C4'
346+
# By default, clean all rooms
347+
return '*'
348+
349+
room_list = self._get_device_room_list()
350+
_LOGGER.debug(f'Room list identifier is: {room_list["identifier"]}')
351+
352+
# Header explained:
353+
# 0x80: Control character - some mode selection
354+
# 0x01: Start of Heading Character
355+
# 0x0B: Use Line Tabulation (entries separated by newlines)
356+
# 0xca: Control character - purpose unknown
357+
# 0x02: Start of text (indicates start of room list)
358+
header = '\x80\x01\x0b\xca\x02'
359+
360+
# For each room in the list:
361+
# - Insert a byte representing the length of the room name string
362+
# - Add the room name
363+
# - Join with newlines (presumably because of the 0x0B in the header)
364+
rooms_enc = "\n".join([chr(len(room)) + room for room in rooms])
365+
366+
# The footer starts with control character 0x1A
367+
# Then add the length indicator for the room list identifier
368+
# Then add the room list identifier
369+
footer = '\x1a' + chr(len(room_list['identifier'])) + room_list['identifier']
370+
371+
# Now that we've computed the room list and footer and know their lengths, finish building the header
372+
# This character denotes the length of the remaining input
373+
header += chr(0
374+
+ 1 # Add one for a newline following the length specifier
375+
+ len(rooms_enc)
376+
+ len(footer)
377+
)
378+
header += '\n' # This is the newline reference above
379+
380+
# Finally, join and base64 encode the parts
381+
return base64.b64encode(
382+
# First encode the string as latin_1 to get the right endianness
383+
(header + rooms_enc + footer).encode('latin_1')
384+
# Then return as a utf8 string for ease of handling
385+
).decode('utf8')
386+
387+
def _get_device_room_list(self):
388+
"""Gets the list of known rooms from the device, including the map identifier"""
389+
room_list = self.get_property_value(Properties.ROBOT_ROOM_LIST)
390+
split = room_list.split(':')
391+
return {
392+
# The room list is preceded by an identifier, which I believe identifies the list of rooms with the
393+
# onboard map in the robot
394+
'identifier': split[0],
395+
'rooms': split[1:],
396+
}
397+
398+
def get_room_list(self) -> List[str]:
399+
"""Gets the list of rooms known by the device"""
400+
return self._get_device_room_list()['rooms']
350401

351402
def clean_rooms(self, rooms: List[str]) -> None:
352403
payload = self._encode_room_list(rooms)
353-
raise NotImplementedError
404+
_LOGGER.debug('Room list payload: ' + payload)
405+
self.set_property_value(Properties.AREAS_TO_CLEAN, payload)
406+
self.set_operating_mode(OperatingModes.START)
354407

355408

356409
class SharkPropertiesView(abc.Mapping):

0 commit comments

Comments
 (0)