diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 49b691ce..d7b86d92 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -51,6 +51,13 @@ "series": "Series", "episodes": "Episodes", "songs": "Songs" + }, + "evcc": { + "pvPower": "Erzeugung", + "batterySoc": "Batterie", + "gridpower": "Netz", + "homepower": "Verbrauch", + "chargePower": "Ladepunkt" }, "tautulli": { "playing": "Spielen", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 9dd10d04..5dbe25a8 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -91,6 +91,13 @@ "series": "Series", "episodes": "Episodes", "songs": "Songs" + }, + "evcc": { + "pvPower": "Production", + "batterySoc": "Battery", + "gridpower": "Grid", + "homepower": "Consumption", + "chargePower": "Charger" }, "flood": { "download": "Download", diff --git a/src/widgets/components.js b/src/widgets/components.js index f8828f3b..002ac7eb 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -16,6 +16,7 @@ const components = { docker: dynamic(() => import("./docker/component")), kubernetes: dynamic(() => import("./kubernetes/component")), emby: dynamic(() => import("./emby/component")), + evcc: dynamic(() => import("./evcc/component")), fileflows: dynamic(() => import("./fileflows/component")), flood: dynamic(() => import("./flood/component")), freshrss: dynamic(() => import("./freshrss/component")), diff --git a/src/widgets/evcc/component.jsx b/src/widgets/evcc/component.jsx new file mode 100644 index 00000000..cb5270ad --- /dev/null +++ b/src/widgets/evcc/component.jsx @@ -0,0 +1,39 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + const { data: resultData, error: resultError } = useWidgetAPI(widget, "result"); + + + if (resultError) { + return ; + } + + if (!resultData) { + return ( + , + + + + + + + ); + } + + return ( + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/evcc/proxy.js b/src/widgets/evcc/proxy.js new file mode 100644 index 00000000..8aad741d --- /dev/null +++ b/src/widgets/evcc/proxy.js @@ -0,0 +1,89 @@ +import { httpProxy } from "utils/proxy/http"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; + +const logger = createLogger("homeassistantProxyHandler"); + +const defaultQueries = [ + { + template: "{{ states.person|selectattr('state','equalto','home')|list|length }} / {{ states.person|list|length }}", + label: "homeassistant.people_home" + }, + { + template: "{{ states.light|selectattr('state','equalto','on')|list|length }} / {{ states.light|list|length }}", + label: "homeassistant.lights_on" + }, + { + template: "{{ states.switch|selectattr('state','equalto','on')|list|length }} / {{ states.switch|list|length }}", + label: "homeassistant.switches_on" + } +]; + +function formatOutput(output, data) { + return output.replace(/\{.*?\}/g, + (match) => match.replace(/\{|\}/g, "").split(".").reduce((o, p) => o ? o[p] : "", data) ?? ""); +} + +async function getQuery(query, { url, key }) { + const headers = { Authorization: `Bearer ${key}` }; + const { state, template, label, value } = query; + if (state) { + return { + result: await httpProxy(new URL(`${url}/api/states/${state}`), { + headers, + method: "GET" + }), + output: (data) => { + const jsonData = JSON.parse(data); + return { + label: formatOutput(label ?? "{attributes.friendly_name}", jsonData), + value: formatOutput(value ?? "{state} {attributes.unit_of_measurement}", jsonData) + }; + } + }; + } + if (template) { + return { + result: await httpProxy(new URL(`${url}/api/template`), { + headers, + method: "POST", + body: JSON.stringify({ template }) + }), + output: (data) => ({ label, value: data.toString() }) + }; + } + return { result: [500, null, { error: { message: `invalid query ${JSON.stringify(query)}` } }] }; +} + +export default async function evccProxyHandler(req, res) { + const { group, service } = req.query; + + if (!group || !service) { + logger.debug("Invalid or missing service '%s' or group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const widget = await getServiceWidget(group, service); + if (!widget) { + logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + let queries = defaultQueries; + if (!widget.fields && widget.custom) { + queries = widget.custom.slice(0, 4); + } + + const results = await Promise.all(queries.map(q => getQuery(q, widget))); + + const err = results.find(r => r.result[2]?.error); + if (err) { + const [status, , data] = err.result; + return res.status(status).send(data); + } + + return res.status(200).send(results.map(r => { + const [status, , data] = r.result; + return status === 200 ? r.output(data) : { label: status, value: data.toString() }; + })); +} diff --git a/src/widgets/evcc/widget.js b/src/widgets/evcc/widget.js new file mode 100644 index 00000000..f3b22417 --- /dev/null +++ b/src/widgets/evcc/widget.js @@ -0,0 +1,14 @@ +import genericProxyHandler from "utils/proxy/handlers/generic"; + +const widget = { + api: "{url}/api/{endpoint}", + proxyHandler: genericProxyHandler, + + mappings: { + result: { + endpoint: "state", + } + }, +}; + +export default widget; \ No newline at end of file diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 9e155383..40d57abe 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -11,6 +11,7 @@ import deluge from "./deluge/widget"; import diskstation from "./diskstation/widget"; import downloadstation from "./downloadstation/widget"; import emby from "./emby/widget"; +import evcc from "./evcc/widget"; import fileflows from "./fileflows/widget"; import flood from "./flood/widget"; import freshrss from "./freshrss/widget"; @@ -92,6 +93,7 @@ const widgets = { diskstation, downloadstation, emby, + evcc, fileflows, flood, freshrss,