diff --git a/pymammotion/data/model/hash_list.py b/pymammotion/data/model/hash_list.py index 1536555..7f7a5b8 100644 --- a/pymammotion/data/model/hash_list.py +++ b/pymammotion/data/model/hash_list.py @@ -3,7 +3,7 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin -from pymammotion.proto.mctrl_nav import AreaHashName, NavGetCommDataAck +from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck class PathType(IntEnum): @@ -20,6 +20,12 @@ class FrameList(DataClassORJSONMixin): data: list[NavGetCommDataAck] +@dataclass +class RootHashList(DataClassORJSONMixin): + total_frame: int + data: list[NavGetHashListAck] + + @dataclass class AreaHashNameList(DataClassORJSONMixin): """Wrapper so we can serialize to and from dict.""" @@ -35,18 +41,38 @@ class HashList(DataClassORJSONMixin): hashlist for all our hashIDs for verification """ + root_hash_list: RootHashList = field(default_factory=RootHashList) area: dict = field(default_factory=dict) # type 0 path: dict = field(default_factory=dict) # type 2 obstacle: dict = field(default_factory=dict) # type 1 - hashlist: list[int] = field(default_factory=list) area_name: list[AreaHashNameList] = field(default_factory=list) - def set_hashlist(self, hashlist: list[int]) -> None: - self.hashlist = hashlist + def update_hash_lists(self, hashlist: list[int]) -> None: self.area = {hash_id: frames for hash_id, frames in self.area.items() if hash_id in hashlist} self.path = {hash_id: frames for hash_id, frames in self.path.items() if hash_id in hashlist} self.obstacle = {hash_id: frames for hash_id, frames in self.obstacle.items() if hash_id in hashlist} + @property + def hashlist(self) -> list[int]: + return [i for obj in self.root_hash_list.data for i in obj.data_couple] + + def update_root_hash_list(self, hash_list: NavGetHashListAck) -> None: + self.root_hash_list.total_frame = hash_list.total_frame + + for index, obj in enumerate(self.root_hash_list.data): + if obj.current_frame == hash_list.current_frame: + # Replace the item if current_frame matches + self.root_hash_list.data[index] = hash_list + self.update_hash_lists(self.hashlist) + return + + # If no match was found, append the new item + self.root_hash_list.data.append(hash_list) + self.update_hash_lists(self.hashlist) + + def missing_hash_frame(self): + return self._find_missing_frames(self.root_hash_list) + def missing_frame(self, hash_data: NavGetCommDataAck) -> list[int]: if hash_data.type == PathType.AREA: return self._find_missing_frames(self.area.get(hash_data.hash)) @@ -62,7 +88,7 @@ def update(self, hash_data: NavGetCommDataAck) -> bool: if hash_data.type == PathType.AREA: existing_name = next((area for area in self.area_name if area.hash == hash_data.hash), None) if not existing_name: - self.area_name.append(AreaHashName(name=f"area {len(self.area_name)+1}", hash=hash_data.hash)) + self.area_name.append(AreaHashNameList(name=f"area {len(self.area_name)+1}", hash=hash_data.hash)) return self._add_hash_data(self.area, hash_data) if hash_data.type == PathType.OBSTACLE: @@ -72,7 +98,7 @@ def update(self, hash_data: NavGetCommDataAck) -> bool: return self._add_hash_data(self.path, hash_data) @staticmethod - def _find_missing_frames(frame_list: FrameList) -> list[int]: + def _find_missing_frames(frame_list: FrameList | RootHashList) -> list[int]: if frame_list.total_frame == len(frame_list.data): return [] number_list = list(range(1, frame_list.total_frame + 1)) diff --git a/pymammotion/data/state_manager.py b/pymammotion/data/state_manager.py index c5abda9..b126009 100644 --- a/pymammotion/data/state_manager.py +++ b/pymammotion/data/state_manager.py @@ -62,7 +62,7 @@ async def _update_nav_data(self, message) -> None: match nav_msg[0]: case "toapp_gethash_ack": hashlist_ack: NavGetHashListAck = nav_msg[1] - self._device.map.set_hashlist(hashlist_ack.data_couple) + self._device.map.update_root_hash_list(hashlist_ack) await self.gethash_ack_callback(nav_msg[1]) case "toapp_get_commondata_ack": common_data: NavGetCommDataAck = nav_msg[1] diff --git a/pymammotion/mammotion/commands/mammotion_command.py b/pymammotion/mammotion/commands/mammotion_command.py index cf2e03c..0a05566 100644 --- a/pymammotion/mammotion/commands/mammotion_command.py +++ b/pymammotion/mammotion/commands/mammotion_command.py @@ -24,26 +24,26 @@ def get_device_product_key(self) -> str: def set_device_product_key(self, product_key: str) -> None: self._product_key = product_key - async def move_forward(self, linear: float) -> None: + def move_forward(self, linear: float) -> bytes: """Move forward. values 0.0 1.0.""" linear_percent = get_percent(abs(linear * 100)) (linear_speed, angular_speed) = transform_both_speeds(90.0, 0.0, linear_percent, 0.0) - await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) - async def move_back(self, linear: float) -> None: + def move_back(self, linear: float) -> bytes: """Move back. values 0.0 1.0.""" linear_percent = get_percent(abs(linear * 100)) (linear_speed, angular_speed) = transform_both_speeds(270.0, 0.0, linear_percent, 0.0) - await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) - async def move_left(self, angular: float) -> None: + def move_left(self, angular: float) -> bytes: """Move forward. values 0.0 1.0.""" angular_percent = get_percent(abs(angular * 100)) (linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent) - await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) - async def move_right(self, angular: float) -> None: + def move_right(self, angular: float) -> bytes: """Move back. values 0.0 1.0.""" angular_percent = get_percent(abs(angular * 100)) (linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent) - await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) diff --git a/pymammotion/mammotion/devices/base.py b/pymammotion/mammotion/devices/base.py index bdcfde3..0edfab2 100644 --- a/pymammotion/mammotion/devices/base.py +++ b/pymammotion/mammotion/devices/base.py @@ -9,7 +9,6 @@ from pymammotion.data.model import RegionData from pymammotion.data.model.device import MowingDevice from pymammotion.data.state_manager import StateManager -from pymammotion.proto import has_field from pymammotion.proto.luba_msg import LubaMsg from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck @@ -55,7 +54,15 @@ def set_queue_callback(self, func: Callable[[str, dict[str, Any]], Awaitable[byt async def datahash_response(self, hash_ack: NavGetHashListAck) -> None: """Handle datahash responses.""" - await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0]) + current_frame = hash_ack.current_frame + + missing_frames = self.mower.map.missing_hash_frame() + if len(missing_frames) == 0: + return await self.queue_command("synchronize_hash_data", hash_num=self.mower.map.hashlist) + + if current_frame != missing_frames[0] - 1: + current_frame = missing_frames[0] - 1 + await self.queue_command("get_hash_response", total_frame=hash_ack.total_frame, current_frame=current_frame) async def commdata_response(self, common_data: NavGetCommDataAck) -> None: """Handle common data responses.""" @@ -66,7 +73,7 @@ async def commdata_response(self, common_data: NavGetCommDataAck) -> None: if len(missing_frames) == 0: # get next in hash ack list - data_hash = find_next_integer(self.mower.nav.toapp_gethash_ack.data_couple, common_data.hash) + data_hash = find_next_integer(self.mower.map.hashlist, common_data.hash) if data_hash is None: return @@ -201,22 +208,16 @@ async def start_sync(self, retry: int) -> None: async def start_map_sync(self) -> None: """Start sync of map data.""" - try: - # work out why this crashes sometimes for better proto - if self._cloud_device and len(self.mower.map.area_name) == 0: - await self.queue_command("get_area_name_list", device_id=self._cloud_device.iotId) - except Exception: - """Do nothing for now.""" + if self._cloud_device and len(self.mower.map.area_name) == 0: + await self.queue_command("get_area_name_list", device_id=self._cloud_device.iotId) await self.queue_command("read_plan", sub_cmd=2, plan_index=0) - if not has_field(self.mower.nav.toapp_gethash_ack): - await self.queue_command("get_all_boundary_hash_list", sub_cmd=0) - await self.queue_command("get_hash_response", total_frame=1, current_frame=1) - else: - for data_hash in self.mower.nav.toapp_gethash_ack.data_couple: - await self.queue_command("synchronize_hash_data", hash_num=data_hash) + await self.queue_command("get_all_boundary_hash_list", sub_cmd=0) + await self.queue_command("get_hash_response", total_frame=1, current_frame=1) + for data_hash in self.mower.map.hashlist: + await self.queue_command("synchronize_hash_data", hash_num=data_hash) # sub_cmd 3 is job hashes?? # sub_cmd 4 is dump location (yuka) diff --git a/pymammotion/mammotion/devices/mammotion.py b/pymammotion/mammotion/devices/mammotion.py index 2605eaf..b6ae6f1 100644 --- a/pymammotion/mammotion/devices/mammotion.py +++ b/pymammotion/mammotion/devices/mammotion.py @@ -247,7 +247,7 @@ async def login(self, account: str, password: str) -> CloudIOTGateway: await loop.run_in_executor(None, cloud_client.list_binding_by_account) return cloud_client - def remove_device(self, name: str) -> None: + async def remove_device(self, name: str) -> None: await self.devices.remove_device(name) def get_device_by_name(self, name: str) -> MammotionMixedDeviceManager: diff --git a/pyproject.toml b/pyproject.toml index 15a2895..1765b86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "pymammotion" -version = "0.2.44" +version = "0.2.47" [tool.poetry] name = "pymammotion" -version = "0.2.44" +version = "0.2.47" license = "GNU-3.0" description = "" readme = "README.md" @@ -55,7 +55,7 @@ mypy = "^1.10.0" pre-commit = "^3.8.0" [tool.bumpver] -current_version = "0.2.44" +current_version = "0.2.47" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = true