Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added room name information to Position object #397

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions deebot_client/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .event_bus import EventBus
from .logging_filter import get_logger
from .message import HandlingResult, HandlingState, Message
from .models import DeviceInfo
from .models import DeviceInfo, Room

_LOGGER = get_logger(__name__)

Expand Down Expand Up @@ -63,7 +63,7 @@ def _get_payload(self) -> dict[str, Any] | list[Any] | str:

@final
async def execute(
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus, rooms: list[Room] = []
) -> bool:
"""Execute command.

Expand All @@ -73,14 +73,14 @@ async def execute(
This value is not indicating if the command was executed successfully.
"""
try:
result = await self._execute(authenticator, device_info, event_bus)
result = await self._execute(authenticator, device_info, event_bus, rooms)
if result.state == HandlingState.SUCCESS:
# Execute command which are requested by the handler
async with asyncio.TaskGroup() as tg:
for requested_command in result.requested_commands:
tg.create_task(
requested_command.execute(
authenticator, device_info, event_bus
authenticator, device_info, event_bus, rooms
)
)

Expand All @@ -94,11 +94,14 @@ async def execute(
return False

async def _execute(
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus, rooms: list[Room] = []
) -> CommandResult:
"""Execute command."""
response = await self._execute_api_request(authenticator, device_info)

if self.name == "getPos":
response["resp"]["body"]["data"]["rooms"] = rooms
Comment on lines +102 to +103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?
getPos specific code should go into the GetPos class and not in the general Command class.

Copy link
Author

@francescopalma86 francescopalma86 Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to pass rooms parameter to GetPos class. This is the only way I've found. Help me?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code should be in map.py and not in the command. The commands module is what we get from the API and we should not do calculations there.
Instead I think the calculation should be part of map.py, where you already have the rooms and the position


result = self.__handle_response(event_bus, response)
if result.state == HandlingState.ANALYSE:
_LOGGER.debug(
Expand Down
6 changes: 3 additions & 3 deletions deebot_client/commands/json/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from deebot_client.events import StateEvent
from deebot_client.logging_filter import get_logger
from deebot_client.message import HandlingResult, MessageBodyDataDict
from deebot_client.models import CleanAction, CleanMode, DeviceInfo, State
from deebot_client.models import CleanAction, CleanMode, DeviceInfo, State, Room

from .common import ExecuteCommand, JsonCommandWithMessageHandling

Expand All @@ -23,7 +23,7 @@ def __init__(self, action: CleanAction) -> None:
super().__init__(self.__get_args(action))

async def _execute(
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus
self, authenticator: Authenticator, device_info: DeviceInfo, event_bus: EventBus, rooms: list[Room] = []
) -> CommandResult:
"""Execute command."""
state = event_bus.get_last_event(StateEvent)
Expand All @@ -39,7 +39,7 @@ async def _execute(
):
self._args = self.__get_args(CleanAction.RESUME)

return await super()._execute(authenticator, device_info, event_bus)
return await super()._execute(authenticator, device_info, event_bus, rooms)

@staticmethod
def __get_args(action: CleanAction) -> dict[str, Any]:
Expand Down
35 changes: 27 additions & 8 deletions deebot_client/commands/json/pos.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .common import JsonCommandWithMessageHandling

from shapely.geometry import Point

class GetPos(JsonCommandWithMessageHandling, MessageBodyDataDict):
"""Get volume command."""
Expand All @@ -27,26 +28,44 @@ def _handle_body_data_dict(
"""
positions = []

rooms = data.get("rooms", [])

for type_str in ["deebotPos", "chargePos"]:

room_name = None
data_positions = data.get(type_str, [])

if isinstance(data_positions, dict):
point = Point(data_positions.get("x"), data_positions.get("y"))
for room in rooms:
if room.polygon is not None and room.polygon.contains(point):
room_name = room.name
break

positions.append(
Position(
type=PositionType(type_str),
x=data_positions["x"],
y=data_positions["y"],
x=data_positions.get("x"),
y=data_positions.get("y"),
room=room_name
)
)
else:
positions.extend(
[
for entry in data_positions:
point = Point(entry.get("x"), entry.get("y"))
for room in rooms:
if room.polygon is not None and room.polygon.contains(point):
room_name = room.name
break

positions.append(
Position(
type=PositionType(type_str), x=entry["x"], y=entry["y"]
type=PositionType(type_str),
x=entry.get("x"),
y=entry.get("y"),
room=room_name
)
for entry in data_positions
]
)
)

if positions:
event_bus.notify(PositionsEvent(positions=positions))
Expand Down
22 changes: 20 additions & 2 deletions deebot_client/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
StateEvent,
StatsEvent,
TotalStatsEvent,
RoomsEvent, Position
)
from .logging_filter import get_logger
from .map import Map
from .map import Map, Room
from .messages import get_message
from .models import DeviceInfo, State

from shapely.geometry import Point

if TYPE_CHECKING:
from collections.abc import Callable

Expand All @@ -53,6 +56,8 @@ def __init__(
self._available_task: asyncio.Task[Any] | None = None
self._unsubscribe: Callable[[], None] | None = None

self.rooms: list[Room] = []

self.fw_version: str | None = None
self.mac: str | None = None
self.events: Final[EventBus] = EventBus(
Expand Down Expand Up @@ -80,6 +85,12 @@ async def on_pos(event: PositionsEvent) -> None:

self.events.subscribe(PositionsEvent, on_pos)

async def on_rooms(event: RoomsEvent) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should go into map.py

self.rooms = event.rooms
self.events.request_refresh(PositionsEvent)

self.events.subscribe(RoomsEvent, on_rooms)

async def on_state(event: StateEvent) -> None:
if event.state == State.DOCKED:
self.events.request_refresh(CleanLogEvent)
Expand Down Expand Up @@ -155,7 +166,7 @@ async def _execute_command(self, command: Command) -> bool:
"""Execute given command."""
async with self._semaphore:
if await command.execute(
self._authenticator, self.device_info, self.events
self._authenticator, self.device_info, self.events, self.rooms
):
self._set_available(available=True)
return True
Expand Down Expand Up @@ -184,10 +195,17 @@ def _handle_message(
_LOGGER.debug("Try to handle message %s: %s", message_name, message_data)

if message := get_message(message_name, self.device_info.data_type):

rooms = self.rooms

if isinstance(message_data, dict):
data = message_data
if message_name == "onPos":
data["rooms"] = rooms
else:
data = json.loads(message_data)
if message_name == "onPos":
data["body"]["data"]["rooms"] = rooms

fw_version = data.get("header", {}).get("fwVer", None)
if fw_version:
Expand Down
1 change: 1 addition & 0 deletions deebot_client/events/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Position:
type: PositionType
x: int
y: int
room: str | None


@dataclass(frozen=True)
Expand Down
11 changes: 10 additions & 1 deletion deebot_client/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import struct
from typing import Any, Final
import zlib
import shapely.geometry

from PIL import Image, ImageColor, ImageOps, ImagePalette
import svg
Expand Down Expand Up @@ -407,7 +408,15 @@ async def on_map_set(event: MapSetEvent) -> None:

async def on_map_subset(event: MapSubsetEvent) -> None:
if event.type == MapSetType.ROOMS and event.name:
room = Room(event.name, event.id, event.coordinates)

if event.coordinates.endswith("="):
coords = _decompress_7z_base64_data(event.coordinates).decode('utf-8')
else:
coords = event.coordinates

coordinates = [tuple(map(float, pair.split(','))) for pair in coords.split(';')]
room = Room(event.name, event.id, coords, shapely.geometry.Polygon(coordinates))

if self._map_data.rooms.get(event.id, None) != room:
self._map_data.rooms[room.id] = room

Expand Down
3 changes: 3 additions & 0 deletions deebot_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from deebot_client.const import DataType
from deebot_client.util.continents import get_continent

from shapely.geometry import Polygon

if TYPE_CHECKING:
from deebot_client.capabilities import Capabilities

Expand Down Expand Up @@ -109,6 +111,7 @@ class Room:
name: str
id: int
coordinates: str
polygon: Polygon = None


@unique
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ defusedxml
numpy>=1.23.2,<2.0
Pillow>=10.0.1,<11.0
svg.py>=1.4.2
shapely>=2.0.2
2 changes: 1 addition & 1 deletion tests/test_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async def test_MapData(event_bus: EventBus) -> None:

async def test_cycle() -> None:
for x in range(10000):
map_data.positions.append(Position(PositionType.DEEBOT, x, x))
map_data.positions.append(Position(PositionType.DEEBOT, x, x, "test"))
map_data.rooms[x] = Room("test", x, "1,2")

assert map_data.changed is True
Expand Down