mirror of
https://github.com/mx42/home-assistant-ecocito.git
synced 2026-01-14 13:59:50 +01:00
feat: grab all collection events in 1 call and dispatch in several sensors after
It becomes a bit of a mess with a lot of dynamically named sensors, and possibly a lot of empty ones. It adds a bit of a burden on the user to clean-up the mess. :/
This commit is contained in:
@@ -27,11 +27,9 @@ PLATFORMS: list[Platform] = [Platform.SENSOR]
|
|||||||
class EcocitoData:
|
class EcocitoData:
|
||||||
"""Ecocito data type."""
|
"""Ecocito data type."""
|
||||||
|
|
||||||
garbage_collections: CollectionDataUpdateCoordinator | None
|
collection_types: dict[int, str]
|
||||||
garbage_collections_previous: CollectionDataUpdateCoordinator | None
|
collections: CollectionDataUpdateCoordinator
|
||||||
recycling_collections: CollectionDataUpdateCoordinator | None
|
waste_depot_visits: WasteDepotVisitsDataUpdateCoordinator
|
||||||
recycling_collections_previous: CollectionDataUpdateCoordinator | None
|
|
||||||
waste_depot_visits: WasteDepotVisitsDataUpdateCoordinator # Maybe we could have an optional here if we had some checkbox config?
|
|
||||||
|
|
||||||
|
|
||||||
type EcocitoConfigEntry = ConfigEntry[EcocitoData]
|
type EcocitoConfigEntry = ConfigEntry[EcocitoData]
|
||||||
@@ -46,31 +44,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: EcocitoConfigEntry) -> b
|
|||||||
)
|
)
|
||||||
await client.authenticate()
|
await client.authenticate()
|
||||||
|
|
||||||
garbage_id = entry.data.get(ECOCITO_GARBAGE_TYPE)
|
|
||||||
recycle_id = entry.data.get(ECOCITO_RECYCLE_TYPE)
|
|
||||||
refresh_time = entry.data.get(ECOCITO_REFRESH_MIN_KEY, ECOCITO_DEFAULT_REFRESH_MIN)
|
refresh_time = entry.data.get(ECOCITO_REFRESH_MIN_KEY, ECOCITO_DEFAULT_REFRESH_MIN)
|
||||||
|
|
||||||
|
collect_types = await client.get_collection_types()
|
||||||
|
|
||||||
data = EcocitoData(
|
data = EcocitoData(
|
||||||
garbage_collections=CollectionDataUpdateCoordinator(
|
collection_types = collect_types,
|
||||||
hass, client, 0, garbage_id, refresh_time
|
collections = CollectionDataUpdateCoordinator(
|
||||||
) if garbage_id is not None else None,
|
hass, client, refresh_time
|
||||||
garbage_collections_previous=CollectionDataUpdateCoordinator(
|
),
|
||||||
hass, client, -1, garbage_id, refresh_time
|
|
||||||
) if garbage_id is not None else None,
|
|
||||||
recycling_collections=CollectionDataUpdateCoordinator(
|
|
||||||
hass, client, 0, recycle_id, refresh_time
|
|
||||||
) if recycle_id is not None else None,
|
|
||||||
recycling_collections_previous=CollectionDataUpdateCoordinator(
|
|
||||||
hass, client, -1, recycle_id, refresh_time
|
|
||||||
) if recycle_id is not None else None,
|
|
||||||
waste_depot_visits=WasteDepotVisitsDataUpdateCoordinator(
|
waste_depot_visits=WasteDepotVisitsDataUpdateCoordinator(
|
||||||
hass, client, 0, refresh_time
|
hass, client, 0, refresh_time
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for field in fields(data):
|
await data.collections.async_config_entry_first_refresh()
|
||||||
coordinator = getattr(data, field.name)
|
await data.waste_depot_visits.async_config_entry_first_refresh()
|
||||||
if coordinator:
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
|
||||||
entry.runtime_data = data
|
entry.runtime_data = data
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ class EcocitoClient:
|
|||||||
) from e
|
) from e
|
||||||
|
|
||||||
async def get_collection_events(
|
async def get_collection_events(
|
||||||
self, event_type: str, year: int
|
self, year: int
|
||||||
) -> list[CollectionEvent]:
|
) -> dict[str, dict[str, CollectionEvent]]:
|
||||||
"""Return the list of the collection events for a type and a year."""
|
"""Return the list of the collection events for a type and a year."""
|
||||||
async with aiohttp.ClientSession(cookie_jar=self._cookies) as session:
|
async with aiohttp.ClientSession(cookie_jar=self._cookies) as session:
|
||||||
try:
|
try:
|
||||||
@@ -119,8 +119,8 @@ class EcocitoClient:
|
|||||||
"skip": "0",
|
"skip": "0",
|
||||||
"take": "1000",
|
"take": "1000",
|
||||||
"requireTotalCount": "true",
|
"requireTotalCount": "true",
|
||||||
"idMatiere": str(event_type),
|
"idMatiere": str(-1),
|
||||||
"dateDebut": f"{year}-01-01T00:00:00.000Z",
|
"dateDebut": f"{year - 1}-01-01T00:00:00.000Z",
|
||||||
"dateFin": f"{year}-12-31T23:59:59.999Z",
|
"dateFin": f"{year}-12-31T23:59:59.999Z",
|
||||||
},
|
},
|
||||||
raise_for_status=True,
|
raise_for_status=True,
|
||||||
@@ -128,15 +128,24 @@ class EcocitoClient:
|
|||||||
content = await response.text()
|
content = await response.text()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return [
|
result = {}
|
||||||
CollectionEvent(
|
for row in json.loads(content).get("data", []):
|
||||||
type=event_type,
|
date = datetime.fromisoformat(row["DATE_DONNEE"])
|
||||||
date=datetime.fromisoformat(row["DATE_DONNEE"]),
|
y = "current" if date.year == year else "last"
|
||||||
location=row["LIBELLE_ADRESSE"],
|
matter = row["ID_MATIERE"]
|
||||||
quantity=row["QUANTITE_NETTE"],
|
if y not in result:
|
||||||
|
result[y] = {}
|
||||||
|
if matter not in result[y]:
|
||||||
|
result[y][matter] = []
|
||||||
|
result[y][matter].append(
|
||||||
|
CollectionEvent(
|
||||||
|
type=matter,
|
||||||
|
date=date,
|
||||||
|
location=row["LIBELLE_ADRESSE"],
|
||||||
|
quantity=row["QUANTITE_NETTE"],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for row in json.loads(content).get("data", [])
|
return result
|
||||||
]
|
|
||||||
except Exception as e: # noqa: BLE001
|
except Exception as e: # noqa: BLE001
|
||||||
await self._handle_error(content, e)
|
await self._handle_error(content, e)
|
||||||
|
|
||||||
|
|||||||
@@ -64,21 +64,18 @@ class CollectionDataUpdateCoordinator(
|
|||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
client: EcocitoClient,
|
client: EcocitoClient,
|
||||||
year_offset: int,
|
|
||||||
coll_type_id: int,
|
|
||||||
refresh_time: int
|
refresh_time: int
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the coordinator."""
|
"""Initialize the coordinator."""
|
||||||
|
self.cached: dict[str, dict[str, list[CollectionEvent]]] | None = None
|
||||||
super().__init__(hass, client, refresh_time)
|
super().__init__(hass, client, refresh_time)
|
||||||
self._year_offset = year_offset
|
|
||||||
self._coll_type_id = coll_type_id
|
|
||||||
|
|
||||||
async def _fetch_data(self) -> list[CollectionEvent]:
|
async def _fetch_data(self) -> list[CollectionEvent]:
|
||||||
"""Fetch the data."""
|
"""Fetch the data."""
|
||||||
return await self.client.get_collection_events(
|
if self.cached is None:
|
||||||
str(self._coll_type_id),
|
self.cached = await self.client.get_collection_events(datetime.now(tz=self._time_zone).year)
|
||||||
datetime.now(tz=self._time_zone).year + self._year_offset,
|
|
||||||
)
|
return self.cached
|
||||||
|
|
||||||
|
|
||||||
class WasteDepotVisitsDataUpdateCoordinator(
|
class WasteDepotVisitsDataUpdateCoordinator(
|
||||||
|
|||||||
@@ -9,5 +9,8 @@
|
|||||||
"issue_tracker": "https://github.com/rclsilver/home-assistant-ecocito/issues",
|
"issue_tracker": "https://github.com/rclsilver/home-assistant-ecocito/issues",
|
||||||
"loggers": ["custom_components.ecocito"],
|
"loggers": ["custom_components.ecocito"],
|
||||||
"single_config_entry": true,
|
"single_config_entry": true,
|
||||||
"version": "0.0.0"
|
"version": "0.0.0",
|
||||||
|
"requirements": [
|
||||||
|
"unidecode>=1.3.8"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import unidecode
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Generic
|
from typing import Any, Generic
|
||||||
@@ -18,21 +19,25 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from . import EcocitoConfigEntry
|
from . import EcocitoConfigEntry
|
||||||
from .client import CollectionEvent, EcocitoEvent
|
from .client import CollectionEvent, EcocitoEvent
|
||||||
from .const import DEVICE_ATTRIBUTION
|
from .const import DEVICE_ATTRIBUTION, LOGGER
|
||||||
from .coordinator import T
|
from .coordinator import T
|
||||||
from .entity import EcocitoEntity
|
from .entity import EcocitoEntity
|
||||||
|
|
||||||
|
|
||||||
def get_count(data: list[Any]) -> int:
|
def get_count(data: list[Any]) -> int:
|
||||||
"""Return the size of the given list."""
|
"""Return the size of the given list."""
|
||||||
return len(data)
|
if data:
|
||||||
|
return len(data)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_event_collections_weight(data: list[CollectionEvent]) -> int:
|
def get_event_collections_weight(data: list[CollectionEvent]) -> int:
|
||||||
"""Return the sum of the events quantities."""
|
"""Return the sum of the events quantities."""
|
||||||
result = 0
|
result = 0
|
||||||
for row in data:
|
if data:
|
||||||
result += row.quantity
|
for row in data:
|
||||||
|
result += row.quantity
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -59,152 +64,91 @@ class EcocitoSensorEntityDescription(SensorEntityDescription, Generic[T]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
year: str | None,
|
||||||
|
mat_type: str | None,
|
||||||
value_fn: Callable[[T], str | int],
|
value_fn: Callable[[T], str | int],
|
||||||
last_updated_fn: Callable[[T], datetime],
|
last_updated_fn: Callable[[T], datetime],
|
||||||
*args: tuple,
|
*args: tuple,
|
||||||
**kwargs: dict[str, any],
|
**kwargs: dict[str, any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Build a Ecocito sensor."""
|
"""Build a Ecocito sensor."""
|
||||||
|
self.mat_type = mat_type
|
||||||
|
self.year = year
|
||||||
self.value_fn = value_fn
|
self.value_fn = value_fn
|
||||||
self.last_updated_fn = last_updated_fn
|
self.last_updated_fn = last_updated_fn
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def cleanup_name(matter_name) -> str:
|
||||||
|
matter_name = str(matter_name).replace(" ", "_")
|
||||||
|
return unidecode.unidecode(matter_name, "utf-8").lower()
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[tuple[str, EcocitoSensorEntityDescription]] = (
|
def build_sensor_types(mat_types) -> list[tuple[str, EcocitoSensorEntityDescription]]:
|
||||||
(
|
sensors = []
|
||||||
"garbage_collections",
|
for mat_type, mat_type_name in mat_types.items():
|
||||||
EcocitoSensorEntityDescription(
|
if int(mat_type) == -1:
|
||||||
key="garbage_collections_count",
|
continue
|
||||||
translation_key="garbage_collections_count",
|
name = cleanup_name(str(mat_type_name))
|
||||||
value_fn=get_count,
|
for year in ["current", "last"]:
|
||||||
last_updated_fn=get_latest_date,
|
sensors.append((
|
||||||
icon="mdi:trash-can",
|
"collections",
|
||||||
state_class=SensorStateClass.TOTAL,
|
EcocitoSensorEntityDescription(
|
||||||
),
|
name=f"{name}_collections_count_{year}_year",
|
||||||
),
|
key=f"{name}_collections_count_{year}_year",
|
||||||
(
|
# translation_key=f"garbage_collections_count_{year}_year",
|
||||||
"garbage_collections",
|
year=year,
|
||||||
EcocitoSensorEntityDescription(
|
mat_type=mat_type,
|
||||||
key="garbage_collections_total",
|
value_fn=get_count,
|
||||||
translation_key="garbage_collections_total",
|
last_updated_fn=get_latest_date,
|
||||||
icon="mdi:trash-can",
|
icon="mdi:trash-can",
|
||||||
value_fn=get_event_collections_weight,
|
state_class=SensorStateClass.TOTAL,
|
||||||
last_updated_fn=get_latest_date,
|
),
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
))
|
||||||
state_class=SensorStateClass.TOTAL,
|
sensors.append((
|
||||||
suggested_display_precision=0,
|
"collections",
|
||||||
),
|
EcocitoSensorEntityDescription(
|
||||||
),
|
name=f"{name}_collections_total_{year}_year",
|
||||||
(
|
key=f"{name}_collections_total_{year}_year",
|
||||||
"garbage_collections",
|
# translation_key=f"garbage_collections_total_{year}_year",
|
||||||
EcocitoSensorEntityDescription(
|
icon="mdi:trash-can",
|
||||||
key="latest_garbage_collections",
|
year=year,
|
||||||
translation_key="latest_garbage_collection",
|
mat_type=mat_type,
|
||||||
icon="mdi:trash-can",
|
value_fn=get_event_collections_weight,
|
||||||
value_fn=get_latest_event_collection_weight,
|
last_updated_fn=get_latest_date,
|
||||||
last_updated_fn=get_latest_date,
|
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
state_class=SensorStateClass.TOTAL,
|
||||||
state_class=SensorStateClass.TOTAL,
|
suggested_display_precision=0,
|
||||||
suggested_display_precision=0,
|
),
|
||||||
),
|
))
|
||||||
),
|
sensors.append((
|
||||||
(
|
"collections",
|
||||||
"garbage_collections_previous",
|
EcocitoSensorEntityDescription(
|
||||||
EcocitoSensorEntityDescription(
|
key=f"latest_{name}_collection",
|
||||||
key="garbage_collections_count_previous",
|
name=f"latest_{name}_collection",
|
||||||
translation_key="previous_garbage_collections_count",
|
# translation_key=f"latest_garbage_collection",
|
||||||
icon="mdi:trash-can",
|
icon="mdi:trash-can",
|
||||||
value_fn=get_count,
|
year=year,
|
||||||
last_updated_fn=get_latest_date,
|
mat_type=mat_type,
|
||||||
state_class=SensorStateClass.TOTAL,
|
value_fn=get_latest_event_collection_weight,
|
||||||
),
|
last_updated_fn=get_latest_date,
|
||||||
),
|
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
||||||
(
|
state_class=SensorStateClass.TOTAL,
|
||||||
"garbage_collections_previous",
|
suggested_display_precision=0,
|
||||||
EcocitoSensorEntityDescription(
|
),
|
||||||
key="garbage_collections_total_previous",
|
))
|
||||||
translation_key="previous_garbage_collections_total",
|
sensors.append((
|
||||||
icon="mdi:trash-can",
|
|
||||||
value_fn=get_event_collections_weight,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
suggested_display_precision=0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"recycling_collections",
|
|
||||||
EcocitoSensorEntityDescription(
|
|
||||||
key="recycling_collections_count",
|
|
||||||
translation_key="recycling_collections_count",
|
|
||||||
icon="mdi:recycle",
|
|
||||||
value_fn=get_count,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"recycling_collections",
|
|
||||||
EcocitoSensorEntityDescription(
|
|
||||||
key="recycling_collections_total",
|
|
||||||
translation_key="recycling_collections_total",
|
|
||||||
icon="mdi:recycle",
|
|
||||||
value_fn=get_event_collections_weight,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
suggested_display_precision=0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"recycling_collections",
|
|
||||||
EcocitoSensorEntityDescription(
|
|
||||||
key="latest_recycling_collections",
|
|
||||||
translation_key="latest_recycling_collection",
|
|
||||||
icon="mdi:recycle",
|
|
||||||
value_fn=get_latest_event_collection_weight,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
suggested_display_precision=0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"recycling_collections_previous",
|
|
||||||
EcocitoSensorEntityDescription(
|
|
||||||
key="recycling_collections_count_previous",
|
|
||||||
translation_key="previous_recycling_collections_count",
|
|
||||||
icon="mdi:recycle",
|
|
||||||
value_fn=get_count,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"recycling_collections_previous",
|
|
||||||
EcocitoSensorEntityDescription(
|
|
||||||
key="recycling_collections_total_previous",
|
|
||||||
translation_key="previous_recycling_collections_total",
|
|
||||||
icon="mdi:recycle",
|
|
||||||
value_fn=get_event_collections_weight,
|
|
||||||
last_updated_fn=get_latest_date,
|
|
||||||
unit_of_measurement=UnitOfMass.KILOGRAMS,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
suggested_display_precision=0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"waste_depot_visits",
|
"waste_depot_visits",
|
||||||
EcocitoSensorEntityDescription(
|
EcocitoSensorEntityDescription(
|
||||||
key="waste_deposit_visit",
|
key="waste_deposit_visit",
|
||||||
translation_key="waste_deposit_visit",
|
translation_key="waste_deposit_visit",
|
||||||
icon="mdi:car",
|
icon="mdi:car",
|
||||||
|
year=None,
|
||||||
|
mat_type=None,
|
||||||
value_fn=get_count,
|
value_fn=get_count,
|
||||||
last_updated_fn=get_latest_date,
|
last_updated_fn=get_latest_date,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.TOTAL,
|
||||||
),
|
),
|
||||||
),
|
))
|
||||||
)
|
return sensors
|
||||||
|
|
||||||
|
|
||||||
class EcocitoSensor(EcocitoEntity[T], SensorEntity):
|
class EcocitoSensor(EcocitoEntity[T], SensorEntity):
|
||||||
@@ -216,14 +160,25 @@ class EcocitoSensor(EcocitoEntity[T], SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> str | int:
|
def native_value(self) -> str | int:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
data = self.coordinator.data
|
||||||
|
if self.entity_description.year is not None:
|
||||||
|
data = data.get(self.entity_description.year)
|
||||||
|
if self.entity_description.mat_type is not None:
|
||||||
|
data = data.get(self.entity_description.mat_type)
|
||||||
|
|
||||||
|
return self.entity_description.value_fn(data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, str] | None:
|
def extra_state_attributes(self) -> dict[str, str] | None:
|
||||||
"""Return the state attributes of the sensor."""
|
"""Return the state attributes of the sensor."""
|
||||||
|
data = self.coordinator.data
|
||||||
|
if self.entity_description.year is not None:
|
||||||
|
data = data.get(self.entity_description.year)
|
||||||
|
if self.entity_description.mat_type is not None:
|
||||||
|
data = data.get(self.entity_description.mat_type)
|
||||||
return {
|
return {
|
||||||
"last_updated": self.entity_description.last_updated_fn(
|
"last_updated": self.entity_description.last_updated_fn(
|
||||||
self.coordinator.data
|
data
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,8 +189,10 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Ecocito sensors based on a config entry."""
|
"""Set up Ecocito sensors based on a config entry."""
|
||||||
|
|
||||||
entities: list[EcocitoSensor[Any]] = []
|
entities: list[EcocitoSensor[Any]] = []
|
||||||
for coordinator_type, description in SENSOR_TYPES:
|
mat_types = entry.runtime_data.collection_types
|
||||||
|
for coordinator_type, description in build_sensor_types(mat_types):
|
||||||
coordinator = getattr(entry.runtime_data, coordinator_type)
|
coordinator = getattr(entry.runtime_data, coordinator_type)
|
||||||
if coordinator:
|
if coordinator:
|
||||||
entities.append(EcocitoSensor(coordinator, description))
|
entities.append(EcocitoSensor(coordinator, description))
|
||||||
|
|||||||
@@ -20,19 +20,19 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"garbage_collections_count": {
|
"garbage_collections_count_current_year": {
|
||||||
"name": "Number of garbage collections"
|
"name": "Number of garbage collections"
|
||||||
},
|
},
|
||||||
"garbage_collections_total": {
|
"garbage_collections_total_current_year": {
|
||||||
"name": "Total weight of collected garbage"
|
"name": "Total weight of collected garbage"
|
||||||
},
|
},
|
||||||
"latest_garbage_collection": {
|
"latest_garbage_collection_current_year": {
|
||||||
"name": "Weight of the latest garbage collection"
|
"name": "Weight of the latest garbage collection"
|
||||||
},
|
},
|
||||||
"previous_garbage_collections_count": {
|
"garbage_collections_count_last_year": {
|
||||||
"name": "Number of garbage collections (last year)"
|
"name": "Number of garbage collections (last year)"
|
||||||
},
|
},
|
||||||
"previous_garbage_collections_total": {
|
"garbage_collections_total_last_year": {
|
||||||
"name": "Total weight of collected garbage (last year)"
|
"name": "Total weight of collected garbage (last year)"
|
||||||
},
|
},
|
||||||
"recycling_collections_count": {
|
"recycling_collections_count": {
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ colorlog==6.8.2
|
|||||||
homeassistant==2025.3.4
|
homeassistant==2025.3.4
|
||||||
pip>=21.3.1
|
pip>=21.3.1
|
||||||
ruff==0.11.0
|
ruff==0.11.0
|
||||||
|
unidecode>=1.3.8
|
||||||
|
|||||||
Reference in New Issue
Block a user