DeebotUniverse / Deebot-4-Home-Assistant

Home Assistant integration for deebot vacuums
https://deebot.readthedocs.io/integrations/home-assistant/
GNU General Public License v3.0
179 stars 37 forks source link

Add getPosition event with x y and currentSpotAreaID #505

Open francescopalma86 opened 5 months ago

francescopalma86 commented 5 months ago

Is there an existing issue for this?

Is your feature request related to a problem?

No response

Suggested solution

My goal is to turn on the light in the room where the robot is cleaning and turn it off when it exits the room. I have studied code and examples online, and I have found that the ecovacs-deebot.js library provides the following information: along with the x and y coordinates, it also has the currentSpotAreaID property.

In vacBot.js -> let currentSpotAreaID = mapTools.isPositionInSpotArea(posX, posY, this.mapSpotAreaInfos[this.currentMapMID]);

After retrieving the polygon of each room and the current position of the robot, the process to calculate the area it is in should be as follows:

  1. Decompress the coordinates with lzma and decode the base64: polygon_string = _decompress_7z_base64_data(event.coordinates).decode('utf-8');

  2. Use a function like the following for each room to determine in which polygon the current bot coordinates are located:

import shapely.geometry

def is_point_inside_polygon(x, y, polygon_string):
    coordinates = [tuple(map(float, pair.split(','))) for pair in polygon_string.split(';')]
    polygon = shapely.geometry.Polygon(coordinates)
    return polygon.contains(shapely.geometry.Point(x, y))
  1. Now, you could trigger a new event or modify the value of a new sensor to inform the user that the bot has changed rooms.

Api example request

No response

Alternatives you've considered

No response

Additional information

No response

francescopalma86 commented 5 months ago

I wrote this example

import aiohttp
import asyncio
import logging
import time
import shapely.geometry

from deebot_client.api_client import ApiClient
from deebot_client.authentication import Authenticator
from deebot_client.commands.json import GetMapSubSet, GetMapSet, GetPos
from deebot_client.events import MapSubsetEvent, MapSetEvent, PositionsEvent
from deebot_client.map import _decompress_7z_base64_data
from deebot_client.models import Configuration
from deebot_client.mqtt_client import MqttClient, MqttConfiguration
from deebot_client.util import md5
from deebot_client.device import Device

device_id = md5(str(time.time()))
account_id = ""
password_hash = md5("")
country = "IT"
continent = "eu"
mapId = ""

lastDeebotArea = None

async def main():
    async with aiohttp.ClientSession() as session:
        logging.basicConfig(level=logging.DEBUG)
        config = Configuration(session, device_id=device_id, country=country, continent=continent)

        authenticator = Authenticator(config, account_id, password_hash)
        api_client = ApiClient(authenticator)

        devices_ = await api_client.get_devices()

        bot = Device(devices_[0], authenticator)

        mqtt_config = MqttConfiguration(config=config)
        mqtt = MqttClient(mqtt_config, authenticator)
        await bot.initialize(mqtt)

        subsets = []

        def get_room_dict_from(event: MapSubsetEvent):
            s_coords = _decompress_7z_base64_data(event.coordinates).decode('utf-8')
            coordinates = [tuple(map(float, pair.split(','))) for pair in s_coords.split(';')]
            polygon = shapely.geometry.Polygon(coordinates)
            return {
                "id": event.id,
                "name": event.name,
                "area": polygon
            }

        async def on_mapsubset(event: MapSubsetEvent):
            if event.type == "ar":
                subsets.append(get_room_dict_from(event))

        async def on_mapset(event: MapSetEvent):
            if event.type == "ar":
                subsets.clear()
                for room_id in event.subsets:
                    await bot.execute_command(GetMapSubSet(mid=mapId, mssid=str(room_id), type="ar", msid="0"))

        async def on_position(event: PositionsEvent):
            deebot_pos = event.positions[0]
            deebot_point = shapely.geometry.Point(deebot_pos.x, deebot_pos.y)
            print(deebot_pos.x, deebot_pos.y)
            global lastDeebotArea
            for subset in subsets:
                if subset["area"].contains(deebot_point):
                    if subset['name'] != lastDeebotArea:
                        lastDeebotArea = subset["name"]
                        print(f"Deebot has changed area: {subset['name']}")
                    break

        bot.events.subscribe(PositionsEvent, on_position)
        bot.events.subscribe(MapSetEvent, on_mapset)
        bot.events.subscribe(MapSubsetEvent, on_mapsubset)

        # Execute commands
        await bot.execute_command(GetMapSet(mid=mapId))

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.create_task(main())
    loop.run_forever()

in this example print new Area when deebot change her, now i miss homassistant dev skill

edenhaus commented 5 months ago

Thanks for your improvement. Could you open a PR directly in https://github.com/DeebotUniverse/client.py? Afterwards I can add it to the component

francescopalma86 commented 5 months ago

I released implementation in currentSpotAreaID brench and opened a new pull request. After I forked Deebot-4-Home-Assistant and homeassistant core.

Now I don't know how to proceed.

I also finished the total implementation and this is the result Screenshot 2024-01-18 alle 20 34 50

but I did everything manually, because I don't know the process of implementing custom integrations and development on home assistants in general