Initial commit.

This commit is contained in:
2025-04-21 21:03:30 -04:00
commit d4f58c985f
2077 changed files with 75132 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
import logging
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
_LOGGER = logging.getLogger(__name__)
# Configuration:
SIDEPANEL_TITLE = "sidepanel_title"
SIDEPANEL_ICON = "sidepanel_icon"
@config_entries.HANDLERS.register("dwains_dashboard")
class DwainsDashboardConfigFlow(config_entries.ConfigFlow):
async def async_step_user(self, user_input=None):
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_create_entry(title="", data={})
@staticmethod
@callback
def async_get_options_flow(config_entry):
return DwainsDashboardEditFlow(config_entry)
class DwainsDashboardEditFlow(config_entries.OptionsFlow):
def __init__(self, config_entry):
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
schema = {
vol.Optional(SIDEPANEL_TITLE, default=self.config_entry.options.get("sidepanel_title", "Dwains Dashboard")): str,
vol.Optional(SIDEPANEL_ICON, default=self.config_entry.options.get("sidepanel_icon", "mdi:alpha-d-box")): str,
}
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(schema)
)
@@ -0,0 +1,2 @@
DOMAIN = "dwains_dashboard"
VERSION = "3.8.0"
@@ -0,0 +1,4 @@
node_modules/
src/*
!src/translations.js
dwains-dashboard.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
const path = require('path');
module.exports = {
entry: [
'./src/dwains-navigation-card.js',
'./src/dwains-dashboard.js',
'./src/dwains-dashboard-layout.js',
'./src/dwains-homepage-card.js',
'./src/dwains-more-pages-card.js',
'./src/dwains-more-page-card.js',
'./src/dwains-edit-more-page-card.js',
'./src/dwains-notification-card.js',
'./src/dwains-house-information-card.js',
'./src/dwains-house-information-more-info-card.js',
'./src/dwains-blueprint-card.js',
'./src/dwains-devicespage-card.js',
//'./src/dwains-thermostat-card.js',
//'./src/dwains-light-card.js',
//'./src/dwains-cover-card.js',
//'./src/dwains-button-card.js',
'./src/dwains-flexbox-card.js',
'./src/dwains-heading-card.js',
'./src/dwains-create-custom-card-card.js',
'./src/dwains-edit-area-button-card.js',
'./src/dwains-edit-entity-card-card.js',
'./src/dwains-edit-entity-card.js',
'./src/dwains-edit-entity-popup-card.js',
'./src/dwains-edit-homepage-header-card.js',
'./src/dwains-edit-device-card-card.js',
'./src/dwains-edit-device-popup-card.js',
'./src/dwains-edit-device-button-card.js',
'./src/dwains-popup.js',
],
mode: 'production',
output: {
filename: 'dwains-dashboard.js',
path: path.resolve(__dirname)
},
devtool: "source-map"
};
@@ -0,0 +1,36 @@
import logging
from homeassistant.components.lovelace.dashboard import LovelaceYAML
from homeassistant.components.lovelace import _register_panel
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
def load_dashboard(hass, config_entry):
#_LOGGER.warning(config_entry.options)
#_LOGGER.warning(config_entry.options["sidepanel_title"])
sidepanel_title = "Dwains Dashboard"
sidepanel_icon = "mdi:alpha-d-box"
if("sidepanel_title" in config_entry.options):
sidepanel_title = config_entry.options["sidepanel_title"]
if("sidepanel_icon" in config_entry.options):
sidepanel_icon = config_entry.options["sidepanel_icon"]
dashboard_url = "dwains-dashboard"
dashboard_config = {
"mode": "yaml",
"icon": sidepanel_icon,
"title": sidepanel_title,
"filename": "custom_components/dwains_dashboard/lovelace/ui-lovelace.yaml",
"show_in_sidebar": True,
"require_admin": False,
}
hass.data["lovelace"].dashboards[dashboard_url] = LovelaceYAML(hass, dashboard_url, dashboard_config)
_register_panel(hass, dashboard_url, "yaml", dashboard_config, False)
@@ -0,0 +1,19 @@
import logging
from homeassistant.components.frontend import add_extra_js_url
from homeassistant.components.http import HomeAssistantHTTP
from homeassistant.components.http import StaticPathConfig
DATA_EXTRA_MODULE_URL = 'frontend_extra_module_url'
_LOGGER = logging.getLogger(__name__)
from .const import VERSION
async def load_plugins(hass, name):
#_LOGGER.warning(f"load_plugins() version: {VERSION}")
add_extra_js_url(hass, f"/dwains_dashboard/js/dwains-dashboard.js?version={VERSION}")
await hass.http.async_register_static_paths(
[StaticPathConfig("/dwains_dashboard/js", hass.config.path(f"custom_components/{name}/js"), True)]
)
@@ -0,0 +1,18 @@
# Start of the dwains dashboard ui-lovelace.yaml
#Load all settings for Dwains Dashboard
dwains_dashboard:
active: true
#For people who want to use button card templates
button_card_templates:
!include_dir_merge_named ../../../dwains-dashboard/button_card_templates
#For people who want to use apexcharts card templates
apexcharts_card_templates:
!include_dir_merge_named ../../../dwains-dashboard/apexcharts_card_templates
# Start of main lovelace theme
lovelace-background: var(--background-image)
views:
!include_dir_merge_list views/
@@ -0,0 +1,6 @@
- title: Home
icon: mdi:home
path: home
type: custom:dwains-dashboard-layout
cards:
- type: custom:homepage-card
@@ -0,0 +1,6 @@
- title: Devices
icon: mdi:format-list-bulleted-type
path: devices
type: custom:dwains-dashboard-layout
cards:
- type: custom:devices-card
@@ -0,0 +1,19 @@
# dwains_dashboard
#More_page addon view
{% if _dd_more_pages %}
{% for addon in _dd_more_pages %}
- title: {{ _dd_more_pages[addon]["name"] }}
path: more_page_{{ addon|lower|replace("'", "_")|replace(" ", "_") }}
type: custom:dwains-dashboard-layout
icon: {{ _dd_more_pages[addon]["icon"] }}
visible: true
cards:
- type: custom:more-page-card
name: {{ _dd_more_pages[addon]["name"] }}
icon: {{ _dd_more_pages[addon]["icon"] }}
show_in_navbar: {{ _dd_more_pages[addon]["show_in_navbar"] }}
foldername: {{ addon }}
card: !include ../../../../{{ _dd_more_pages[addon]["path"] }}
{% endfor %}
{% endif %}
@@ -0,0 +1,7 @@
- title: More page
path: more_page
icon: mdi:dots-horizontal
visible: true
type: custom:dwains-dashboard-layout
cards:
- type: custom:more-pages-card
@@ -0,0 +1,221 @@
import os
import logging
import json
import io
import time
import voluptuous as vol
import homeassistant.util.dt as dt_util
from collections import OrderedDict
from typing import Any, Mapping, MutableMapping, Optional
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.loader import bind_hass
from homeassistant.util import slugify
from .const import DOMAIN
ATTR_CREATED_AT = "created_at"
ATTR_MESSAGE = "message"
ATTR_NOTIFICATION_ID = "notification_id"
ATTR_TITLE = "title"
ATTR_STATUS = "status"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED = "dwains_dashboard_notifications_updated"
SERVICE_CREATE = "notification_create"
SERVICE_DISMISS = "notification_dismiss"
SERVICE_MARK_READ = "notification_mark_read"
SCHEMA_SERVICE_CREATE = vol.Schema(
{
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_NOTIFICATION_ID): cv.string,
}
)
SCHEMA_SERVICE_DISMISS = vol.Schema({vol.Required(ATTR_NOTIFICATION_ID): cv.string})
SCHEMA_SERVICE_MARK_READ = vol.Schema({vol.Required(ATTR_NOTIFICATION_ID): cv.string})
DEFAULT_OBJECT_ID = "notification"
STATE = "notifying"
STATUS_UNREAD = "unread"
STATUS_READ = "read"
#Notifications part
@bind_hass
def create(hass, message, title=None, notification_id=None):
"""Generate a notification."""
hass.add_job(async_create, hass, message, title, notification_id)
@bind_hass
def dismiss(hass, notification_id):
"""Remove a notification."""
hass.add_job(async_dismiss, hass, notification_id)
@callback
@bind_hass
def async_create(
hass: HomeAssistant,
message: str,
title: Optional[str] = None,
notification_id: Optional[str] = None,
) -> None:
"""Generate a notification."""
data = {
key: value
for key, value in [
(ATTR_TITLE, title),
(ATTR_MESSAGE, message),
(ATTR_NOTIFICATION_ID, notification_id),
]
if value is not None
}
hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_CREATE, data))
@callback
@bind_hass
def async_dismiss(hass: HomeAssistant, notification_id: str) -> None:
"""Remove a notification."""
data = {ATTR_NOTIFICATION_ID: notification_id}
hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_DISMISS, data))
@callback
@websocket_api.websocket_command({vol.Required("type"): "dwains_dashboard_notification/get"})
def websocket_get_notifications(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: Mapping[str, Any],
) -> None:
"""Return a list of dwains_dashboard_notifications."""
connection.send_message(
websocket_api.result_message(
msg["id"],
[
{
key: data[key]
for key in (
ATTR_NOTIFICATION_ID,
ATTR_MESSAGE,
ATTR_STATUS,
ATTR_TITLE,
ATTR_CREATED_AT,
)
}
for data in hass.data[DOMAIN]["notifications"].values()
],
)
)
#End notifications part
def notifications(hass, name):
#Notifications part setup
"""Set up the dwains dashboard notification component."""
dwains_dashboard_notifications: MutableMapping[str, MutableMapping] = OrderedDict()
hass.data[DOMAIN]["notifications"] = dwains_dashboard_notifications
@callback
def create_service(call):
"""Handle a create notification service call."""
title = call.data.get(ATTR_TITLE)
message = call.data.get(ATTR_MESSAGE)
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
if notification_id is not None:
entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id))
else:
entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID, hass=hass
)
notification_id = entity_id.split(".")[1]
attr = {}
if title is not None:
try:
title.hass = hass
title = title.async_render()
except TemplateError as ex:
_LOGGER.error("Error rendering title %s: %s", title, ex)
title = title.template
attr[ATTR_TITLE] = title
try:
message.hass = hass
message = message.async_render()
except TemplateError as ex:
_LOGGER.error("Error rendering message %s: %s", message, ex)
message = message.template
attr[ATTR_MESSAGE] = message
hass.states.async_set(entity_id, STATE, attr)
# Store notification and fire event
# This will eventually replace state machine storage
dwains_dashboard_notifications[entity_id] = {
ATTR_MESSAGE: message,
ATTR_NOTIFICATION_ID: notification_id,
ATTR_STATUS: STATUS_UNREAD,
ATTR_TITLE: title,
ATTR_CREATED_AT: dt_util.utcnow(),
}
hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED)
@callback
def dismiss_service(call):
"""Handle the dismiss notification service call."""
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id))
if entity_id not in dwains_dashboard_notifications:
return
hass.states.async_remove(entity_id)
del dwains_dashboard_notifications[entity_id]
hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED)
@callback
def mark_read_service(call):
"""Handle the mark_read notification service call."""
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id))
if entity_id not in dwains_dashboard_notifications:
_LOGGER.error(
"Marking dwains dashboard_notification read failed: "
"Notification ID %s not found.",
notification_id,
)
return
dwains_dashboard_notifications[entity_id][ATTR_STATUS] = STATUS_READ
hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED)
hass.services.async_register(
DOMAIN, SERVICE_CREATE, create_service, SCHEMA_SERVICE_CREATE
)
hass.services.async_register(
DOMAIN, SERVICE_DISMISS, dismiss_service, SCHEMA_SERVICE_DISMISS
)
hass.services.async_register(
DOMAIN, SERVICE_MARK_READ, mark_read_service, SCHEMA_SERVICE_MARK_READ
)
websocket_api.async_register_command(hass, websocket_get_notifications)
#End notifications part setup
@@ -0,0 +1,213 @@
import logging
import yaml
import os
import logging
import json
import io
import time
from collections import OrderedDict
import jinja2
import shutil
from concurrent.futures import ThreadPoolExecutor
import asyncio
from aiofiles.os import scandir
#from homeassistant.util.yaml import Secrets, loader
from annotatedyaml import loader
from annotatedyaml.loader import Secrets
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import HomeAssistant
from .const import DOMAIN, VERSION
_LOGGER = logging.getLogger(__name__)
def fromjson(value):
return json.loads(value)
jinja = jinja2.Environment(loader=jinja2.FileSystemLoader("/"))
jinja.filters['fromjson'] = fromjson
dwains_dashboard_more_pages = {}
llgen_config = {}
def load_yamll(fname, secrets = None, args={}):
try:
process_yaml = False
with open(fname, encoding="utf-8") as f:
if f.readline().lower().startswith(("# dwains_dashboard", "# dwains_theme", "# lovelace_gen", "#dwains_dashboard")):
process_yaml = True
#_LOGGER.debug(f"load_yamll() Loading YAML: {fname}, process_yaml={process_yaml}")
if process_yaml:
stream = io.StringIO(jinja.get_template(fname).render({
**args,
"_dd_more_pages": dwains_dashboard_more_pages,
"_global": llgen_config
}))
stream.name = fname
return loader.yaml.load(stream, Loader=lambda _stream: loader.PythonSafeLoader(_stream, secrets)) or OrderedDict()
else:
with open(fname, encoding="utf-8") as config_file:
data = loader.yaml.load(config_file, Loader=lambda stream: loader.PythonSafeLoader(stream, secrets)) or OrderedDict()
#_LOGGER.warning(f"load_yamll() DATA: {data}")
return data
except loader.yaml.YAMLError as exc:
_LOGGER.error(f"YAMLError: {str(exc)}")
raise HomeAssistantError(exc)
except UnicodeDecodeError as exc:
_LOGGER.error("Unicode Error :: Unable to read file %s: %s", fname, exc)
raise HomeAssistantError(exc)
def _include_yaml(ldr, node):
args = {}
if isinstance(node.value, str):
fn = node.value
else:
fn, args, *_ = ldr.construct_sequence(node)
fname = os.path.abspath(os.path.join(os.path.dirname(ldr.name), fn))
try:
return loader._add_reference(load_yamll(fname, ldr.secrets, args=args), ldr, node)
except FileNotFoundError as exc:
_LOGGER.error("Unable to include file %s: %s", fname, exc);
raise HomeAssistantError(exc)
loader.load_yaml = load_yamll
loader.PythonSafeLoader.add_constructor("!include", _include_yaml)
def compose_node(self, parent, index):
if self.check_event(yaml.events.AliasEvent):
event = self.get_event()
anchor = event.anchor
if anchor not in self.anchors:
raise yaml.composer.ComposerError(None, None, "found undefined alias %r"
% anchor, event.start_mark)
return self.anchors[anchor]
event = self.peek_event()
anchor = event.anchor
self.descend_resolver(parent, index)
if self.check_event(yaml.events.ScalarEvent):
node = self.compose_scalar_node(anchor)
elif self.check_event(yaml.events.SequenceStartEvent):
node = self.compose_sequence_node(anchor)
elif self.check_event(yaml.events.MappingStartEvent):
node = self.compose_mapping_node(anchor)
self.ascend_resolver()
return node
yaml.composer.Composer.compose_node = compose_node
async def process_yaml(hass: HomeAssistant, config_entry):
"""Process all YAML files for Dwains Dashboard."""
#_LOGGER.warning('Start of function to process all yaml files!')
# Check for HKI installation
if os.path.exists(hass.config.path("hki-user/config")):
#_LOGGER.warning("HKI Installed!")
for fname in loader._find_files(hass.config.path("hki-user/config"), "*.yaml"):
loaded_yaml = load_yamll(fname)
if isinstance(loaded_yaml, dict):
llgen_config.update(loaded_yaml)
if os.path.exists(hass.config.path("dwains-dashboard/configs")):
if os.path.isdir(hass.config.path("dwains-dashboard/configs/more_pages")):
#for subdir in os.listdir(hass.config.path("dwains-dashboard/configs/more_pages")):
more_pages_path = hass.config.path("dwains-dashboard/configs/more_pages")
subdirs = await hass.async_add_executor_job(os.listdir, more_pages_path)
for subdir in subdirs:
#Lets check if there is a page.yaml in the more_pages folder
if os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml")):
# Page.yaml exists now check if there is a config.yaml otherwise create it
if not os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml")):
#_LOGGER.warning(f"process_yaml() config.yaml does not exist, {subdir}")
#with open(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), 'w') as f:
file_content = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), "w")
with file_content as f:
page_config = OrderedDict()
page_config.update({
"name": subdir,
"icon": "mdi:puzzle"
})
yaml.safe_dump(page_config, f, default_flow_style=False)
dwains_dashboard_more_pages[subdir] = {
"name": subdir,
"icon": "mdi:puzzle",
"path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml",
}
else:
#_LOGGER.warning(f"process_yaml() config.yaml exists, {subdir}")
try:
#with open(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml")) as f:
data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), "r")
with data as f:
filecontent = yaml.safe_load(f)
#_LOGGER.warning(f"FILE CONTENT: {filecontent}")
if "name" in filecontent and "icon" in filecontent:
dwains_dashboard_more_pages[subdir] = {
"name": filecontent["name"],
"icon": filecontent["icon"],
"path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml",
}
else:
_LOGGER.warning(f"Invalid config.yaml in {subdir}: Missing 'name' or 'icon'")
except Exception as e:
_LOGGER.error(f"Failed to read config.yaml in {subdir}: {e}")
hass.bus.async_fire("dwains_dashboard_reload")
async def handle_reload(call):
#Service call to reload Dwains Theme config
_LOGGER.warning("Reload Dwains Dashboard Configuration")
await reload_configuration(hass)
# Register service dwains_dashboard.reload
hass.services.async_register(DOMAIN, "reload", handle_reload)
async def reload_configuration(hass):
_LOGGER.warning('Reload YAML configuration files...!')
if os.path.exists(hass.config.path("dwains-dashboard/configs")):
if os.path.isdir(hass.config.path("dwains-dashboard/configs/more_pages")):
#for subdir in os.listdir(hass.config.path("dwains-dashboard/configs/more_pages")):
more_pages_path = hass.config.path("dwains-dashboard/configs/more_pages")
subdirs = await hass.async_add_executor_job(os.listdir, more_pages_path)
for subdir in subdirs:
#Lets check if there is a page.yaml in the more_pages folder
if os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml")):
page_config = hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml")
#Page.yaml exists now check if there is a config.yaml otherwise create it
if not os.path.exists(page_config):
data = await hass.async_add_executor_job(open, page_config, "w")
with data as f:
page_config = OrderedDict()
page_config.update({
"name": subdir,
"icon": "mdi:puzzle"
})
yaml.safe_dump(page_config, f, default_flow_style=False)
dwains_dashboard_more_pages[subdir] = {
"name": subdir,
"icon": "mdi:puzzle",
"path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml",
}
else:
data = await hass.async_add_executor_job(open, page_config, "r")
with data as f:
filecontent = yaml.safe_load(f)
dwains_dashboard_more_pages[subdir] = {
"name": filecontent["name"],
"icon": filecontent["icon"],
"path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml",
}
hass.bus.async_fire("dwains_dashboard_reload")
@@ -0,0 +1,100 @@
from homeassistant.helpers.entity import Entity
from datetime import datetime, timedelta
from homeassistant.util import Throttle
from .const import DOMAIN, VERSION
import logging
import asyncio
import aiohttp
import async_timeout
import json
from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
_RESOURCE = "https://dwains-dashboard.dwainscheeren.nl/version?v="+VERSION
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=800)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Setup sensor platform."""
#_LOGGER.error("async_setup_platform called")
async_add_entities([LatestVersionSensor()])
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Setup sensor platform."""
#_LOGGER.error("async_setup_entry called")
data = LatestVersion(hass)
async_add_devices([LatestVersionSensor(data)])
class LatestVersionSensor(Entity):
"""Latest version sensor."""
def __init__(self, data):
"""Initialize the sensor."""
self._state = None
self.data = data
@property
def unique_id(self):
"""Return a unique ID to use for this sensor."""
return (
"dwains-dashboard-latest-version"
)
@property
def name(self):
"""Return the name of the sensor."""
return "Dwains Dashboard Latest version"
@property
def icon(self):
"""Return the icon of the sensor."""
return "mdi:alpha-d-box"
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return "latest version"
# def update(self):
# """Fetch new state data for the sensor.
# This is the only method that should fetch new data for Home Assistant.
# """
# self._state = self.hass.data[DOMAIN]['latest_version']
async def async_update(self):
await self.data.update()
self._state = self.hass.data[DOMAIN]['latest_version']
class LatestVersion:
def __init__(self, hass):
self._hass = hass
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def update(self):
session = async_get_clientsession(self._hass)
try:
with async_timeout.timeout(10):
response = await session.get(_RESOURCE)
result = await response.read()
data = json.loads(result)
if "latest_version" in data:
#_LOGGER.error(data)
self._hass.data[DOMAIN]['latest_version'] = json.loads(result)["latest_version"]
except ValueError as err:
_LOGGER.error("Dwains Dashboard version check failed %s", err.args)
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
_LOGGER.error("Dwains Dashboard version check failed %s", repr(err))
@@ -0,0 +1,26 @@
reload:
description: Reload dashboard configuration from Dwains dashboard
notification_create:
description: Show a notification in the frontend.
fields:
message:
description: Message body of the notification. [Templates accepted]
example: Dishwasher is done! :D
notification_id:
description: Target ID of the notification, will replace a notification with the same Id. [Optional]
example: 1234
notification_dismiss:
description: Remove a notification from the frontend.
fields:
notification_id:
description: Target ID of the notification, which should be removed. [Required]
example: 1234
notification_mark_read:
description: Mark a notification read.
fields:
notification_id:
description: Target ID of the notification, which should be mark read. [Required]
example: 1234