Visualise Vehicle SoC Limit (#4245)
This commit is contained in:
parent
c8b6698dcb
commit
382eb67785
18 changed files with 1990 additions and 2331 deletions
|
|
@ -1,3 +1,4 @@
|
|||
<script>
|
||||
window.global = window;
|
||||
</script>
|
||||
</script>
|
||||
<meta name="theme-color" content="#020318" />
|
||||
|
|
@ -2,11 +2,14 @@ import "bootstrap/dist/css/bootstrap.min.css";
|
|||
import "bootstrap";
|
||||
import smoothscroll from "smoothscroll-polyfill";
|
||||
import { app } from "@storybook/vue3";
|
||||
import VueNumber from "vue-number-animation";
|
||||
import i18n from "../assets/js/i18n";
|
||||
import "../assets/css/app.css";
|
||||
import { watchThemeChanges } from "../assets/js/theme";
|
||||
|
||||
smoothscroll.polyfill();
|
||||
app.use(i18n);
|
||||
app.use(VueNumber);
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
|
|
@ -30,3 +33,5 @@ export const parameters = {
|
|||
],
|
||||
},
|
||||
};
|
||||
|
||||
watchThemeChanges();
|
||||
|
|
|
|||
|
|
@ -172,6 +172,11 @@ type VehiclePosition interface {
|
|||
Position() (float64, float64, error)
|
||||
}
|
||||
|
||||
// SocLimiter returns the vehicles charge limit
|
||||
type SocLimiter interface {
|
||||
TargetSoC() (float64, error)
|
||||
}
|
||||
|
||||
// VehicleChargeController allows to start/stop the charging session on the vehicle side
|
||||
type VehicleChargeController interface {
|
||||
StartCharge() error
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ export default {
|
|||
vehicleRange: Number,
|
||||
vehicleSoC: Number,
|
||||
vehicleTitle: String,
|
||||
vehicleTargetSoC: Number,
|
||||
vehicles: Array,
|
||||
minSoC: Number,
|
||||
targetTime: String,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export default {
|
|||
connected: Boolean,
|
||||
vehiclePresent: Boolean,
|
||||
vehicleSoC: Number,
|
||||
vehicleTargetSoC: Number,
|
||||
enabled: Boolean,
|
||||
charging: Boolean,
|
||||
minSoC: Number,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@
|
|||
:class="progressColor"
|
||||
:style="{ width: `${remainingSoCWidth}%`, transition: 'none' }"
|
||||
></div>
|
||||
<div
|
||||
v-show="vehicleTargetSoC"
|
||||
ref="vehicleTargetSoC"
|
||||
class="vehicle-target-soc"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="$t('main.vehicleSoC.vehicleTarget', { soc: vehicleTargetSoC })"
|
||||
:class="{ 'vehicle-target-soc--active': vehicleTargetSoCActive }"
|
||||
:style="{ left: `${vehicleTargetSoC}%` }"
|
||||
/>
|
||||
</div>
|
||||
<div class="target">
|
||||
<input
|
||||
|
|
@ -29,6 +38,7 @@
|
|||
step="5"
|
||||
:value="visibleTargetSoC"
|
||||
class="target-slider"
|
||||
:class="{ 'target-slider--active': targetSliderActive }"
|
||||
@mousedown="changeTargetSoCStart"
|
||||
@touchstart="changeTargetSoCStart"
|
||||
@input="movedTargetSoC"
|
||||
|
|
@ -40,12 +50,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { Tooltip } from "bootstrap";
|
||||
|
||||
export default {
|
||||
name: "VehicleSoc",
|
||||
props: {
|
||||
connected: Boolean,
|
||||
vehiclePresent: Boolean,
|
||||
vehicleSoC: Number,
|
||||
vehicleTargetSoC: Number,
|
||||
enabled: Boolean,
|
||||
charging: Boolean,
|
||||
minSoC: Number,
|
||||
|
|
@ -57,6 +70,7 @@ export default {
|
|||
return {
|
||||
selectedTargetSoC: null,
|
||||
interactionStartScreenY: null,
|
||||
tooltip: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -66,6 +80,12 @@ export default {
|
|||
}
|
||||
return 100;
|
||||
},
|
||||
vehicleTargetSoCActive: function () {
|
||||
return this.vehicleTargetSoC > 0 && this.vehicleTargetSoC > this.vehicleSoC;
|
||||
},
|
||||
targetSliderActive: function () {
|
||||
return !this.visibleTargetSoC || this.visibleTargetSoC <= this.vehicleTargetSoC;
|
||||
},
|
||||
progressColor: function () {
|
||||
if (this.minSoCActive) {
|
||||
return "bg-danger";
|
||||
|
|
@ -82,8 +102,9 @@ export default {
|
|||
if (this.minSoCActive) {
|
||||
return this.minSoC - this.vehicleSoC;
|
||||
}
|
||||
if (this.visibleTargetSoC > this.vehicleSoC) {
|
||||
return this.visibleTargetSoC - this.vehicleSoC;
|
||||
let targetSoC = this.targetSliderActive ? this.visibleTargetSoC : this.vehicleTargetSoC;
|
||||
if (targetSoC > this.vehicleSoC) {
|
||||
return targetSoC - this.vehicleSoC;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
@ -95,6 +116,12 @@ export default {
|
|||
targetSoC: function () {
|
||||
this.selectedTargetSoC = this.targetSoC;
|
||||
},
|
||||
vehicleTargetSoC: function () {
|
||||
this.updateTooltip();
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.updateTooltip();
|
||||
},
|
||||
methods: {
|
||||
changeTargetSoCStart: function (e) {
|
||||
|
|
@ -122,6 +149,14 @@ export default {
|
|||
this.$emit("target-soc-drag", this.selectedTargetSoC);
|
||||
return true;
|
||||
},
|
||||
updateTooltip: function () {
|
||||
this.$nextTick(() => {
|
||||
if (this.tooltip) {
|
||||
this.tooltip.dispose();
|
||||
}
|
||||
this.tooltip = new Tooltip(this.$refs.vehicleTargetSoC);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -177,24 +212,48 @@ export default {
|
|||
margin-left: var(--thumb-width) / 2;
|
||||
height: 100%;
|
||||
width: var(--thumb-width);
|
||||
background-color: var(--evcc-dark-green);
|
||||
background-color: var(--evcc-gray);
|
||||
cursor: grab;
|
||||
border: none;
|
||||
opacity: 1;
|
||||
border-radius: var(--thumb-overlap);
|
||||
box-shadow: 0 0 6px var(--evcc-background);
|
||||
pointer-events: auto;
|
||||
transition: background-color var(--evcc-transition-fast) linear;
|
||||
}
|
||||
.target-slider::-moz-range-thumb {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: var(--thumb-width);
|
||||
background-color: var(--evcc-dark-green);
|
||||
background-color: var(--evcc-gray);
|
||||
cursor: grab;
|
||||
border: none;
|
||||
opacity: 1;
|
||||
border-radius: var(--thumb-overlap);
|
||||
box-shadow: 0 0 6px var(--evcc-background);
|
||||
pointer-events: auto;
|
||||
transition: background-color var(--evcc-transition-fast) linear;
|
||||
}
|
||||
.target-slider--active::-webkit-slider-thumb {
|
||||
background-color: var(--evcc-dark-green);
|
||||
}
|
||||
.target-slider--active::-moz-range-thumb {
|
||||
background-color: var(--evcc-dark-green);
|
||||
}
|
||||
.vehicle-target-soc {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 20px;
|
||||
transform: translateX(-8px);
|
||||
background-color: transparent;
|
||||
background-clip: padding-box;
|
||||
border-width: 0 8px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
transition: background-color var(--evcc-transition-fast) linear;
|
||||
}
|
||||
.vehicle-target-soc--active {
|
||||
background-color: var(--evcc-box);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default {
|
|||
mixins: [formatter],
|
||||
props: {
|
||||
vehicleSoC: Number,
|
||||
vehicleTargetSoC: Number,
|
||||
minSoC: Number,
|
||||
enabled: Boolean,
|
||||
connected: Boolean,
|
||||
|
|
@ -70,6 +71,9 @@ export default {
|
|||
}
|
||||
|
||||
if (this.enabled && !this.charging) {
|
||||
if (this.vehicleTargetSoC > 0 && this.vehicleSoC >= this.vehicleTargetSoC - 1) {
|
||||
return t("vehicleTargetReached", { soc: this.vehicleTargetSoC });
|
||||
}
|
||||
return t("waitForVehicle");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,10 +127,12 @@ export default {
|
|||
charging: "lädt",
|
||||
ready: "bereit",
|
||||
connected: "verbunden",
|
||||
vehicleTarget: "Fahrzeuglimit: {soc}%",
|
||||
},
|
||||
vehicleStatus: {
|
||||
minCharge: "Mindestladung bis {soc}%.",
|
||||
waitForVehicle: "Ladebereit. Warte auf Fahrzeug.",
|
||||
vehicleTargetReached: "Fahrzeuglimit {soc}% erreicht.",
|
||||
charging: "Ladevorgang aktiv.",
|
||||
targetChargePlanned: "Zielladen geplant. Ladung startet {time} Uhr.",
|
||||
targetChargeWaitForVehicle: "Zielladen bereit. Warte auf Fahrzeug.",
|
||||
|
|
|
|||
|
|
@ -126,10 +126,12 @@ export default {
|
|||
charging: "charging",
|
||||
ready: "ready",
|
||||
connected: "connected",
|
||||
vehicleTarget: "Vehicle limit: {soc}%",
|
||||
},
|
||||
vehicleStatus: {
|
||||
minCharge: "minimum charging to {soc}%.",
|
||||
waitForVehicle: "Ready. Waiting for vehicle.",
|
||||
vehicleTargetReached: "Vehicle limit {soc}% reached.",
|
||||
charging: "Charging.",
|
||||
targetChargePlanned: "Target charge planned. Starting {time}.",
|
||||
targetChargeWaitForVehicle: "Target charge ready. Wait for vehicle.",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,14 @@ func (d *dumper) Dump(name string, v interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
if v, ok := v.(api.SocLimiter); ok {
|
||||
if targetSoC, err := v.TargetSoC(); err != nil {
|
||||
fmt.Fprintf(w, "Target SoC:\t%v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(w, "Target SoC:\t%.0f%%\n", targetSoC)
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := v.(api.Vehicle); ok {
|
||||
fmt.Fprintf(w, "Capacity:\t%dkWh\n", v.Capacity())
|
||||
if len(v.Identifiers()) > 0 {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,8 @@ const (
|
|||
phasesActive = "phasesActive" // active phases as used by vehicle (1/2/3)
|
||||
|
||||
vehicleDetectionActive = "vehicleDetectionActive" // vehicle detection is active (bool)
|
||||
|
||||
vehicleRange = "vehicleRange" // vehicle range
|
||||
vehicleOdometer = "vehicleOdometer" // vehicle odometer
|
||||
vehicleTargetSoC = "vehicleTargetSoC" // vehicle soc limit
|
||||
)
|
||||
|
|
|
|||
|
|
@ -845,7 +845,7 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
|
|||
lp.publish("vehiclePresent", false)
|
||||
lp.publish("vehicleTitle", "")
|
||||
lp.publish("vehicleCapacity", int64(0))
|
||||
lp.publish("vehicleOdometer", 0.0)
|
||||
lp.publish(vehicleOdometer, 0.0)
|
||||
}
|
||||
|
||||
// re-publish vehicle settings
|
||||
|
|
@ -878,7 +878,8 @@ func (lp *LoadPoint) unpublishVehicle() {
|
|||
lp.vehicleSoc = 0
|
||||
|
||||
lp.publish("vehicleSoC", 0.0)
|
||||
lp.publish("vehicleRange", int64(0))
|
||||
lp.publish(vehicleRange, int64(0))
|
||||
lp.publish(vehicleTargetSoC, 0.0)
|
||||
|
||||
lp.setRemainingDuration(-1)
|
||||
}
|
||||
|
|
@ -966,7 +967,7 @@ func (lp *LoadPoint) vehicleOdometer() {
|
|||
if vs, ok := lp.vehicle.(api.VehicleOdometer); ok {
|
||||
if odo, err := vs.Odometer(); err == nil {
|
||||
lp.log.DEBUG.Printf("vehicle odometer: %.0fkm", odo)
|
||||
lp.publish("vehicleOdometer", odo)
|
||||
lp.publish(vehicleOdometer, odo)
|
||||
} else {
|
||||
lp.log.ERROR.Printf("vehicle odometer: %v", err)
|
||||
}
|
||||
|
|
@ -1506,7 +1507,15 @@ func (lp *LoadPoint) publishSoCAndRange() {
|
|||
if vs, ok := lp.vehicle.(api.VehicleRange); ok {
|
||||
if rng, err := vs.Range(); err == nil {
|
||||
lp.log.DEBUG.Printf("vehicle range: %dkm", rng)
|
||||
lp.publish("vehicleRange", rng)
|
||||
lp.publish(vehicleRange, rng)
|
||||
}
|
||||
}
|
||||
|
||||
// vehicle target soc
|
||||
if vs, ok := lp.vehicle.(api.SocLimiter); ok {
|
||||
if targetSoC, err := vs.TargetSoC(); err == nil {
|
||||
lp.log.DEBUG.Printf("vehicle target soc: %.0f%%", targetSoC)
|
||||
lp.publish(vehicleTargetSoC, targetSoC)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
97
dist/assets/index.ca617d2b.js
vendored
Normal file
97
dist/assets/index.ca617d2b.js
vendored
Normal file
File diff suppressed because one or more lines are too long
97
dist/assets/index.fa7cff34.js
vendored
97
dist/assets/index.fa7cff34.js
vendored
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
|
|
@ -20,8 +20,8 @@
|
|||
<meta name="theme-color" content="#020318" />
|
||||
|
||||
<title>evcc</title>
|
||||
<script type="module" crossorigin src="./assets/index.fa7cff34.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.15a4660f.css">
|
||||
<script type="module" crossorigin src="./assets/index.ca617d2b.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.b1f68136.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
|
|
|||
3990
package-lock.json
generated
3990
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -186,6 +186,18 @@ func (v *Tesla) Position() (float64, float64, error) {
|
|||
return 0, 0, err
|
||||
}
|
||||
|
||||
var _ api.SocLimiter = (*Tesla)(nil)
|
||||
|
||||
// TargetSoC implements the api.SocLimiter interface
|
||||
func (v *Tesla) TargetSoC() (float64, error) {
|
||||
res, err := v.chargeStateG()
|
||||
if err == nil {
|
||||
return float64(res.ChargeLimitSoc), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.VehicleChargeController = (*Tesla)(nil)
|
||||
|
||||
// StartCharge implements the api.VehicleChargeController interface
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue