@@ -179,6 +179,20 @@ def __init__(self):
179179 self ._session_loop : asyncio .AbstractEventLoop | None = None
180180 self ._session_thread : threading .Thread | None = None
181181 self ._device_manager : DeviceConnectionManager | None = None
182+ self ._virtual_states : dict [str , Any ] = {}
183+
184+ def get_virtual_state (self , device_id : str , map_data : Any = None ) -> Any :
185+ """Get or create a VirtualState for a device."""
186+ from roborock .map import CoordinateTransformer , VirtualState
187+
188+ if device_id not in self ._virtual_states :
189+ if map_data is None :
190+ return None
191+
192+ transformer = CoordinateTransformer .from_map_data (map_data )
193+ self ._virtual_states [device_id ] = VirtualState (map_data , transformer )
194+
195+ return self ._virtual_states [device_id ]
182196
183197 def reload (self ):
184198 if self .roborock_file .is_file ():
@@ -1404,8 +1418,14 @@ async def _execute_edit(device, virtual_state, map_flag: int) -> bool:
14041418 click .echo ("ERROR: Device does not have command trait" )
14051419 return False
14061420
1421+ map_content_trait = getattr (device .v1_properties , 'map_content' , None )
1422+
14071423 # Create translation layer with proper arguments
1408- translation = TranslationLayer (command_trait = command_trait , protocol = "v1" )
1424+ translation = TranslationLayer (
1425+ command_trait = command_trait ,
1426+ map_content_trait = map_content_trait ,
1427+ protocol = "v1" ,
1428+ )
14091429
14101430 # Execute edits
14111431 results = await translation .execute_edits (virtual_state , map_flag )
@@ -1521,7 +1541,11 @@ async def split_room(ctx, device_id: str, room: str, direction: str, ratio: floa
15211541 split_line = calculate_split_line (room_bbox , direction , ratio )
15221542
15231543 # Create virtual state and add edit
1524- virtual_state = VirtualState (map_data , transformer )
1544+ virtual_state = context .get_virtual_state (device_id , map_data )
1545+ if virtual_state is None :
1546+ click .echo ("Failed to initialize virtual state" )
1547+ return
1548+
15251549 edit = SplitRoomEdit (
15261550 segment_id = target_room_id ,
15271551 x1 = split_line .p1 .x ,
@@ -1600,7 +1624,11 @@ async def merge_rooms(ctx, device_id: str, rooms: str, apply: bool, preview: boo
16001624 return
16011625
16021626 transformer = CoordinateTransformer .from_map_data (map_data )
1603- virtual_state = VirtualState (map_data , transformer )
1627+ virtual_state = context .get_virtual_state (device_id , map_data )
1628+ if virtual_state is None :
1629+ click .echo ("Failed to initialize virtual state" )
1630+ return
1631+
16041632 edit = MergeRoomsEdit (segment_ids = segment_ids )
16051633
16061634 success , error = virtual_state .add_edit (edit )
@@ -1673,7 +1701,11 @@ async def rename_room(ctx, device_id: str, room: str, new_name: str, apply: bool
16731701 return
16741702
16751703 transformer = CoordinateTransformer .from_map_data (map_data )
1676- virtual_state = VirtualState (map_data , transformer )
1704+ virtual_state = context .get_virtual_state (device_id , map_data )
1705+ if virtual_state is None :
1706+ click .echo ("Failed to initialize virtual state" )
1707+ return
1708+
16771709 edit = RenameRoomEdit (
16781710 segment_id = target_room_id ,
16791711 new_name = new_name ,
@@ -1736,7 +1768,10 @@ async def add_virtual_wall(ctx, device_id: str, x1: int, y1: int, x2: int, y2: i
17361768
17371769 map_data = map_trait .map_data
17381770 transformer = CoordinateTransformer .from_map_data (map_data )
1739- virtual_state = VirtualState (map_data , transformer )
1771+ virtual_state = context .get_virtual_state (device_id , map_data )
1772+ if virtual_state is None :
1773+ click .echo ("Failed to initialize virtual state" )
1774+ return
17401775
17411776 edit = VirtualWallEdit (x1 = float (x1 ), y1 = float (y1 ), x2 = float (x2 ), y2 = float (y2 ))
17421777
@@ -1796,7 +1831,10 @@ async def add_no_go_zone(ctx, device_id: str, x1: int, y1: int, x2: int, y2: int
17961831
17971832 map_data = map_trait .map_data
17981833 transformer = CoordinateTransformer .from_map_data (map_data )
1799- virtual_state = VirtualState (map_data , transformer )
1834+ virtual_state = context .get_virtual_state (device_id , map_data )
1835+ if virtual_state is None :
1836+ click .echo ("Failed to initialize virtual state" )
1837+ return
18001838
18011839 edit = NoGoZoneEdit (x1 = float (x1 ), y1 = float (y1 ), x2 = float (x2 ), y2 = float (y2 ))
18021840
@@ -1821,6 +1859,101 @@ async def add_no_go_zone(ctx, device_id: str, x1: int, y1: int, x2: int, y2: int
18211859 click .echo ("\n Use --apply flag to execute the edit" )
18221860
18231861
1862+ @session .command ()
1863+ @click .option ("--device_id" , required = True , help = "Device ID" )
1864+ @click .pass_context
1865+ @async_command
1866+ async def map_edit_status (ctx , device_id : str ):
1867+ """Show pending edits in the virtual state."""
1868+ context : RoborockContext = ctx .obj
1869+ virtual_state = context .get_virtual_state (device_id )
1870+
1871+ if not virtual_state or not virtual_state .has_pending_edits :
1872+ click .echo ("No pending edits" )
1873+ return
1874+
1875+ click .echo (f"Pending edits for device { device_id } :" )
1876+ for i , edit in enumerate (virtual_state .pending_edits ):
1877+ click .echo (f" { i + 1 } . { edit .edit_type .name } (Status: { edit .status .name } )" )
1878+
1879+
1880+ @session .command ()
1881+ @click .option ("--device_id" , required = True , help = "Device ID" )
1882+ @click .pass_context
1883+ @async_command
1884+ async def map_edit_sync (ctx , device_id : str ):
1885+ """Sync all pending edits to the device."""
1886+ context : RoborockContext = ctx .obj
1887+ virtual_state = context .get_virtual_state (device_id )
1888+
1889+ if not virtual_state or not virtual_state .has_pending_edits :
1890+ click .echo ("No pending edits to sync" )
1891+ return
1892+
1893+ device_manager = await context .get_device_manager ()
1894+ device = await device_manager .get_device (device_id )
1895+
1896+ # We need the map_flag from the base map
1897+ map_flag = virtual_state ._base_map .map_flag if virtual_state ._base_map else 0
1898+
1899+ success = await _execute_edit (device , virtual_state , map_flag )
1900+ if success :
1901+ click .echo ("Sync successful. Clearing pending edits." )
1902+ virtual_state .clear ()
1903+ else :
1904+ click .echo ("Sync failed or partially completed." )
1905+
1906+
1907+ @session .command ()
1908+ @click .option ("--device_id" , required = True , help = "Device ID" )
1909+ @click .pass_context
1910+ @async_command
1911+ async def map_edit_undo (ctx , device_id : str ):
1912+ """Undo the last pending edit."""
1913+ context : RoborockContext = ctx .obj
1914+ virtual_state = context .get_virtual_state (device_id )
1915+
1916+ if not virtual_state or not virtual_state .can_undo :
1917+ click .echo ("Nothing to undo" )
1918+ return
1919+
1920+ edit = virtual_state .undo ()
1921+ click .echo (f"Undone: { edit .edit_type .name } " )
1922+
1923+
1924+ @session .command ()
1925+ @click .option ("--device_id" , required = True , help = "Device ID" )
1926+ @click .pass_context
1927+ @async_command
1928+ async def map_edit_redo (ctx , device_id : str ):
1929+ """Redo the last undone edit."""
1930+ context : RoborockContext = ctx .obj
1931+ virtual_state = context .get_virtual_state (device_id )
1932+
1933+ if not virtual_state or not virtual_state .can_redo :
1934+ click .echo ("Nothing to redo" )
1935+ return
1936+
1937+ edit = virtual_state .redo ()
1938+ click .echo (f"Redone: { edit .edit_type .name } " )
1939+
1940+
1941+ @session .command ()
1942+ @click .option ("--device_id" , required = True , help = "Device ID" )
1943+ @click .pass_context
1944+ @async_command
1945+ async def map_edit_clear (ctx , device_id : str ):
1946+ """Clear all pending edits."""
1947+ context : RoborockContext = ctx .obj
1948+ virtual_state = context .get_virtual_state (device_id )
1949+
1950+ if virtual_state :
1951+ virtual_state .clear ()
1952+ click .echo ("Cleared all pending edits" )
1953+ else :
1954+ click .echo ("No virtual state found for this device" )
1955+
1956+
18241957def main ():
18251958 return cli ()
18261959
0 commit comments