Skip to content

Commit

Permalink
Adding Obstacle Images phase 1 #287 from sca075/refactoring_camera
Browse files Browse the repository at this point in the history
Adding Obstacle Images from the robot. Created the link to locally get the images from the vacuums.
  • Loading branch information
sca075 authored Nov 30, 2024
2 parents b4880bf + bcb8b19 commit 68d63ef
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 77 deletions.
5 changes: 2 additions & 3 deletions custom_components/mqtt_vacuum_camera/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""MQTT Vacuum Camera.
Version: 2024.11.1"""

import logging
from functools import partial
import logging
import os

from homeassistant import config_entries, core
Expand All @@ -14,7 +14,6 @@
SERVICE_RELOAD,
Platform,
)

from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.reload import async_register_admin_service
from homeassistant.helpers.storage import STORAGE_DIR
Expand All @@ -28,7 +27,7 @@
DOMAIN,
)
from .coordinator import MQTTVacuumCoordinator
from .utils.camera.camera_services import reset_trims, reload_camera_config
from .utils.camera.camera_services import reload_camera_config, reset_trims
from .utils.files_operations import (
async_clean_up_all_auto_crop_files,
async_get_translations_vacuum_id,
Expand Down
53 changes: 29 additions & 24 deletions custom_components/mqtt_vacuum_camera/camera.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Camera
Version: v2024.11.0
Version: v2024.12.0
"""

from __future__ import annotations
Expand All @@ -10,7 +10,6 @@
import concurrent.futures
from datetime import timedelta
from io import BytesIO
import json
import logging
import os
import platform
Expand All @@ -26,11 +25,11 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from psutil_home_assistant import PsutilWrapper as ProcInsp

from .camera_processing import CameraProcessor
from .common import get_vacuum_unique_id_from_mqtt_topic
from .const import (
ATTR_FRIENDLY_NAME,
ATTR_JSON_DATA,
ATTR_OBSTACLES,
ATTR_SNAPSHOT_PATH,
ATTR_VACUUM_TOPIC,
CAMERA_STORAGE,
Expand All @@ -40,8 +39,13 @@
)
from .snapshots.snapshot import Snapshots
from .types import SnapshotStore
from .utils.camera.camera_processing import CameraProcessor
from .utils.colors_man import ColorsManagment
from .utils.files_operations import async_get_active_user_language, is_auth_updated
from .utils.files_operations import (
async_get_active_user_language,
async_load_file,
is_auth_updated,
)

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

Expand Down Expand Up @@ -218,7 +222,8 @@ def extra_state_attributes(self) -> dict:
ATTR_JSON_DATA: self._vac_json_available,
ATTR_SNAPSHOT_PATH: f"/local/snapshot_{self._file_name}.png",
}

if self._shared.obstacles_data:
attributes.update({ATTR_OBSTACLES: self._shared.obstacles_data})
# Update with the shared attributes generated by SharedData
attributes.update(self._shared.generate_attributes())

Expand Down Expand Up @@ -287,18 +292,6 @@ async def take_snapshot(self, json_data: Any, image_data: Image.Image) -> None:
await partial_snapshot.async_set_snapshot_save_data(self._file_name)
await self._snapshots.run_async_take_snapshot(json_data, image_data)

async def load_test_json(self, file_path: str = None) -> Any:
"""Load a test json."""
if file_path:
json_file = file_path
with open(json_file, "rb") as j_file:
tmp_json = j_file.read()
parsed_json = json.loads(tmp_json)
self._should_poll = False
return parsed_json
else:
return None

async def async_update(self):
"""Camera Frame Update."""

Expand Down Expand Up @@ -331,7 +324,7 @@ async def async_update(self):
f"{self._file_name}: Camera image data update available: {process_data}"
)
try:
parsed_json = await self._process_parsed_json()
parsed_json, is_a_test = await self._process_parsed_json(True)
except ValueError:
self._vac_json_available = "Error"
pass
Expand All @@ -346,12 +339,21 @@ async def async_update(self):
)
)
elif self._rrm_data is None:
_LOGGER.debug("Image creation in progress")
pil_img = await self.hass.async_create_task(
self.processor.run_async_process_valetudo_data(parsed_json)
)
else:
# if no image was processed empty or last snapshot/frame
pil_img = self.empty_if_no_data()
if not is_a_test:
pil_img = self.empty_if_no_data()
else:
_LOGGER.debug("Producing test mode image")
pil_img = await self.hass.async_create_task(
self.processor.run_async_process_valetudo_data(
parsed_json
)
)

# update the image
self._last_image = pil_img
Expand All @@ -362,7 +364,7 @@ async def async_update(self):
await self._take_snapshot(parsed_json, pil_img)

# clean up
del pil_img
# del pil_img
_LOGGER.debug(f"{self._file_name}: Image update complete")
self._update_frame_interval(start_time)
else:
Expand Down Expand Up @@ -401,10 +403,13 @@ async def _handle_no_mqtt_data(self):
async def _process_parsed_json(self, test_mode: bool = False):
"""Process the parsed JSON data and return the generated image."""
if test_mode:
parsed_json = await self.load_test_json(
"custom_components/mqtt_vacuum_camera/snapshots/test.json"
_LOGGER.debug("Camera Test Mode Active...")
parsed_json = await async_load_file(
file_to_load="custom_components/mqtt_vacuum_camera/snapshots/test.json",
is_json=True,
)
return parsed_json
self._should_poll = False
return parsed_json, test_mode
parsed_json = await self._mqtt.update_data(self._shared.image_grab)
if not parsed_json:
self._vac_json_available = "Error"
Expand All @@ -421,7 +426,7 @@ async def _process_parsed_json(self, test_mode: bool = False):
self._rrm_data = None

self._vac_json_available = "Success"
return parsed_json
return parsed_json, test_mode

async def _take_snapshot(self, parsed_json, pil_img):
"""Take a snapshot if conditions are met."""
Expand Down
39 changes: 38 additions & 1 deletion custom_components/mqtt_vacuum_camera/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Common functions for the MQTT Vacuum Camera integration.
Version: 2024.11.1
Version: 2024.12.0
"""

from __future__ import annotations
Expand Down Expand Up @@ -207,3 +207,40 @@ def get_entity_id(
_LOGGER.error(f"No vacuum entities found for device_id: {device_id}")
return None
return vacuum_entity_id


def compose_obstacle_links(vacuum_host: str, obstacles: list) -> list:
"""
Compose JSON with obstacle details including the image link.
"""
obstacle_links = []

for obstacle in obstacles:
# Extract obstacle details
label = obstacle.get("label", "")
points = obstacle.get("points", {})
image_id = obstacle.get("id", "None")

if label and points and image_id:
# Append formatted obstacle data
if image_id != "None":
# Compose the link
image_link = f"{vacuum_host}/api/v2/robot/capabilities/ObstacleImagesCapability/img/{image_id}"
obstacle_links.append(
{
"point": points,
"label": label,
"link": image_link,
}
)
else:
obstacle_links.append(
{
"point": points,
"label": label,
}
)

_LOGGER.debug(f"Obstacle links: {obstacle_links}")

return obstacle_links
1 change: 1 addition & 0 deletions custom_components/mqtt_vacuum_camera/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,4 @@
ATTR_ROOMS = "rooms"
ATTR_ZONES = "zones"
ATTR_POINTS = "points"
ATTR_OBSTACLES = "obstacles"
8 changes: 6 additions & 2 deletions custom_components/mqtt_vacuum_camera/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
MQTT Vacuum Camera Coordinator.
Version: v2024.11.0
Version: v2024.12.0
"""

import asyncio
Expand All @@ -15,7 +15,11 @@
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .camera_shared import CameraShared, CameraSharedManager
from custom_components.mqtt_vacuum_camera.utils.camera.camera_shared import (
CameraShared,
CameraSharedManager,
)

from .common import get_camera_device_info
from .const import DEFAULT_NAME, SENSOR_NO_DATA
from .valetudo.MQTT.connector import ValetudoConnector
Expand Down
2 changes: 1 addition & 1 deletion custom_components/mqtt_vacuum_camera/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/sca075/mqtt_vacuum_camera/issues",
"requirements": ["pillow>=10.3.0,<=11.0.0", "numpy"],
"version": "2024.11.1"
"version": "2024.12.0b0"
}
2 changes: 1 addition & 1 deletion custom_components/mqtt_vacuum_camera/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import PERCENTAGE, UnitOfTime, UnitOfArea
from homeassistant.const import PERCENTAGE, UnitOfArea, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import EntityCategory
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
import concurrent.futures
import logging

from .const import NOT_STREAMING_STATES
from .types import Color, JsonType, PilPNG
from .utils.drawable import Drawable as Draw
from .utils.files_operations import async_get_active_user_language
from .utils.status_text import StatusText
from .valetudo.hypfer.image_handler import MapImageHandler
from .valetudo.rand256.image_handler import ReImageHandler
from custom_components.mqtt_vacuum_camera.const import NOT_STREAMING_STATES
from custom_components.mqtt_vacuum_camera.types import Color, JsonType, PilPNG
from custom_components.mqtt_vacuum_camera.utils.drawable import Drawable as Draw
from custom_components.mqtt_vacuum_camera.utils.files_operations import (
async_get_active_user_language,
)
from custom_components.mqtt_vacuum_camera.utils.status_text import StatusText
from custom_components.mqtt_vacuum_camera.valetudo.hypfer.image_handler import (
MapImageHandler,
)
from custom_components.mqtt_vacuum_camera.valetudo.rand256.image_handler import (
ReImageHandler,
)

_LOGGER = logging.getLogger(__name__)
_LOGGER.propagate = True
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""Camera-related services for the MQTT Vacuum Camera integration."""

import logging
import asyncio
import async_timeout
import logging

from homeassistant.core import ServiceCall, HomeAssistant
import async_timeout
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import SERVICE_RELOAD
from homeassistant.core import HomeAssistant, ServiceCall

from ...utils.files_operations import async_clean_up_all_auto_crop_files
from ...const import DOMAIN
from ...utils.files_operations import async_clean_up_all_auto_crop_files

_LOGGER = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""
Class Camera Shared.
Keep the data between the modules.
Version: v2024.10.0
Version: v2024.12.0
"""

import asyncio
import logging

from .const import (
from custom_components.mqtt_vacuum_camera.const import (
ATTR_CALIBRATION_POINTS,
ATTR_MARGINS,
ATTR_POINTS,
Expand All @@ -33,7 +33,7 @@
CONF_ZOOM_LOCK_RATIO,
DEFAULT_VALUES,
)
from .types import Colors
from custom_components.mqtt_vacuum_camera.types import Colors

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -76,8 +76,10 @@ def __init__(self, file_name):
self.vacuum_status_position: bool = True # Vacuum status text image top
self.snapshot_take = False # Take snapshot
self.vacuum_error = None # Vacuum error
self.vacuum_api = None # Vacuum API
self.vac_json_id = None # Vacuum json id
self.margins = "100" # Image margins
self.obstacles_data = None # Obstacles data
self.offset_top = 0 # Image offset top
self.offset_down = 0 # Image offset down
self.offset_left = 0 # Image offset left
Expand Down Expand Up @@ -121,7 +123,7 @@ async def batch_get(self, *args):
return {key: getattr(self, key) for key in args}

def generate_attributes(self) -> dict:
"""Generate and return the shared attributes dictionary."""
"""Generate and return the shared attribute's dictionary."""
attrs = {
ATTR_VACUUM_BATTERY: f"{self.vacuum_battery}%",
ATTR_VACUUM_POSITION: self.current_room,
Expand Down
12 changes: 11 additions & 1 deletion custom_components/mqtt_vacuum_camera/valetudo/MQTT/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ def __init__(self, mqtt_topic: str, hass: HomeAssistant, camera_shared):
self._file_name = camera_shared.file_name
self._shared = camera_shared
self._room_store = RoomStore()
vacuum_identifier = self._mqtt_topic.split("/")[-1]
self.mqtt_hass_vacuum = f"homeassistant/vacuum/{vacuum_identifier}/{vacuum_identifier}_vacuum/config"
self.command_topic = (
f"{self._mqtt_topic}/hass/{self._mqtt_topic.split('/')[-1]}_vacuum/command"
f"{self._mqtt_topic}/hass/{vacuum_identifier}_vacuum/command"
)
self.rrm_command = f"{self._mqtt_topic}/command" # added for ValetudoRe
self._pkohelrs_maploader_map = None
Expand Down Expand Up @@ -397,6 +399,12 @@ async def async_message_received(self, msg) -> None:
await self.handle_pkohelrs_maploader_map(msg)
elif self._rcv_topic == f"{self._mqtt_topic}/maploader/status":
await self.handle_pkohelrs_maploader_state(msg)
elif self._rcv_topic == self.mqtt_hass_vacuum:
temp_json = await self.async_decode_mqtt_payload(msg)
self._shared.vacuum_api = temp_json.get("device", {}).get(
"configuration_url", None
)
_LOGGER.debug(f"Vacuum API URL: {self._shared.vacuum_api}")

async def async_subscribe_to_topics(self) -> None:
"""Subscribe to the MQTT topics for Hypfer and ValetudoRe."""
Expand All @@ -412,6 +420,8 @@ async def async_subscribe_to_topics(self) -> None:
topic_suffixes=DECODED_TOPICS,
add_topic=self.rrm_command,
)
# add_topic=self.mqtt_hass_vacuum, for Hypfer config data.
topics_with_default_encoding.add(self.mqtt_hass_vacuum)

for x in topics_with_none_encoding:
self._unsubscribe_handlers.append(
Expand Down
Loading

0 comments on commit 68d63ef

Please sign in to comment.