Skip to content

Commit

Permalink
v2.0.0b6
Browse files Browse the repository at this point in the history
  • Loading branch information
Yiğit Topcu committed Sep 19, 2023
1 parent d8744b1 commit 6980baf
Show file tree
Hide file tree
Showing 28 changed files with 2,920 additions and 1,251 deletions.
172 changes: 121 additions & 51 deletions custom_components/dreame_vacuum/camera.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import collections
import voluptuous as vol
from enum import IntEnum
import time
import asyncio
Expand All @@ -11,7 +12,7 @@
from functools import partial
from aiohttp import web

from homeassistant.components.camera import Camera, CameraEntityDescription
from homeassistant.components.camera import Camera, CameraEntityDescription, CameraView
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNAVAILABLE, CONTENT_TYPE_MULTIPART
from homeassistant.core import HomeAssistant, callback
Expand All @@ -36,7 +37,6 @@
from .entity import DreameVacuumEntity, DreameVacuumEntityDescription
from .dreame.map import (
DreameVacuumMapRenderer,
DreameVacuumMapDataRenderer,
DreameVacuumMapDataJsonRenderer,
)

Expand All @@ -45,7 +45,6 @@ class DreameVacuumMapType(IntEnum):
FLOOR_MAP = 0
WIFI_MAP = 1
JSON_MAP_DATA = 2
DREAME_MAP_DATA = 3


@dataclass
Expand All @@ -70,6 +69,57 @@ class DreameVacuumCameraEntityDescription(
),
)

class CameraDataView(CameraView):
"""Camera view to serve the map data."""

url = "/api/camera_map_data_proxy/{entity_id}"
name = "api:camera:map_data"

async def handle(self, request: web.Request, camera: Camera) -> web.Response:
"""Serve camera data."""
if not camera.available:
raise web.HTTPServiceUnavailable()
if not camera.map_data_json:
return web.Response(body=camera.map_data_string, content_type="application/json")
raise web.HTTPNotFound()

class CameraObstacleView(CameraView):
"""Camera view to serve the map data obstacle image."""

url = "/api/camera_map_obstacle_proxy/{entity_id}"
name = "api:camera:map_obstacle"

async def handle(self, request: web.Request, camera: Camera) -> web.Response:
"""Serve camera obstacle image."""
if not camera.available:
raise web.HTTPServiceUnavailable()

if camera.map_index == 0:
crop = request.query.get("crop")
result = await camera.obstacle_image(request.query.get("index", 1), crop and (crop == True or crop == "true" or crop == "1"))
if result:
return web.Response(body=result, content_type="image/jpeg")

raise web.HTTPNotFound()

class CameraHistoryView(CameraView):
"""Camera view to serve the cleaning history map."""

url = "/api/camera_map_history_proxy/{entity_id}"
name = "api:camera:map_history"

async def handle(self, request: web.Request, camera: Camera) -> web.Response:
"""Serve camera cleaning history data."""
if not camera.available:
raise web.HTTPServiceUnavailable()

if not camera.map_data_json and camera.map_index == 0:
data = request.query.get("data")
data = data and (data == True or data == "true" or data == "1")
result = await camera.cleaning_history_map(request.query.get("index", 1), data)
if result:
return web.Response(body=result, content_type="application/json" if data else "image/jpeg")
raise web.HTTPNotFound()

async def async_setup_entry(
hass: HomeAssistant,
Expand All @@ -78,12 +128,13 @@ async def async_setup_entry(
) -> None:
"""Set up Dreame Vacuum Camera based on a config entry."""
coordinator: DreameVacuumDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
color_scheme = entry.options.get(CONF_COLOR_SCHEME)
icon_set = entry.options.get(CONF_ICON_SET)
low_resolution = entry.options.get(CONF_LOW_RESOLUTION, False)
square = entry.options.get(CONF_SQUARE, False)
map_objects = entry.options.get(CONF_MAP_OBJECTS, MAP_OBJECTS.keys())
if coordinator.device.capability.map:
color_scheme = entry.options.get(CONF_COLOR_SCHEME)
icon_set = entry.options.get(CONF_ICON_SET)
low_resolution = entry.options.get(CONF_LOW_RESOLUTION, False)
square = entry.options.get(CONF_SQUARE, False)
map_objects = entry.options.get(CONF_MAP_OBJECTS, MAP_OBJECTS.keys())

async_add_entities(
DreameVacuumCameraEntity(
coordinator,
Expand All @@ -97,23 +148,27 @@ async def async_setup_entry(
for description in CAMERAS
)

update_map_cameras = partial(
async_update_map_cameras,
coordinator,
{},
async_add_entities,
color_scheme,
icon_set,
map_objects,
low_resolution,
square,
)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
"update", {}, DreameVacuumCameraEntity.async_update.__name__
)
coordinator.async_add_listener(update_map_cameras)
update_map_cameras()
update_map_cameras = partial(
async_update_map_cameras,
coordinator,
{},
async_add_entities,
color_scheme,
icon_set,
map_objects,
low_resolution,
square,
)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
"update", {}, DreameVacuumCameraEntity.async_update.__name__
)
coordinator.async_add_listener(update_map_cameras)
update_map_cameras()

hass.http.register_view(CameraDataView(hass.data["camera"]))
hass.http.register_view(CameraObstacleView(hass.data["camera"]))
hass.http.register_view(CameraHistoryView(hass.data["camera"]))


@callback
Expand Down Expand Up @@ -223,15 +278,11 @@ def __init__(
self._available = self.device.device_connected and self.device.cloud_connected
if description.map_type == DreameVacuumMapType.JSON_MAP_DATA:
self._renderer = DreameVacuumMapDataJsonRenderer()
elif description.map_type == DreameVacuumMapType.DREAME_MAP_DATA:
self._renderer = DreameVacuumMapDataRenderer(
color_scheme, icon_set, self.device.capability.robot_type
)
else:
self._renderer = DreameVacuumMapRenderer(
color_scheme,
icon_set,
["charger"] if self._wifi_map else map_objects,
["charger"] if self.wifi_map else map_objects,
self.device.capability.robot_type,
low_resolution,
square,
Expand All @@ -241,7 +292,7 @@ def __init__(
self._default_map = True
self.map_index = map_index
self._state = STATE_UNAVAILABLE
if self.map_index == 0 and not self._map_data_json:
if self.map_index == 0 and not self.map_data_json:
self._image = self._renderer.default_map_image

map_data = self._map_data
Expand All @@ -252,15 +303,15 @@ def __init__(
self._map_name = map_data.custom_name
else:
self._map_name = None
self._set_map_name(self._wifi_map)
self._attr_unique_id = f"{self.device.mac}_{'wifi_' if self._wifi_map else ''}map_{self.map_index}"
self.entity_id = f"camera.{self.device.name.lower()}_{'wifi_' if self._wifi_map else ''}map_{self.map_index}"
self._set_map_name(self.wifi_map)
self._attr_unique_id = f"{self.device.mac}_{'wifi_' if self.wifi_map else ''}map_{self.map_index}"
self.entity_id = f"camera.{self.device.name.lower()}_{'wifi_' if self.wifi_map else ''}map_{self.map_index}"
else:
self._attr_name = f"{self.device.name} Current {'Wifi ' if self._wifi_map else ''}{description.name}"
self._attr_unique_id = f"{self.device.mac}_map_{'wifi_' if self._wifi_map else ''}{description.key}"
self.entity_id = f"camera.{self.device.name.lower()}_{'wifi_' if self._wifi_map else ''}{description.key.lower()}"
self._attr_name = f"{self.device.name} Current {'Wifi ' if self.wifi_map else ''}{description.name}"
self._attr_unique_id = f"{self.device.mac}_map_{'wifi_' if self.wifi_map else ''}{description.key}"
self.entity_id = f"camera.{self.device.name.lower()}_{'wifi_' if self.wifi_map else ''}{description.key.lower()}"

if self.map_index == 0 and not self._map_data_json:
if self.map_index == 0 and not self.map_data_json:
self.update()

def _set_map_name(self, wifi_map) -> None:
Expand All @@ -287,7 +338,7 @@ def _handle_coordinator_update(self) -> None:
if self.map_index > 0:
if self._map_name != map_data.custom_name:
self._map_name = map_data.custom_name
self._set_map_name(self._wifi_map)
self._set_map_name(self.wifi_map)

if self._map_id != map_data.map_id:
self._map_id = map_data.map_id
Expand Down Expand Up @@ -372,7 +423,7 @@ def update(self) -> None:
):
if (
self.map_index == 0
and not self._map_data_json
and not self.map_data_json
and map_data.last_updated != self._last_updated
and not self._renderer.render_complete
):
Expand All @@ -382,7 +433,7 @@ def update(self) -> None:
self._renderer.render_complete
and map_data.last_updated != self._last_updated
):
if self.map_index == 0 and not self._map_data_json:
if self.map_index == 0 and not self.map_data_json:
LOGGER.debug("Update map")

self._last_updated = map_data.last_updated
Expand All @@ -397,7 +448,7 @@ def update(self) -> None:

self.coordinator.hass.async_create_task(
self._update_image(
self.device.get_map_for_render(self.map_index, self._wifi_map),
self.device.get_map_for_render(self._map_data),
self.device.status.robot_status,
)
)
Expand All @@ -408,11 +459,27 @@ def update(self) -> None:
self._last_updated = -1
self._state = STATE_UNAVAILABLE

async def obstacle_image(self, index, crop = False):
if self.map_index == 0 and not self.map_data_json:
response, obstacle = await self.hass.async_add_executor_job(self.device.obstacle_image, index)
if response and obstacle:
return self._renderer.render_obstacle_image(response, obstacle, self.device.capability.obstacle_image_crop, crop)

async def cleaning_history_map(self, index, data_string):
if self.map_index == 0 and not self.map_data_json:
map_data = await self.hass.async_add_executor_job(self.device.cleaning_history_map, index)
if map_data:
map_data = self.device.get_map_for_render(map_data)
if data_string:
return self._renderer.get_data_string(map_data)
else:
return self._renderer.render_map(map_data, 0, False)

async def _update_image(self, map_data, robot_status) -> None:
try:
self._image = self._renderer.render_map(map_data, robot_status)
if (
not self._map_data_json
not self.map_data_json
and self._calibration_points != self._renderer.calibration_points
):
self._calibration_points = self._renderer.calibration_points
Expand All @@ -421,20 +488,17 @@ async def _update_image(self, map_data, robot_status) -> None:
LOGGER.warn("Map render Failed: %s", traceback.format_exc())

@property
def _wifi_map(self) -> bool:
def wifi_map(self) -> bool:
return bool(self.entity_description.map_type == DreameVacuumMapType.WIFI_MAP)

@property
def _map_data_json(self) -> bool:
return bool(
self.entity_description.map_type == DreameVacuumMapType.JSON_MAP_DATA
or self.entity_description.map_type == DreameVacuumMapType.DREAME_MAP_DATA
)
def map_data_json(self) -> bool:
return bool(self.entity_description.map_type == DreameVacuumMapType.JSON_MAP_DATA)

@property
def _map_data(self) -> Any:
map_data = self.device.get_map(self.map_index)
if self._wifi_map and map_data:
if self.wifi_map and map_data:
return map_data.wifi_map_data
return map_data

Expand All @@ -446,6 +510,12 @@ def _default_map_image(self) -> Any:
return self._renderer.disconnected_map_image
return self._renderer.default_map_image

@property
def map_data_string(self) -> str | None:
if not self.map_data_json and self._map_data:
return self._renderer.get_data_string(self.device.get_map_for_render(self._map_data), self.device.status.robot_status)
return "{}"

@property
def frame_interval(self) -> float:
return 0.25
Expand All @@ -466,7 +536,7 @@ def available(self) -> bool:

@property
def extra_state_attributes(self) -> Dict[str, Any]:
if not self._map_data_json:
if not self.map_data_json:
map_data = self._map_data
if (
map_data
Expand Down
22 changes: 20 additions & 2 deletions custom_components/dreame_vacuum/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)

DREAME_MODELS = [
"dreame.vacuum.p2008",
"dreame.vacuum.r2205",
"dreame.vacuum.r2243",
"dreame.vacuum.r2240",
Expand All @@ -64,6 +65,7 @@
"dreame.vacuum.r2215o",
"dreame.vacuum.r2216o",
"dreame.vacuum.r2228o",
"dreame.vacuum.r2228d",
"dreame.vacuum.r2228",
"dreame.vacuum.r2246",
"dreame.vacuum.r2233",
Expand All @@ -89,6 +91,22 @@
"dreame.vacuum.p2157",
"dreame.vacuum.p2156o",
"dreame.vacuum.r2360",
"dreame.vacuum.r2253a",
"dreame.vacuum.r2253b",
"dreame.vacuum.r2253c",
"dreame.vacuum.r2253d",
"dreame.vacuum.r2253w",
"dreame.vacuum.r2253t",
"dreame.vacuum.r2253m",
"dreame.vacuum.r2260",
"dreame.vacuum.r2273",
"dreame.vacuum.r2273a",
"dreame.vacuum.r2386",
"dreame.vacuum.r2398",
"dreame.vacuum.r2375",
"dreame.vacuum.r2388",
"dreame.vacuum.r2361a",
"dreame.vacuum.r9301",
]

MIJIA_MODELS = [
Expand All @@ -104,13 +122,13 @@
"dreame.vacuum.p2150b",
"dreame.vacuum.p2150o",
"dreame.vacuum.r2209",
"dreame.vacuum.p2008",
"dreame.vacuum.p2148o",
"dreame.vacuum.p2140o",
"dreame.vacuum.r2254",
"dreame.vacuum.p2140p",
"dreame.vacuum.p2140q",
"dreame.vacuum.p2041o",
"xiaomi.vacuum.c102cn"
]

MIHOME: Final = "Xiaomi Home Account"
Expand Down Expand Up @@ -411,7 +429,7 @@ async def async_step_mi(
str(d["model"]) in DREAME_MODELS
or str(d["model"]) in MIJIA_MODELS
),
devices["result"]["list"],
devices,
)
)

Expand Down
Loading

0 comments on commit 6980baf

Please sign in to comment.