Skip to content

Commit

Permalink
Added sensor.py and optimized coordinator.py to return the sensors va…
Browse files Browse the repository at this point in the history
…lues

Signed-off-by: [email protected] <[email protected]>
  • Loading branch information
sca075 committed Sep 19, 2024
1 parent 6b78fab commit 90ca548
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 11 deletions.
9 changes: 4 additions & 5 deletions custom_components/mqtt_vacuum_camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
)
from homeassistant.core import ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.reload import async_register_admin_service
from homeassistant.helpers.storage import STORAGE_DIR

Expand All @@ -40,8 +39,7 @@
async_rename_room_description,
)

PLATFORMS = [Platform.CAMERA]
CONFIG_SCHEMA = cv.config_entry_only_config_schema
PLATFORMS = [Platform.CAMERA, Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -99,6 +97,7 @@ async def reset_trims(call: ServiceCall) -> None:
"Unable to lookup vacuum's entity ID. Was it removed?"
)

_LOGGER.debug(vacuum_entity_id)
mqtt_topic_vacuum = get_vacuum_mqtt_topic(vacuum_entity_id, hass)
if not mqtt_topic_vacuum:
raise ConfigEntryNotReady("MQTT was not ready yet, automatically retrying")
Expand All @@ -124,14 +123,14 @@ async def reset_trims(call: ServiceCall) -> None:

# Forward the setup to the camera platform.
await hass.async_create_task(
hass.config_entries.async_forward_entry_setups(entry, ["camera"])
hass.config_entries.async_forward_entry_setups(entry, ["camera", "sensor"])
)

return True


async def async_unload_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
Expand Down
75 changes: 69 additions & 6 deletions custom_components/mqtt_vacuum_camera/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,30 @@
from .const import DEFAULT_NAME
from .valetudo.MQTT.connector import ValetudoConnector

SENSOR_NO_DATA = {
"mainBrush": 0,
"sideBrush": 0,
"filter": 0,
"sensor": 0,
"currentCleanTime": 0,
"currentCleanArea": 0,
"cleanTime": 0,
"cleanArea": 0,
"cleanCount": 0,
}

_LOGGER = logging.getLogger(__name__)


class MQTTVacuumCoordinator(DataUpdateCoordinator):
"""Coordinator for MQTT Vacuum Camera."""

def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
vacuum_topic: str,
polling_interval=timedelta(seconds=3),
self,
hass: HomeAssistant,
entry: ConfigEntry,
vacuum_topic: str,
polling_interval=timedelta(seconds=3),
):
"""Initialize the coordinator."""
super().__init__(
Expand All @@ -45,11 +57,29 @@ def __init__(
self.file_name: str = ""
self.connector: Optional[ValetudoConnector] = None
self.in_sync_with_camera: bool = False
self.sensor_data = SENSOR_NO_DATA

# Initialize shared data and MQTT connector
self.shared, self.file_name = self._init_shared_data(self.vacuum_topic)
self.stat_up_mqtt()

async def _async_update_data(self):
"""Fetch data from the MQTT topics for sensors."""
try:
async with async_timeout.timeout(10):
# Fetch and process sensor data from the MQTT connector
sensor_data = await self.connector.get_rand256_attributes()
if sensor_data:
# Format the data before returning it
self.sensor_data = await self.async_update_sensor_data(sensor_data)
# self.sensor_data = temp_data
return self.sensor_data
return self.sensor_data
except Exception as err:
_LOGGER.error(f"Error fetching sensor data: {err}")
raise UpdateFailed(f"Error fetching sensor data: {err}")


def _init_shared_data(self, mqtt_listen_topic: str) -> tuple[CameraShared, str]:
"""
Initialize the shared data.
Expand Down Expand Up @@ -96,7 +126,7 @@ def update_shared_data(self, dev_info: DeviceInfo) -> tuple[CameraShared, str]:
self.in_sync_with_camera = True
return self.shared, self.file_name

async def _async_update_data(self, process: bool = True):
async def async_update_camera_data(self, process: bool = True):
"""
Fetch data from the MQTT topics.
Expand All @@ -115,3 +145,36 @@ async def _async_update_data(self, process: bool = True):
except Exception as err:
_LOGGER.error(f"Error communicating with MQTT or processing data: {err}")
raise UpdateFailed(f"Error communicating with MQTT: {err}") from err

async def async_update_sensor_data(self, sensor_data):
"""
Update the sensor data format before sending to the sensors.
Args:
sensor_data: The raw sensor data from MQTT.
Returns:
A dictionary with formatted sensor data.
"""
if sensor_data:
# Assume sensor_data is a dictionary or transform it into the expected format
battery_level = await self.connector.get_battery_level()
vacuum_state = await self.connector.get_vacuum_status()
formatted_data = {
"mainBrush": sensor_data.get("mainBrush", 0),
"sideBrush": sensor_data.get("sideBrush", 0),
"filter": sensor_data.get("filter", 0),
"currentCleanTime": sensor_data.get("currentCleanTime", 0),
"currentCleanArea": sensor_data.get("currentCleanArea", 0),
"cleanTime": sensor_data.get("cleanTime", 0),
"cleanArea": sensor_data.get("cleanArea", 0),
"cleanCount": sensor_data.get("cleanCount", 0),
"battery": battery_level,
"state": vacuum_state,
"last_run_stats": sensor_data.get("last_run_stats", {}),
"last_bin_out": sensor_data.get("last_bin_out", 0),
"last_bin_full": sensor_data.get("last_bin_full", 0),
"last_loaded_map": sensor_data.get("last_loaded_map", "None"),
}
return formatted_data
return SENSOR_NO_DATA
234 changes: 234 additions & 0 deletions custom_components/mqtt_vacuum_camera/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
"""Sensors for Rand256."""
from __future__ import annotations

import logging
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import AREA_SQUARE_METERS, PERCENTAGE, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .coordinator import MQTTVacuumCoordinator
SCAN_INTERVAL = timedelta(seconds=3)
SENSOR_NO_DATA = {
"mainBrush": 0,
"sideBrush": 0,
"filter": 0,
"sensor": 0,
"currentCleanTime": 0,
"currentCleanArea": 0,
"cleanTime": 0,
"cleanArea": 0,
"cleanCount": 0,
}
_LOGGER = logging.getLogger(__name__)

@dataclass
class VacuumSensorDescription(SensorEntityDescription):
"""A class that describes vacuum sensor entities."""
attributes: tuple = ()
parent_key: str = None
keys: list[str] = None
value: Callable = None

SENSOR_TYPES = {
"consumable_main_brush": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.SECONDS,
key="mainBrush",
icon="mdi:brush",
device_class=SensorDeviceClass.DURATION,
name="Main brush",
entity_category=EntityCategory.DIAGNOSTIC,
),
"consumable_side_brush": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.SECONDS,
key="sideBrush",
icon="mdi:brush",
device_class=SensorDeviceClass.DURATION,
name="Side brush",
entity_category=EntityCategory.DIAGNOSTIC,
),
"consumable_filter": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.SECONDS,
key="filter",
icon="mdi:air-filter",
device_class=SensorDeviceClass.DURATION,
name="Filter",
entity_category=EntityCategory.DIAGNOSTIC,
),
"battery": VacuumSensorDescription(
native_unit_of_measurement=PERCENTAGE,
key="battery",
icon="mdi:battery",
name="Battery",
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
),
"current_clean_time": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.SECONDS,
key="currentCleanTime",
icon="mdi:timer-sand",
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.DURATION,
name="Current clean time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"current_clean_area": VacuumSensorDescription(
native_unit_of_measurement=AREA_SQUARE_METERS,
key="currentCleanArea",
icon="mdi:texture-box",
name="Current clean area",
entity_category=EntityCategory.DIAGNOSTIC,
),
"clean_count": VacuumSensorDescription(
native_unit_of_measurement="",
key="cleanCount",
icon="mdi:counter",
name="Total clean count",
entity_category=EntityCategory.DIAGNOSTIC,
),
"clean_time": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.MINUTES.SECONDS,
key="cleanTime",
icon="mdi:timer-sand",
state_class=SensorStateClass.TOTAL_INCREASING,
name="Total clean time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"state": VacuumSensorDescription(
key="state",
icon="mdi:robot-vacuum",
name="Vacuum state",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_run_start": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.HOURS.MINUTES.SECONDS,
key="last_run_stats.startTime",
icon="mdi:clock-start",
name="Last run start time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_run_end": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.HOURS,
key="last_run_stats.endTime",
icon="mdi:clock-end",
name="Last run end time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_run_duration": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.MINUTES,
key="last_run_stats.duration",
icon="mdi:timer",
name="Last run duration",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_run_area": VacuumSensorDescription(
native_unit_of_measurement=AREA_SQUARE_METERS,
key="last_run_stats.area",
icon="mdi:texture-box",
name="Last run area",
entity_category=EntityCategory.DIAGNOSTIC,
),
# Sensors for bin and map-related data
"last_bin_out": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.DAYS,
key="last_bin_out",
icon="mdi:delete",
name="Last bin out time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_bin_full": VacuumSensorDescription(
native_unit_of_measurement=UnitOfTime.DAYS,
key="last_bin_full",
icon="mdi:delete-alert",
name="Last bin full time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"last_loaded_map": VacuumSensorDescription(
native_unit_of_measurement="",
key="last_loaded_map",
icon="mdi:map",
name="Last loaded map",
entity_category=EntityCategory.DIAGNOSTIC,
),
}


class VacuumSensor(CoordinatorEntity, SensorEntity):
"""Representation of a vacuum sensor."""

entity_description: VacuumSensorDescription

def __init__(self, coordinator: MQTTVacuumCoordinator, description: VacuumSensorDescription, sensor_type: str):
"""Initialize the vacuum sensor."""
super().__init__(coordinator)
self.entity_description = description
self.coordinator = coordinator
self._attr_native_value = None
self._attr_unique_id = f"{coordinator.file_name}_{sensor_type}"
self.entity_id = f"sensor.{coordinator.file_name}_{sensor_type}"

@callback
async def async_update(self):
"""Update the sensor's state."""
if self.coordinator.last_update_success:
await self._handle_coordinator_update()

@property
def should_poll(self) -> bool:
"""Indicate if the sensor should poll for updates."""
return True # This will tell Home Assistant to poll for data

@callback
async def _extract_attributes(self):
"""Return state attributes with valid values."""
data = self.coordinator.sensor_data
if self.entity_description.parent_key:
data = getattr(data, self.entity_description.key)
if data is None:
return
return {
attr: getattr(data, attr)
for attr in self.entity_description.attributes
if hasattr(data, attr)
}

@callback
async def _handle_coordinator_update(self):
"""Fetch the latest state from the coordinator and update the sensor."""
data = self.coordinator.sensor_data
_LOGGER.debug(f"{self.coordinator.file_name} getting sensors update: {data}")
if data is None:
data = SENSOR_NO_DATA

# Fetch the value based on the key in the description
native_value = data.get(self.entity_description.key, 0)
_LOGGER.debug(f"{self.entity_description.key}, return {native_value}")

if native_value is not None:
self._attr_native_value = native_value
else:
self._attr_native_value = 0 # Set to None if the value is missing or invalid

self.async_write_ha_state()


async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up vacuum sensors based on a config entry."""
coordinator = hass.data["mqtt_vacuum_camera"][config_entry.entry_id]["coordinator"]

# Create and add sensor entities
sensors = []
for sensor_type, description in SENSOR_TYPES.items():
sensors.append(VacuumSensor(coordinator, description, sensor_type))

async_add_entities(sensors, update_before_add=False)
Loading

0 comments on commit 90ca548

Please sign in to comment.