Dateien nach "/" hochladen
This commit is contained in:
95
config_flow.py
Normal file
95
config_flow.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
"""Config flow for Notification Store."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_STORE_PATH,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DEFAULT_STORE_PATH,
|
||||||
|
DOMAIN,
|
||||||
|
MIN_SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationStoreConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Notification Store."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input: dict[str, Any] | None = None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
await self.async_set_unique_id(DOMAIN)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
scan_interval = int(user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
|
||||||
|
if scan_interval < MIN_SCAN_INTERVAL:
|
||||||
|
errors[CONF_SCAN_INTERVAL] = "scan_interval_too_low"
|
||||||
|
else:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input.get(CONF_NAME, DEFAULT_NAME),
|
||||||
|
data=user_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME, default=DEFAULT_NAME): str,
|
||||||
|
vol.Required(CONF_STORE_PATH, default=DEFAULT_STORE_PATH): str,
|
||||||
|
vol.Required(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=schema,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def async_get_options_flow(config_entry: config_entries.ConfigEntry):
|
||||||
|
"""Create the options flow."""
|
||||||
|
return NotificationStoreOptionsFlow(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationStoreOptionsFlow(config_entries.OptionsFlow):
|
||||||
|
"""Handle options for Notification Store."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||||
|
"""Initialize options flow."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input: dict[str, Any] | None = None):
|
||||||
|
"""Manage options."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
current = {**self.config_entry.data, **self.config_entry.options}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
scan_interval = int(user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
|
||||||
|
if scan_interval < MIN_SCAN_INTERVAL:
|
||||||
|
errors[CONF_SCAN_INTERVAL] = "scan_interval_too_low"
|
||||||
|
else:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_STORE_PATH, default=current.get(CONF_STORE_PATH, DEFAULT_STORE_PATH)): str,
|
||||||
|
vol.Required(CONF_SCAN_INTERVAL, default=current.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)): int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=schema,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
58
const.py
Normal file
58
const.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""Constants for the Notification Store integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
DOMAIN = "notification_store"
|
||||||
|
NAME = "Notification Store"
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Dashboard Notifications"
|
||||||
|
DEFAULT_SENSOR_NAME = "Items"
|
||||||
|
DEFAULT_STORE_PATH = "notification_store/notifications.json"
|
||||||
|
DEFAULT_SCAN_INTERVAL = 30
|
||||||
|
MIN_SCAN_INTERVAL = 5
|
||||||
|
|
||||||
|
CONF_STORE_PATH = "store_path"
|
||||||
|
CONF_SCAN_INTERVAL = "scan_interval"
|
||||||
|
CONF_NAME = "name"
|
||||||
|
|
||||||
|
ATTR_NOTIFICATIONS = "notifications"
|
||||||
|
ATTR_COUNT = "count"
|
||||||
|
ATTR_TOTAL = "total"
|
||||||
|
ATTR_LATEST = "latest"
|
||||||
|
ATTR_LATEST_UNREAD = "latest_unread"
|
||||||
|
|
||||||
|
EVENT_NOTIFICATION_ADDED = "notification_store_notification_added"
|
||||||
|
EVENT_NOTIFICATION_READ = "notification_store_notification_read"
|
||||||
|
EVENT_NOTIFICATIONS_CLEARED = "notification_store_notifications_cleared"
|
||||||
|
|
||||||
|
SERVICE_ADD_NOTIFICATION = "add_notification"
|
||||||
|
SERVICE_MARK_ALL_READ = "mark_all_read"
|
||||||
|
SERVICE_MARK_ONE_READ = "mark_one_read"
|
||||||
|
SERVICE_MARK_INDEX_READ = "mark_index_read"
|
||||||
|
SERVICE_CLEAR_READ = "clear_read"
|
||||||
|
SERVICE_CLEAR_ALL = "clear_all"
|
||||||
|
SERVICE_RELOAD = "reload"
|
||||||
|
|
||||||
|
FIELD_TITLE = "title"
|
||||||
|
FIELD_MESSAGE = "message"
|
||||||
|
FIELD_LEVEL = "level"
|
||||||
|
FIELD_TAG = "tag"
|
||||||
|
FIELD_SOURCE = "source"
|
||||||
|
FIELD_ICON = "icon"
|
||||||
|
FIELD_NOTIFICATION_ID = "notification_id"
|
||||||
|
FIELD_INDEX = "index"
|
||||||
|
FIELD_ENTRY_ID = "entry_id"
|
||||||
|
|
||||||
|
LEVEL_INFO = "info"
|
||||||
|
LEVEL_SUCCESS = "success"
|
||||||
|
LEVEL_WARNING = "warning"
|
||||||
|
LEVEL_CRITICAL = "critical"
|
||||||
|
LEVELS = [LEVEL_INFO, LEVEL_SUCCESS, LEVEL_WARNING, LEVEL_CRITICAL]
|
||||||
|
|
||||||
|
DEFAULT_UPDATE_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
|
||||||
54
coordinator.py
Normal file
54
coordinator.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""Coordinator for the Notification Store integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from .const import DOMAIN, NAME
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
from .store import NotificationStore
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationStoreCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""Coordinate notification store updates."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
store: NotificationStore,
|
||||||
|
update_interval: timedelta,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
logger=_LOGGER,
|
||||||
|
name=NAME,
|
||||||
|
update_interval=update_interval,
|
||||||
|
always_update=False,
|
||||||
|
)
|
||||||
|
self.store = store
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Fetch data from the notification store."""
|
||||||
|
return await self.store.async_load()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notifications(self) -> list[dict[str, Any]]:
|
||||||
|
"""Return all notifications."""
|
||||||
|
return list((self.data or {}).get("notifications", []))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unread_notifications(self) -> list[dict[str, Any]]:
|
||||||
|
"""Return unread notifications."""
|
||||||
|
return [
|
||||||
|
item
|
||||||
|
for item in self.notifications
|
||||||
|
if item.get("status", "unread") == "unread"
|
||||||
|
]
|
||||||
14
manifest.json
Normal file
14
manifest.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"domain": "notification_store",
|
||||||
|
"name": "Notification Store",
|
||||||
|
"codeowners": [
|
||||||
|
"@torsten_brendgen"
|
||||||
|
],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://github.com/torsten-brendgen/notification-store",
|
||||||
|
"integration_type": "service",
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"issue_tracker": "https://github.com/torsten-brendgen/notification-store/issues",
|
||||||
|
"requirements": [],
|
||||||
|
"version": "0.1.3"
|
||||||
|
}
|
||||||
91
sensor.py
Normal file
91
sensor.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
"""Sensor platform for Notification Store."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.util import slugify
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTR_COUNT,
|
||||||
|
ATTR_LATEST,
|
||||||
|
ATTR_LATEST_UNREAD,
|
||||||
|
ATTR_NOTIFICATIONS,
|
||||||
|
ATTR_TOTAL,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_SENSOR_NAME,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from .coordinator import NotificationStoreCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Notification Store sensor."""
|
||||||
|
coordinator: NotificationStoreCoordinator = hass.data[DOMAIN]["coordinators"][entry.entry_id]
|
||||||
|
|
||||||
|
device_name = entry.data.get(CONF_NAME, DEFAULT_NAME)
|
||||||
|
sensor_name = DEFAULT_SENSOR_NAME
|
||||||
|
unique_id = f"{entry.entry_id}_notifications"
|
||||||
|
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
current_entity_id = registry.async_get_entity_id("sensor", DOMAIN, unique_id)
|
||||||
|
target_entity_id = f"sensor.{slugify(device_name)}_{slugify(sensor_name)}"
|
||||||
|
|
||||||
|
if (
|
||||||
|
current_entity_id
|
||||||
|
and current_entity_id != target_entity_id
|
||||||
|
and not registry.async_is_registered(target_entity_id)
|
||||||
|
):
|
||||||
|
registry.async_update_entity(current_entity_id, new_entity_id=target_entity_id)
|
||||||
|
|
||||||
|
async_add_entities([NotificationStoreSensor(coordinator, entry, unique_id, sensor_name)])
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationStoreSensor(CoordinatorEntity[NotificationStoreCoordinator], SensorEntity):
|
||||||
|
"""Sensor exposing unread notification count."""
|
||||||
|
|
||||||
|
_attr_icon = "mdi:bell-ring-outline"
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(self, coordinator: NotificationStoreCoordinator, entry: ConfigEntry, unique_id: str, sensor_name: str) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.entry = entry
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
|
self._attr_name = sensor_name
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, entry.entry_id)},
|
||||||
|
name=entry.title,
|
||||||
|
manufacturer="Torsten Brendgen",
|
||||||
|
model="Notification Store",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> int:
|
||||||
|
"""Return unread notification count."""
|
||||||
|
return len(self.coordinator.unread_notifications)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return notification attributes."""
|
||||||
|
notifications = self.coordinator.notifications
|
||||||
|
unread = self.coordinator.unread_notifications
|
||||||
|
return {
|
||||||
|
ATTR_COUNT: len(unread),
|
||||||
|
ATTR_TOTAL: len(notifications),
|
||||||
|
ATTR_NOTIFICATIONS: unread,
|
||||||
|
ATTR_LATEST: notifications[0] if notifications else None,
|
||||||
|
ATTR_LATEST_UNREAD: unread[0] if unread else None,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user