Initial commit.
This commit is contained in:
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"
|
||||
};
|
||||
Binary file not shown.
@@ -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
|
||||
Reference in New Issue
Block a user