Skip to content

Commit 6b28a49

Browse files
authored
Adding Coordinator and Refactoring #251 from sca075/refactoring_camera
Adding Coordinator and Refactoring
2 parents 30fcca9 + 465ed86 commit 6b28a49

15 files changed

+206
-124
lines changed

custom_components/mqtt_vacuum_camera/__init__.py

+31-23
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
Platform,
1414
)
1515
from homeassistant.core import ServiceCall
16+
from homeassistant.config_entries import ConfigEntryState, ConfigEntry
1617
from homeassistant.exceptions import ConfigEntryNotReady
1718
from homeassistant.helpers.reload import async_register_admin_service
1819
from homeassistant.helpers.storage import STORAGE_DIR
1920

2021
from .common import (
21-
get_device_info,
22+
get_vacuum_device_info,
2223
get_entity_identifier_from_mqtt,
2324
get_vacuum_mqtt_topic,
2425
get_vacuum_unique_id_from_mqtt_topic,
@@ -31,6 +32,7 @@
3132
CONF_VACUUM_IDENTIFIERS,
3233
DOMAIN,
3334
)
35+
from .coordinator import MQTTVacuumCoordinator
3436
from .utils.files_operations import (
3537
async_clean_up_all_auto_crop_files,
3638
async_get_translations_vacuum_id,
@@ -41,32 +43,32 @@
4143
_LOGGER = logging.getLogger(__name__)
4244

4345

44-
async def options_update_listener(
45-
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
46-
):
46+
async def options_update_listener(hass: core.HomeAssistant, config_entry: ConfigEntry):
4747
"""Handle options update."""
4848
await hass.config_entries.async_reload(config_entry.entry_id)
4949

5050

51-
async def async_setup_entry(
52-
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
53-
) -> bool:
51+
async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> bool:
5452
"""Set up platform from a ConfigEntry."""
5553

5654
async def _reload_config(call: ServiceCall) -> None:
5755
"""Reload the camera platform for all entities in the integration."""
58-
_LOGGER.debug("Reloading the config entry for all camera entities")
59-
56+
_LOGGER.debug(f"Reloading the config entry for all {DOMAIN} entities")
6057
# Retrieve all config entries associated with the DOMAIN
6158
camera_entries = hass.config_entries.async_entries(DOMAIN)
6259

63-
# Iterate over each config entry
60+
# Iterate over each config entry and check if it's LOADED
6461
for camera_entry in camera_entries:
65-
_LOGGER.debug(f"Unloading entry: {camera_entry.entry_id}")
66-
await async_unload_entry(hass, camera_entry)
62+
if camera_entry.state == ConfigEntryState.LOADED:
63+
_LOGGER.debug(f"Unloading entry: {camera_entry.entry_id}")
64+
await async_unload_entry(hass, camera_entry)
6765

68-
_LOGGER.debug(f"Reloading entry: {camera_entry.entry_id}")
69-
await async_setup_entry(hass, camera_entry)
66+
_LOGGER.debug(f"Reloading entry: {camera_entry.entry_id}")
67+
await async_setup_entry(hass, camera_entry)
68+
else:
69+
_LOGGER.debug(
70+
f"Skipping entry {camera_entry.entry_id} as it is NOT_LOADED"
71+
)
7072

7173
# Optionally, trigger other reinitialization steps if needed
7274
hass.bus.async_fire(f"event_{DOMAIN}_reloaded", context=call.context)
@@ -78,10 +80,15 @@ async def reset_trims(call: ServiceCall) -> None:
7880
await hass.services.async_call(DOMAIN, "reload")
7981
hass.bus.async_fire(f"event_{DOMAIN}_reset_trims", context=call.context)
8082

83+
# Register Services
84+
hass.services.async_register(DOMAIN, "reset_trims", reset_trims)
85+
if not hass.services.has_service(DOMAIN, SERVICE_RELOAD):
86+
async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config)
87+
8188
hass.data.setdefault(DOMAIN, {})
8289
hass_data = dict(entry.data)
8390

84-
vacuum_entity_id, vacuum_device = get_device_info(
91+
vacuum_entity_id, vacuum_device = get_vacuum_device_info(
8592
hass_data[CONF_VACUUM_CONFIG_ENTRY_ID], hass
8693
)
8794

@@ -90,15 +97,21 @@ async def reset_trims(call: ServiceCall) -> None:
9097
"Unable to lookup vacuum's entity ID. Was it removed?"
9198
)
9299

100+
_LOGGER.debug(vacuum_entity_id)
93101
mqtt_topic_vacuum = get_vacuum_mqtt_topic(vacuum_entity_id, hass)
94102
if not mqtt_topic_vacuum:
95103
raise ConfigEntryNotReady("MQTT was not ready yet, automatically retrying")
96104

105+
vacuum_topic = "/".join(mqtt_topic_vacuum.split("/")[:-1])
106+
107+
data_coordinator = MQTTVacuumCoordinator(hass, entry, vacuum_topic)
108+
97109
hass_data.update(
98110
{
99-
CONF_VACUUM_CONNECTION_STRING: "/".join(mqtt_topic_vacuum.split("/")[:-1]),
111+
CONF_VACUUM_CONNECTION_STRING: vacuum_topic,
100112
CONF_VACUUM_IDENTIFIERS: vacuum_device.identifiers,
101113
CONF_UNIQUE_ID: entry.unique_id,
114+
"coordinator": data_coordinator,
102115
}
103116
)
104117

@@ -108,23 +121,18 @@ async def reset_trims(call: ServiceCall) -> None:
108121
hass_data["unsub_options_update_listener"] = unsub_options_update_listener
109122
hass.data[DOMAIN][entry.entry_id] = hass_data
110123

111-
# Register Services
112-
hass.services.async_register(DOMAIN, "reset_trims", reset_trims)
113-
if not hass.services.has_service(DOMAIN, SERVICE_RELOAD):
114-
async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config)
115-
116124
# Forward the setup to the camera platform.
117125
await hass.async_create_task(
118126
hass.config_entries.async_forward_entry_setups(entry, ["camera"])
119127
)
128+
120129
return True
121130

122131

123132
async def async_unload_entry(
124133
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
125134
) -> bool:
126135
"""Unload a config entry."""
127-
_LOGGER.debug(f"unloading {entry.entry_id}")
128136
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
129137
# Remove config entry from domain.
130138
entry_data = hass.data[DOMAIN].pop(entry.entry_id)
@@ -138,7 +146,7 @@ async def async_unload_entry(
138146

139147
# noinspection PyCallingNonCallable
140148
async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
141-
"""Set up the Valetudo Camera Custom component from yaml configuration."""
149+
"""Set up the MQTT Camera Custom component from yaml configuration."""
142150

143151
async def handle_homeassistant_stop(event):
144152
"""Handle Home Assistant stop event."""

custom_components/mqtt_vacuum_camera/camera.py

+25-45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Camera
3-
Version: v2024.09.0
3+
Version: v2024.10.0b2
44
"""
55

66
from __future__ import annotations
@@ -20,17 +20,13 @@
2020
from PIL import Image
2121
from homeassistant import config_entries, core
2222
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, CameraEntityFeature
23+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
2324
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, MATCH_ALL
2425
from homeassistant.helpers import config_validation as cv
25-
from homeassistant.helpers.entity_platform import AddEntitiesCallback
26-
from homeassistant.helpers.reload import async_setup_reload_service
2726
from homeassistant.helpers.storage import STORAGE_DIR
28-
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
2927
from psutil_home_assistant import PsutilWrapper as ProcInsp
3028
import voluptuous as vol
3129

32-
from .camera_processing import CameraProcessor
33-
from .camera_shared import CameraSharedManager
3430
from .common import get_vacuum_unique_id_from_mqtt_topic
3531
from .const import (
3632
ATTR_FRIENDLY_NAME,
@@ -45,13 +41,12 @@
4541
DEFAULT_NAME,
4642
DOMAIN,
4743
NOT_STREAMING_STATES,
48-
PLATFORMS,
4944
)
50-
from .snapshots.snapshot import Snapshots
5145
from .types import SnapshotStore
5246
from .utils.colors_man import ColorsManagment
5347
from .utils.files_operations import async_get_active_user_language, is_auth_updated
54-
from .valetudo.MQTT.connector import ValetudoConnector
48+
from .snapshots.snapshot import Snapshots
49+
from .camera_processing import CameraProcessor
5550

5651
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
5752
{
@@ -73,27 +68,16 @@ async def async_setup_entry(
7368
) -> None:
7469
"""Setup camera from a config entry created in the integrations UI."""
7570
config = hass.data[DOMAIN][config_entry.entry_id]
71+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
7672
# Update our config to and eventually add or remove option.
7773
if config_entry.options:
7874
config.update(config_entry.options)
7975

80-
camera = [ValetudoCamera(hass, config)]
76+
camera = [MQTTCamera(coordinator, config)]
8177
async_add_entities(camera, update_before_add=True)
8278

8379

84-
async def async_setup_platform(
85-
hass: core.HomeAssistant,
86-
config: ConfigType,
87-
async_add_entities: AddEntitiesCallback,
88-
discovery_info: DiscoveryInfoType | None = None,
89-
):
90-
"""Set up the camera platform."""
91-
async_add_entities([ValetudoCamera(hass, config)])
92-
93-
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
94-
95-
96-
class ValetudoCamera(Camera):
80+
class MQTTCamera(CoordinatorEntity, Camera):
9781
"""
9882
Rend the vacuum map and the vacuum state for:
9983
Valetudo Hypfer and rand256 Firmwares Vacuums maps.
@@ -103,27 +87,25 @@ class ValetudoCamera(Camera):
10387
_attr_has_entity_name = True
10488
_unrecorded_attributes = frozenset({MATCH_ALL})
10589

106-
def __init__(self, hass, device_info):
107-
super().__init__()
108-
self.hass = hass
90+
def __init__(self, coordinator, device_info):
91+
super().__init__(coordinator)
92+
Camera.__init__(self)
93+
self.hass = coordinator.hass
10994
self._state = "init"
11095
self._attr_model = "MQTT Vacuums"
11196
self._attr_brand = "MQTT Vacuum Camera"
11297
self._attr_name = "Camera"
11398
self._attr_is_on = True
11499
self._directory_path = self.hass.config.path() # get Home Assistant path
115-
self._mqtt_listen_topic = str(device_info.get(CONF_VACUUM_CONNECTION_STRING))
116-
self._shared, self._file_name = self._handle_init_shared_data(
117-
self._mqtt_listen_topic,
118-
device_info,
119-
)
100+
self._shared, self._file_name = coordinator.update_shared_data(device_info)
120101
self._start_up_logs()
121102
self._storage_path, self.snapshot_img, self.log_file = self._init_paths()
103+
self._mqtt_listen_topic = coordinator.vacuum_topic
122104
self._attr_unique_id = device_info.get(
123105
CONF_UNIQUE_ID,
124106
get_vacuum_unique_id_from_mqtt_topic(self._mqtt_listen_topic),
125107
)
126-
self._mqtt = ValetudoConnector(self._mqtt_listen_topic, self.hass, self._shared)
108+
self._mqtt = coordinator.connector
127109
self._identifiers = device_info.get(CONF_VACUUM_IDENTIFIERS)
128110
self._snapshots = Snapshots(self.hass, self._shared)
129111
self.Image = None
@@ -144,23 +126,13 @@ def __init__(self, hass, device_info):
144126
self._colours.set_initial_colours(device_info)
145127
# Create the processor for the camera.
146128
self.processor = CameraProcessor(self.hass, self._shared)
147-
self._attr_brand = "MQTT Vacuum Camera"
148129

149-
@staticmethod
150-
def _handle_init_shared_data(mqtt_listen_topic: str, device_info):
151-
"""Handle the shared data initialization."""
152-
manager, shared, file_name = None, None, None
153-
if mqtt_listen_topic:
154-
manager = CameraSharedManager(
155-
mqtt_listen_topic.split("/")[1].lower(), device_info
156-
)
157-
shared = manager.get_instance()
158-
file_name = shared.file_name
159-
_LOGGER.debug(f"Camera {file_name} Starting up..")
160-
return shared, file_name
130+
# Listen to the vacuum.start event
131+
self.hass.bus.async_listen("vacuum.start", self.handle_vacuum_start)
161132

162133
@staticmethod
163134
def _start_up_logs():
135+
"""Logs the machine running the component data"""
164136
_LOGGER.info(f"System Release: {platform.node()}, {platform.release()}")
165137
_LOGGER.info(f"System Version: {platform.version()}")
166138
_LOGGER.info(f"System Machine: {platform.machine()}")
@@ -172,6 +144,7 @@ def _start_up_logs():
172144
)
173145

174146
def _init_clear_www_folder(self):
147+
"""Remove PNG and ZIP's stored in HA config WWW"""
175148
# If enable_snapshots check if for png in www
176149
if not self._shared.enable_snapshots and os.path.isfile(
177150
f"{self._directory_path}/www/snapshot_{self._file_name}.png"
@@ -263,6 +236,13 @@ def extra_state_attributes(self) -> dict:
263236

264237
return attributes
265238

239+
async def handle_vacuum_start(self, event):
240+
"""Handle the vacuum.start event."""
241+
_LOGGER.debug(f"Received event: {event.event_type}, Data: {event.data}")
242+
243+
# Call the reset_trims service when vacuum.start event occurs
244+
await self.hass.services.async_call("mqtt_vacuum_camera", "reset_trims")
245+
266246
@property
267247
def should_poll(self) -> bool:
268248
"""ON/OFF Camera Polling"""

custom_components/mqtt_vacuum_camera/camera_processing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Multiprocessing module
3-
Version: v2024.09.0
3+
Version: v2024.10.0
44
This module provide the image multiprocessing in order to
55
avoid the overload of the main_thread of Home Assistant.
66
"""

custom_components/mqtt_vacuum_camera/camera_shared.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
"""
22
Class Camera Shared.
33
Keep the data between the modules.
4-
Version: v2024.09.0
4+
Version: v2024.10.0
55
"""
66

77
import asyncio
88
import logging
99

10-
from custom_components.mqtt_vacuum_camera.types import Colors
11-
1210
from .const import (
1311
ATTR_CALIBRATION_POINTS,
1412
ATTR_MARGINS,
@@ -35,6 +33,7 @@
3533
CONF_ZOOM_LOCK_RATIO,
3634
DEFAULT_VALUES,
3735
)
36+
from .types import Colors
3837

3938
_LOGGER = logging.getLogger(__name__)
4039

@@ -160,9 +159,9 @@ def __init__(self, file_name, device_info):
160159
self.device_info = device_info
161160

162161
# Automatically initialize shared data for the instance
163-
self._init_shared_data(device_info)
162+
# self._init_shared_data(device_info)
164163

165-
def _init_shared_data(self, device_info):
164+
def update_shared_data(self, device_info):
166165
"""Initialize the shared data with device_info."""
167166
instance = self.get_instance() # Retrieve the correct instance
168167

custom_components/mqtt_vacuum_camera/common.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Common functions for the MQTT Vacuum Camera integration.
3-
Version: 2024.07.4
3+
Version: 2024.10.0
44
"""
55

66
from __future__ import annotations
@@ -20,7 +20,7 @@
2020
_LOGGER = logging.getLogger(__name__)
2121

2222

23-
def get_device_info(
23+
def get_vacuum_device_info(
2424
config_entry_id: str, hass: HomeAssistant
2525
) -> tuple[str, DeviceEntry] | None:
2626
"""
@@ -44,6 +44,16 @@ def get_device_info(
4444
return vacuum_entity_id, vacuum_device
4545

4646

47+
def get_camera_device_info(hass, entry):
48+
"""Fetch the device info from the device registry based on entry_id or identifier."""
49+
camera_entry = dict(hass.config_entries.async_get_entry(str(entry.entry_id)).data)
50+
camera_entry_options = dict(
51+
hass.config_entries.async_get_entry(str(entry.entry_id)).options
52+
)
53+
camera_entry.update(camera_entry_options)
54+
return camera_entry
55+
56+
4757
def get_entity_identifier_from_mqtt(
4858
mqtt_identifier: str, hass: HomeAssistant
4959
) -> str | None:

0 commit comments

Comments
 (0)