evcc-io/assets/index.html

531 lines
19 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="EV Charge Controller">
<meta name="author" content="andig">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png">
<link rel="manifest" href="ico/site.webmanifest">
<link rel="mask-icon" href="ico/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="ico/favicon.ico">
<meta name="msapplication-TileColor" content="#2b5797">
<meta name="msapplication-config" content="ico/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>evcc</title>
<!-- CSS -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/fontawesome.min.css" rel="stylesheet">
<!-- Bootstrap -->
<script src="js/jquery-3.4.1.slim.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.bundle.min.js"></script>
<!-- Axios -->
<script src="js/axios.min.js"></script>
<!-- VueJS -->
<script src="js/vue.min.js"></script>
<script src="js/vue-router.min.js"></script>
<style>
/* reverse loading animation */
.progress-bar-animated {
animation-direction: reverse;
}
.btn.caption {
opacity: 1;
}
.btn.first {
border-top-left-radius: .2rem;
border-bottom-left-radius: .2rem;
}
code {
font-size: 87.5%;
color: #e83e8c;
word-wrap: break-word;
}
</style>
</head>
<body>
<div id="app">
<version installed="<<.Version>>" id="version-bar"></version>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="https://github.com/andig/evcc"><i class="text-primary fas fa-leaf mr-2"></i>evcc</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<router-link class="nav-item nav-link pb-1" to="/">Laden</router-link>
<!-- <<range $idx, $link := .Links>> -->
<router-link class="nav-item nav-link pb-1" to="/links/<<$idx>>"><<$link.Title>></router-link>
<!-- <<end>> -->
<router-link class="nav-item nav-link pb-1" to="/config">Konfiguration</router-link>
<a class="nav-item nav-link pb-1" href="https://github.com/andig/evcc/issues">Support</a>
</div>
</div>
</nav>
<message-toasts></message-toasts>
<router-view></router-view>
</div>
<script type="text/x-template" id="version-template">
<div class="row p-3 bg-warning collapse" ref="bar">
<div class="col-12">
Neue Version verfügbar!
Installiert: {{installed}}.
Verfügbar: {{state.availableVersion}}.
<b><a v-bind:href="'https://github.com/andig/evcc/releases/tag/'+state.availableVersion"
class="text-body pr-2">Download...</a></b>
<button type="button" class="close float-right" style="margin-top: -2px" aria-label="Close" data-toggle="collapse" data-target="#version-bar">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
</script>
<script type="text/x-template" id="embed-template">
<div class="container mx-auto text-center">
<h1 class="display-4 pt-3" v-if="title">{{title}}</h1>
<p class="lead mx-auto text-center" v-if="subtitle">{{subtitle}}</p>
<a v-bind:href="link" v-if="link">
<img v-bind:src="img" v-if="img">
<iframe v-bind:src="iframe" v-if="iframe">
</a>
<span v-else>
<img class="img-fluid" v-bind:src="img" v-if="img">
<iframe v-bind:src="iframe" v-if="iframe">
</span>
</div>
</script>
<div id="toasts">
<div aria-atomic="true" style="position: absolute; top: 4rem; right: 0.5rem; min-height: 200px; min-width: 300px">
<!-- Position it -->
<message-toast v-for="item in items" v-bind:item="item" :id="'message-id-'+item.id" :key="item.id">
</message-toast>
</div>
</div>
<script type="text/x-template" id="message-template">
<div class="toast" data-delay="10000" v-bind:data-autohide="true">
<div class="toast-header">
<strong class="mr-auto" v-if="item.type != 'warn'"><i class="text-danger fas fa-exclamation-triangle"></i> Error</strong>
<strong class="mr-auto" v-if="item.type == 'warn'"><i class="text-warning fas fa-exclamation-triangle"></i> Warning</strong>
<small v-if="item.status">HTTP {{item.status}}</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">{{item.message}}</div>
</div>
</script>
<script type="text/x-template" id="main-template">
<div class="container">
<site v-bind:state="state" v-if="configured"></site>
<div v-else>
<div class="row py-5">
<div class="col12">
<p class="h1 pt-5 pb-2 border-bottom">Willkommen bei evcc</p>
<p class="lead pt-2"><b>evcc</b> ist dient zur flexiblen Ladesteuerung von Elektrofahrzeugen.</p>
<p class="pt-2"> Es sieht aus, als wäre Dein <b>evcc</b> noch nicht konfiguriert. Um <b>evcc</b> zu
konfigurieren sind die folgenden Schritte notwendig:
<ol class="pt-2">
<li>Erzeugen einer Konfigurationsdatei mit Namen <code>evcc.yaml</code>. Die
Standardkonfiguration <code>evcc.dist.yaml</code> kann dafür als Vorlage dienen (<a
href="https://github.com/andig/evcc/blob/master/evcc.dist.yaml">Download</a>).
<li>Konfiguration der Wallbox als <code>chargers</code>.
<li>Konfiguration des EVU Zählers und evtl. weiterer Zähler unter <code>meters</code>.
<li>Konfiguration des Netzanschlusses unter <code>site</code>. In einer Site wird der Netzanschluss mit
dem konfigurierten EVU Zähler (<code>meter</code>) verbunden.
<li>Konfiguration eines Ladepunktes unter <code>loadpoints</code>. In einem Ladepunkt wird die
konfigurierte Wallbox (<code>charger</code>) mit dem Ladepunkt verbunden.
<li>Start von <b>evcc</b> mit der neu erstellten Konfiguration: <code>evcc -c evcc.yaml</code>
</ol>
</p>
<p>Minimale Beispielkonfiguration für <b>evcc</b>:</p>
<p>
<code><pre class="mx-3">
uri: localhost:7070 # Adresse für UI
interval: 10s # Regelintervall
meters:
- name: evu-zähler
type: ... # Detailkonfiguration des EVU Zählers
- name: ladezähler
type: ... # Detailkonfiguration des Ladezählers (optional)
chargers:
- name: wallbox
type: ... # Detailkonfiguration der Wallbox
site:
title: Home
meters:
grid: evu-zähler # EVU Zähler
loadpoints:
- title: Ladepunkt # ui display name
charger: wallbox # charger
meters:
charge: ladezähler # Ladezählers (optional)
</pre></code>
</p>
<p>Viel Spass mit <b>evcc</b>! Bei Problemen kannst Du uns auf <a href="https://github.com/andig/evcc/issues">GitHub</a> erreichen.</p>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="site-template">
<div>
<div class="row">
<div class="d-none d-md-flex col-12 col-md-4 mt-md-4 align-items-end">
<p class="h1">{{state.title||"Home"}}</p>
</div>
<div class="col-12 col-md-8 mt-md-4" v-if="multi">
<site-details v-bind:state="state"></site-details>
</div>
</div>
<div class="row d-none d-md-flex border-bottom"></div>
<div class="row" v-if="!multi">
<div class="d-none d-md-block col-md-4"></div>
<div class="col-12 col-md-8">
<site-details v-bind:state="state"></site-details>
</div>
</div>
<loadpoint v-for="(loadpoint,id) in state.loadpoints"
v-bind:id="id" :state="loadpoint" :pv="state.gridMeter" :multi="multi">
</loadpoint>
</div>
</script>
<script type="text/x-template" id="site-details-template">
<div class="row">
<div class="col-6 col-md-3 mt-3" v-if="state.gridMeter">
<div class="mb-2" v-if="state.gridPower > 0">
Bezug <i class="text-primary fas fa-arrow-down"></i>
</div>
<div class="mb-2" v-else>
Einspeisung <i class="text-primary fas fa-arrow-up"></i>
</div>
<h2>
{{fmt(state.gridPower)}} <small class="text-muted">{{fmtUnit(state.gridPower)}}W</small>
</h2>
</div>
<div class="col-6 col-md-3 mt-3" v-if="state.pvMeter">
<div class="mb-2">Erzeugung <i class="fas fas fa-sun" v-bind:class="{'text-primary':state.pvPower<0,'text-muted':state.pvPower>=0}"></i></div>
<h2>
{{fmt(state.pvPower)}} <small class="text-muted">{{fmtUnit(state.pvPower)}}W</small>
</h2>
</div>
<div class="d-none d-inline-block col-md-3 mt-3" v-if="state.batteryMeter">
<div class="mb-2">Batterie <i class="text-primary fas"
v-bind:class="{'fa-arrow-down':state.batteryPower<0, 'fa-arrow-up':state.batteryPower>0}"></i>
</div>
<h2>
{{fmt(state.batteryPower)}} <small class="text-muted">{{fmtUnit(state.batteryPower)}}W</small>
</h2>
</div>
</div>
</script>
<script type="text/x-template" id="loadpoint-template">
<div>
<div class="row" v-if="multi">
<div class="col-12 col-md-4 d-md-flex mt-3 mt-md-5 align-items-end">
<span class="h1 align-bottom">{{state.title||"Ladepunkt"}}</span>
</div>
<div class="col-12 col-md-8 d-none d-md-block mt-3 mt-md-5">
<loadpoint-details v-bind:state="state"></loadpoint-details>
</div>
<div class="col-12 d-md-none">
<div class="row mt-3 pb-3 bg-light">
<div class="col-12 mt-3">
<mode class="w-100" v-bind:mode="state.mode" :pv="pv" v-on:updated="targetMode"></mode>
</div>
<div class="col-12 mt-3" v-if="hasTargetSoC">
<soc class="w-100" v-bind:soc="state.targetSoC" :levels="state.socLevels" v-on:updated="targetSoC"></soc>
</div>
</div>
</div>
</div>
<div class="row d-none d-md-flex mt-5 py-3 pb-4 text-center bg-light" v-if="!multi">
<div class="mt-3" v-bind:class="{'col-md-6':hasTargetSoC,'col-md-12':!hasTargetSoC}">
<mode v-bind:mode="state.mode" :pv="pv" :caption="true" v-on:updated="targetMode"></mode>
</div>
<div class="col-md-6 mt-3" v-if="hasTargetSoC">
<soc v-bind:soc="state.targetSoC" :levels="state.socLevels" :caption="true" v-on:updated="targetSoC"></soc>
</div>
</div>
<div class="row d-md-none mt-2 pb-3 bg-light" v-if="!multi">
<div class="col-12 mt-3">
<mode class="w-100" v-bind:mode="state.mode" :pv="pv" v-on:updated="targetMode"></mode>
</div>
<div class="col-12 mt-3" v-if="hasTargetSoC">
<soc class="w-100" v-bind:soc="state.targetSoC" :levels="state.socLevels" v-on:updated="targetSoC"></soc>
</div>
</div>
<div class="row" v-if="!multi">
<div class="col-12 col-md-4 d-none d-md-flex mt-3 mt-md-5 align-items-end">
<span class="h1 align-bottom">{{state.title||"Ladepunkt"}}</span>
</div>
</div>
<div class="row border-bottom d-none d-md-block"></div>
<div class="row">
<div class="col-12 col-md-4 mt-3 mb-3 mb-md-0">
<vehicle v-bind:state="state"></vehicle>
</div>
<div class="col-12 col-md-4 d-none d-md-block mt-3" v-if="multi">
<div class="mb-2">Modus</div>
<mode class="btn-group-sm" v-bind:mode="state.mode" :pv="pv" v-on:updated="targetMode"></mode>
</div>
<div class="col-12 col-md-4 d-none d-md-block mt-3" v-if="multi && hasTargetSoC">
<div class="mb-2">Ladeziel</div>
<soc class="btn-group-sm" v-bind:soc="state.targetSoC" :levels="state.socLevels" v-on:updated="targetSoC"></soc>
</div>
<div class="col-md-8 d-none d-md-block" v-if="!multi">
<loadpoint-details v-bind:state="state"></loadpoint-details>
</div>
<div class="col-12 d-md-none">
<loadpoint-details v-bind:state="state"></loadpoint-details>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="loadpoint-details-template">
<div class="row">
<div class="col-6 col-md-3 mt-3">
<div class="mb-2">Leistung</div>
<h2>
{{fmt(state.chargePower)}} <small class="text-muted">{{fmtUnit(state.chargePower)}}W</small>
</h2>
</div>
<div class="col-6 col-md-3 mt-3">
<div class="mb-2">Geladen</div>
<h2>
{{fmt(state.chargedEnergy)}} <small class="text-muted">{{fmtUnit(state.chargedEnergy)}}Wh</small>
</h2>
</div>
<div class="col-6 col-md-3 mt-3">
<div class="mb-2">Dauer</div>
<h2>
{{fmtShortDuration(state.chargeDuration)}} <small
class="text-muted">{{fmtShortDurationUnit(state.chargeDuration)}}</small>
</h2>
</div>
<div class="col-6 col-md-3 mt-3" v-if="state.soc">
<div class="mb-2">Restzeit</div>
<h2>
{{fmtShortDuration(state.chargeEstimate)}} <small
class="text-muted">{{fmtShortDurationUnit(state.chargeEstimate)}}</small>
</h2>
</div>
</div>
</script>
<script type="text/x-template" id="mode-template">
<div class="btn-group btn-group-toggle bg-white shadow-none">
<label class="btn btn-outline-primary disabled caption font-weight-bold" v-if="caption">
Modus
</label>
<label class="btn btn-outline-primary" v-bind:class="{active:mode=='off',first:!caption}">
<input type="radio" value="off" v-on:click="targetMode('off')">Stop
</label>
<label class="btn btn-outline-primary" v-bind:class="{active:mode=='now'}">
<input type="radio" value="now" v-on:click="targetMode('now')">Sofort
</label>
<label class="btn btn-outline-primary" v-bind:class="{active:mode=='minpv'}" v-if="pv">
<input type="radio" value="minpv" v-on:click="targetMode('minpv')">
<span class="d-inline d-sm-none">Min + PV</span>
<span class="d-none d-sm-inline">Minimum + PV</span>
</label>
<label class="btn btn-outline-primary" v-bind:class="{active:mode=='pv'}" v-if="pv">
<input type="radio" value="pv" v-on:click="targetMode('pv')">
<span class="d-inline d-sm-none">PV</span>
<span class="d-none d-sm-inline">Nur PV</span>
</label>
</div>
</script>
<script type="text/x-template" id="soc-template">
<div class="btn-group btn-group-toggle bg-white shadow-none">
<label class="btn btn-outline-primary disabled caption font-weight-bold" v-if="caption">
Ladeziel
</label>
<label class="btn btn-outline-primary" v-for="(level,id) in levelsOrDefault" v-bind:level="level" :id="id"
:class="{active:soc==level,first:!caption&&id==0}">
<input type="radio" v-bind:value="level" v-on:click="targetSoC(level)">{{level}}%
</label>
</div>
</script>
<script type="text/x-template" id="vehicle-template">
<div>
<div class="mb-2">{{state.socTitle||"Fahrzeug"}}</div>
<div class="progress" style="height: 24px; font-size:100%; margin-top:16px">
<div class="progress-bar" role="progressbar" v-if="!state.soc" v-bind:class="{
'progress-bar-striped': state.charging,
'progress-bar-animated': state.charging,
'bg-primary': state.connected,
'bg-light': !(state.connected || state.charging),
'text-secondary': !(state.connected || state.charging),
}" v-bind:style="{width: '100%'}">{{state.charging ? "laden" : (state.connected ? "verbunden" : "getrennt")}}
</div>
<div class="progress-bar" role="progressbar" v-else-if="state.socCharge > 9" v-bind:class="{
'progress-bar-striped': state.charging,
'progress-bar-animated': state.charging,
'bg-primary': !state.charging && state.connected,
}" v-bind:style="{width: state.socCharge+'%'}">{{state.socCharge}}%</div>
<div class="progress-bar" role="progressbar" v-else v-bind:class="{
'progress-bar-striped': state.charging,
'progress-bar-animated': state.charging,
'bg-primary': !state.charging && state.connected,
}" v-bind:style="{width: (state.socCharge<0) ? 0 : state.socCharge+'%'}"></div>
</div>
</div>
</script>
<script type="text/x-template" id="config-template">
<div class="container">
<!-- <h1 class="display-4 pt-3 mx-auto text-center">Konfiguration</h1>
<p class="lead mx-auto text-center">Details der Fahrzeug-, Wallbox- und Zählerkonfiguration.</p> -->
<div class="row mt-4 border-bottom">
<div class="col-12">
<p class="h1">{{state.title||"Home"}}</p>
</div>
</div>
<div class="row h5">
<div class="col-md-4"></div>
<div class="col-6 col-md-2 py-3">
Netzzähler:
<span class="text-primary" v-if="state.gridMeter">✓</span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
PV Zähler:
<span class="text-primary" v-if="state.pvMeter">✓</span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
Batteriezähler:
<span class="text-primary" v-if="state.batteryMeter">✓</span>
<span class="text-primary" v-else>&mdash;</span>
</div>
</div>
<div v-for="(loadpoint,id) in state.loadpoints" v-bind:loadpoint="loadpoint" :id="'loadpoint-'+id">
<div class="row mt-4 border-bottom">
<div class="col-12">
<p class="h1">{{loadpoint.title||"Ladepunkt"}}</p>
</div>
</div>
<div class="row h5">
<div class="col-md-4"></div>
<div class="col-6 col-md-2 py-3">
Ladezähler:
<span class="text-primary" v-if="loadpoint.chargeMeter">✓</span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
Phasen:
<span class="text-primary">{{loadpoint.phases}}p</span>
</div>
<div class="col-6 col-md-2 py-3">
Min. Strom:
<span class="text-primary">{{loadpoint.minCurrent}}A</span>
</div>
<div class="col-6 col-md-2 py-3">
Max. Strom:
<span class="text-primary">{{loadpoint.maxCurrent}}A</span>
</div>
</div>
<div class="row h5">
<div class="col-md-4"></div>
<div class="col-md-8 h2">
<div class="row py-3 h2 border-bottom">
<div class="col-12">
Fahrzeug
</div>
</div>
<div class="row h5">
<div class="col-6 py-3">
Modell:
<span class="text-primary">{{loadpoint.socTitle||"—"}}</span>
</div>
<div class="col-6 py-3">
Kapazität:
<span class="text-primary">{{loadpoint.socCapacity}}kWh</span>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<footer class="pt-4 pb-2 pt-md-5 pb-md-4 mt-3 mt-md-5 border-top">
<div class="container">
<div class="row">
<div class="col-6">
<small class="text-muted"><<.Version>> <<if ne .Version .Commit>>(<<.Commit>>)<<end>></small>
</div>
<div class="col-6 text-right">
<small class="text-muted">&copy; 2020</small>
</div>
</div>
</div>
</footer>
<script>
function routerLinks() {
return [
// <<range $idx, $link := .Links>>
{
title: "<<$link.Title>>",
subtitle: "<<$link.Subtitle>>",
img: "<<$link.Img>>",
iframe: "<<$link.Iframe>>",
link: "<<$link.Link>>"
},
// <<end>>
];
}
</script>
<script src="js/app.js"></script>
</body>
</html>