Skip to content

Commit

Permalink
Add new web Dashboard (#1412)
Browse files Browse the repository at this point in the history
* Add new web Dashboard

* [pre-commit.ci lite] apply automatic fixes

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
  • Loading branch information
springfall2008 and pre-commit-ci-lite[bot] authored Aug 31, 2024
1 parent 95aedce commit 1acb9fd
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 10 deletions.
14 changes: 9 additions & 5 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import asyncio
import json

THIS_VERSION = "v8.4.1"
THIS_VERSION = "v8.4.2"
PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py"]
from download import predbat_update_move, predbat_update_download, check_install

Expand Down Expand Up @@ -2316,7 +2316,7 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg
attributes={
"results": self.filtered_times(export_kwh_time),
"today": self.filtered_today(export_kwh_time),
"export_until_charge_kwh": export_to_first_charge,
"export_until_charge_kwh": self.dp2(export_to_first_charge),
"friendly_name": "Predicted exports",
"state_class": "measurement",
"unit_of_measurement": "kWh",
Expand Down Expand Up @@ -2556,7 +2556,7 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg
attributes={
"results": self.filtered_times(export_kwh_time),
"today": self.filtered_today(export_kwh_time),
"export_until_charge_kwh": export_to_first_charge,
"export_until_charge_kwh": self.dp2(export_to_first_charge),
"friendly_name": "Predicted exports best",
"state_class": "measurement",
"unit_of_measurement": "kWh",
Expand Down Expand Up @@ -2760,7 +2760,7 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg
attributes={
"results": self.filtered_times(export_kwh_time),
"today": self.filtered_today(export_kwh_time),
"export_until_charge_kwh": export_to_first_charge,
"export_until_charge_kwh": self.dp2(export_to_first_charge),
"friendly_name": "Predicted exports best 10%",
"state_class": "measurement",
"unit_of_measurement": "kWh",
Expand Down Expand Up @@ -2828,7 +2828,7 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg
attributes={
"results": self.filtered_times(export_kwh_time),
"today": self.filtered_today(export_kwh_time),
"export_until_charge_kwh": export_to_first_charge,
"export_until_charge_kwh": self.dp2(export_to_first_charge),
"friendly_name": "Predicted exports base 10%",
"state_class": "measurement",
"unit_of_measurement": "kWh",
Expand Down Expand Up @@ -5100,6 +5100,7 @@ def reset(self):
self.manual_all_times = []
self.config_index = {}
self.dashboard_index = []
self.dashboard_values = {}
self.prefix = self.args.get("prefix", "predbat")
self.previous_status = None
self.had_errors = False
Expand Down Expand Up @@ -10344,6 +10345,9 @@ def dashboard_item(self, entity, state, attributes):
self.set_state_wrapper(entity_id=entity, state=state, attributes=attributes)
if entity not in self.dashboard_index:
self.dashboard_index.append(entity)
self.dashboard_values[entity] = {}
self.dashboard_values[entity]["state"] = state
self.dashboard_values[entity]["attributes"] = attributes

async def async_update_save_restore_list(self):
return await self.run_in_executor(self.update_save_restore_list)
Expand Down
79 changes: 74 additions & 5 deletions apps/predbat/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from aiohttp import web
import asyncio
import os
from config import CONFIG_ITEMS
import re

from config import CONFIG_ITEMS
from utils import calc_percent_limit


class WebInterface:
def __init__(self, base) -> None:
Expand All @@ -23,6 +25,7 @@ async def start(self):
app.router.add_get("/apps", self.html_apps)
app.router.add_get("/config", self.html_config)
app.router.add_post("/config", self.html_config_post)
app.router.add_get("/dash", self.html_dash)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", 5052)
Expand All @@ -39,13 +42,66 @@ async def stop(self):
self.abort = True
await asyncio.sleep(1)

def get_attributes_html(self, entity):
text = ""
attributes = self.base.dashboard_values.get(entity, {}).get("attributes", {})
if not attributes:
return ""
text += "<table>"
for key in attributes:
if key in ["icon", "device_class", "state_class", "unit_of_measurement", "friendly_name"]:
continue
value = attributes[key]
if len(str(value)) > 30:
value = str(value)[:30] + "..."
text += "<tr><td>{}</td><td>{}</td></tr>".format(key, value)
text += "</table>"
return text

def get_status_html(self, level, status):
text = ""
if not self.base.dashboard_index:
text += "<h2>Loading please wait...</h2>"
return text

text += "<h2>Status</h2>\n"
text += "<table>\n"
text += "<tr><td>Status</td><td>{}</td></tr>\n".format(status)
text += "<tr><td>SOC</td><td>{}%</td></tr>\n".format(level)
text += "</table>\n"
text += "<br>\n"

text += "<h2>Entities</h2>\n"
text += "<table>\n"
text += "<tr><th>Icon</th><th>Name</th><th>Entity</th><th>State</th><th>Attributes</th></tr>\n"

for entity in self.base.dashboard_index:
state = self.base.dashboard_values.get(entity, {}).get("state", None)
attributes = self.base.dashboard_values.get(entity, {}).get("attributes", {})
unit_of_measurement = attributes.get("unit_of_measurement", "")
icon = attributes.get("icon", "")
if icon:
icon = '<span class="mdi mdi-{}"></span>'.format(icon.replace("mdi:", ""))
if unit_of_measurement is None:
unit_of_measurement = ""
friendly_name = attributes.get("friendly_name", "")
if not state:
state = "None"
text += "<tr><td> {} </td><td> {} </td><td>{}</td><td>{} {}</td><td>{}</td></tr>\n".format(
icon, friendly_name, entity, state, unit_of_measurement, self.get_attributes_html(entity)
)
text += "</table>\n"

return text

def get_header(self, title, refresh=0):
"""
Return the HTML header for a page
"""
text = "<!doctype html><html><head><title>Predbat Web Interface</title>"

text += """
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<style>
body, html {
margin: 0;
Expand Down Expand Up @@ -105,8 +161,8 @@ def get_header(self, title, refresh=0):
.modified {
background-color: #ffcccc;
}
</style>
"""
text += "</style>"

if refresh:
text += '<meta http-equiv="refresh" content="{}" >'.format(refresh)
Expand Down Expand Up @@ -240,14 +296,25 @@ def render_type(self, arg, value):
text = str(value)
return text

async def html_dash(self, request):
"""
Render apps.yaml as an HTML page
"""
text = self.get_header("Predbat Dashboard")
text += "<body>\n"
soc_perc = calc_percent_limit(self.base.soc_kw, self.base.soc_max)
text += self.get_status_html(soc_perc, self.base.previous_status)
text += "</body></html>\n"
return web.Response(content_type="text/html", text=text)

async def html_apps(self, request):
"""
Render apps.yaml as an HTML page
"""
text = self.get_header("Predbat Config")
text += "<body>\n"
text += "<table>\n"
text += "<tr><td><b>Name</b></td><td><b>Value</b></td><td>\n"
text += "<tr><th>Name</th><th>Value</th><td>\n"

args = self.base.args
for arg in args:
Expand All @@ -271,7 +338,7 @@ async def html_config(self, request):
text += "<body>\n"
text += '<form class="form-inline" action="/config" method="post" enctype="multipart/form-data" id="configform">\n'
text += "<table>\n"
text += "<tr><td><b>Name</b></td><td><b>Entity</b></td><td><b>Type</b></td><td><b>Current</b></td><td><b>Default</b></td><td><b>Select</b></td></tr>\n"
text += "<tr><th>Name</th><th>Entity</th><th>Type</th><th>Current</th><th>Default</th><th>Select</th></tr>\n"

input_number = """
<input type="number" id="{}" name="{}" value="{}" min="{}" max="{}" step="{}" onchange="javascript: this.form.submit();">
Expand Down Expand Up @@ -334,6 +401,7 @@ async def html_menu(self, request):
text += "<body>\n"
text += "<table><tr>\n"
text += '<td><h2>Predbat</h2></td><td><img src="https://github-production-user-asset-6210df.s3.amazonaws.com/48591903/249456079-e98a0720-d2cf-4b71-94ab-97fe09b3cee1.png" width="50" height="50"></td>\n'
text += '<td><a href="/dash" target="main_frame">Dash</a></td>\n'
text += '<td><a href="/plan" target="main_frame">Plan</a></td>\n'
text += '<td><a href="/config" target="main_frame">Config</a></td>\n'
text += '<td><a href="/apps" target="main_frame">apps.yaml</a></td>\n'
Expand All @@ -347,9 +415,10 @@ async def html_index(self, request):
Return the Predbat index page as an HTML page
"""
text = self.get_header("Predbat Index")
text += "<body>\n"
text += '<div class="iframe-container">\n'
text += '<iframe src="/menu" title="Menu frame" class="menu-frame" name="menu_frame"></iframe>\n'
text += '<iframe src="/plan" title="Main frame" class="main-frame" name="main_frame"></iframe>\n'
text += '<iframe src="/dash" title="Main frame" class="main-frame" name="main_frame"></iframe>\n'
text += "</div>\n"
text += "</body></html>\n"
return web.Response(content_type="text/html", text=text)

0 comments on commit 1acb9fd

Please sign in to comment.