Add target energy API & UI for vehicles without SoC

This commit is contained in:
andig 2022-10-04 20:08:57 +02:00 • committed by GitHub
parent 0ca26eafa2
commit 26f149571c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 664 additions and 192 deletions

View file

@ -221,3 +221,9 @@ type AuthProvider interface {
LoginHandler() http.HandlerFunc
LogoutHandler() http.HandlerFunc
}
// FeatureDescriber optionally provides a list of supported non-api features
type FeatureDescriber interface {
Features() []Feature
Has(Feature) bool
}

17
api/feature.go Normal file
View file

@ -0,0 +1,17 @@
package api
type Feature int
func (f *Feature) UnmarshalText(text []byte) error {
feat, err := FeatureString(string(text))
if err == nil {
*f = feat
}
return err
}
//go:generate enumer -type Feature
const (
_ Feature = iota
Offline
)

75
api/feature_enumer.go Normal file
View file

@ -0,0 +1,75 @@
// Code generated by "enumer -type Feature"; DO NOT EDIT.
package api
import (
"fmt"
"strings"
)
const _FeatureName = "Offline"
var _FeatureIndex = [...]uint8{0, 7}
const _FeatureLowerName = "offline"
func (i Feature) String() string {
i -= 1
if i < 0 || i >= Feature(len(_FeatureIndex)-1) {
return fmt.Sprintf("Feature(%d)", i+1)
}
return _FeatureName[_FeatureIndex[i]:_FeatureIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _FeatureNoOp() {
var x [1]struct{}
_ = x[Offline-(1)]
}
var _FeatureValues = []Feature{Offline}
var _FeatureNameToValueMap = map[string]Feature{
_FeatureName[0:7]: Offline,
_FeatureLowerName[0:7]: Offline,
}
var _FeatureNames = []string{
_FeatureName[0:7],
}
// FeatureString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func FeatureString(s string) (Feature, error) {
if val, ok := _FeatureNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _FeatureNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to Feature values", s)
}
// FeatureValues returns all values of the enum
func FeatureValues() []Feature {
return _FeatureValues
}
// FeatureStrings returns a slice of all String values of the enum
func FeatureStrings() []string {
strs := make([]string, len(_FeatureNames))
copy(strs, _FeatureNames)
return strs
}
// IsAFeature returns "true" if the value is listed in the enum definition. "false" otherwise
func (i Feature) IsAFeature() bool {
for _, v := range _FeatureValues {
if i == v {
return true
}
}
return false
}

View file

@ -1,5 +1,5 @@
<template>
<number :to="to" :format="format" :duration="duration" />
<number :to="to" :format="format" :duration="animationDuration" />
</template>
<script>
@ -10,10 +10,16 @@ export default {
props: {
to: { type: Number },
format: { type: Function },
noAnimation: { type: Boolean },
},
data() {
return { duration: 0 };
},
computed: {
animationDuration() {
return this.noAnimation ? 0 : this.duration;
},
},
watch: {
to: function () {
this.duration = DURATION;

View file

@ -39,6 +39,26 @@ Base.args = {
activePhases: 2,
};
export const WithoutSoc = Template.bind({});
WithoutSoc.args = {
id: 0,
pvConfigured: true,
chargePower: 2800,
chargedEnergy: 7123,
chargeDuration: 95 * 60,
vehiclePresent: false,
enabled: true,
connected: true,
mode: "pv",
charging: true,
vehicleSoC: 66,
targetSoC: 90,
chargeCurrent: 7,
minCurrent: 6,
maxCurrent: 16,
activePhases: 2,
};
export const Idle = Template.bind({});
Idle.args = {
id: 0,

View file

@ -67,6 +67,7 @@
/>
</div>
<LabelAndValue
v-show="socBasedCharging"
:label="$t('main.loadpoint.charged')"
:value="fmtKWh(chargedEnergy)"
align="center"
@ -92,6 +93,7 @@
<Vehicle
v-bind="vehicle"
@target-soc-updated="setTargetSoC"
@target-energy-updated="setTargetEnergy"
@target-time-updated="setTargetTime"
@target-time-removed="removeTargetTime"
@change-vehicle="changeVehicle"
@ -132,6 +134,7 @@ export default {
title: String,
mode: String,
targetSoC: Number,
targetEnergy: Number,
remoteDisabled: Boolean,
remoteDisabledSource: String,
chargeDuration: Number,
@ -147,6 +150,8 @@ export default {
vehicleSoC: Number,
vehicleTitle: String,
vehicleTargetSoC: Number,
vehicleCapacity: Number,
vehicleFeatureOffline: Boolean,
vehicles: Array,
minSoC: Number,
targetTime: String,
@ -170,7 +175,6 @@ export default {
maxCurrent: Number,
phasesActive: Number,
chargeCurrent: Number,
vehicleCapacity: Number,
connectedDuration: Number,
chargeCurrents: Array,
chargeConfigured: Boolean,
@ -205,6 +209,9 @@ export default {
showChargingIndicator: function () {
return this.charging && this.chargePower > 0;
},
socBasedCharging: function () {
return !this.vehicleFeatureOffline && this.vehiclePresent;
},
},
watch: {
phaseRemaining() {
@ -250,6 +257,9 @@ export default {
setTargetSoC: function (soc) {
api.post(this.apiPath("targetsoc") + "/" + soc);
},
setTargetEnergy: function (kWh) {
api.post(this.apiPath("targetenergy") + "/" + kWh);
},
setMaxCurrent: function (maxCurrent) {
api.post(this.apiPath("maxcurrent") + "/" + maxCurrent);
},

View file

@ -0,0 +1,119 @@
<template>
<LabelAndValue class="flex-grow-1" :label="$t('main.targetEnergy.label')" align="end">
<h3 class="value m-0 d-block d-sm-flex align-items-baseline justify-content-end">
<label class="position-relative">
<select :value="targetEnergy" class="custom-select" @change="change">
<option
v-for="{ energy, text, disabled } in options"
:key="energy"
:value="energy"
:disabled="disabled"
>
{{ text }}
</option>
</select>
<span
class="text-decoration-underline"
:class="{ 'text-gray fw-normal': !targetEnergy }"
>
<AnimatedNumber
:to="targetEnergy"
:format="formatKWh"
:no-animation="!targetEnergy"
/>
</span>
</label>
<div v-if="estimatedTargetSoC" class="extraValue ms-0 ms-sm-1 text-nowrap">
<AnimatedNumber :to="estimatedTargetSoC" :format="formatSoC" />
</div>
</h3>
</LabelAndValue>
</template>
<script>
import LabelAndValue from "./LabelAndValue.vue";
import AnimatedNumber from "./AnimatedNumber.vue";
import formatter from "../mixins/formatter";
export default {
name: "TargetEnergySelect",
components: { LabelAndValue, AnimatedNumber },
mixins: [formatter],
props: {
targetEnergy: Number,
socPerKwh: Number,
chargedEnergy: Number,
vehicleCapacity: Number,
},
emits: ["target-energy-updated"],
computed: {
maxEnergy: function () {
return this.vehicleCapacity || 100;
},
steps: function () {
if (this.maxEnergy < 25) {
return 1;
}
if (this.maxEnergy < 50) {
return 2;
}
return 5;
},
options: function () {
const result = [];
for (let energy = 0; energy <= this.maxEnergy; energy += this.steps) {
let text = this.formatKWh(energy);
const disabled = energy < this.chargedEnergy / 1e3 && energy !== 0;
const soc = this.estimatedSoC(energy);
if (soc) {
text += ` (${this.formatSoC(soc)})`;
}
result.push({ energy, text, disabled });
}
return result;
},
estimatedTargetSoC: function () {
return this.estimatedSoC(this.targetEnergy);
},
},
methods: {
change: function (e) {
return this.$emit("target-energy-updated", parseInt(e.target.value, 10));
},
estimatedSoC: function (kWh) {
if (this.socPerKwh) {
return Math.round(kWh * this.socPerKwh);
}
return null;
},
formatKWh: function (value) {
if (value === 0) {
return this.$t("main.targetEnergy.noLimit");
}
return `${Math.round(value)} kWh`;
},
formatSoC: function (value) {
return `+${Math.round(value)}%`;
},
},
};
</script>
<style scoped>
.value {
font-size: 18px;
}
.extraValue {
color: var(--evcc-gray);
font-size: 14px;
}
.custom-select {
left: 0;
top: 0;
bottom: 0;
right: 0;
position: absolute;
opacity: 0;
}
</style>

View file

@ -24,6 +24,7 @@ Base.args = {
vehicleSoC: 42,
vehicleRange: 231,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
@ -36,6 +37,7 @@ Connected.args = {
charging: false,
vehicleSoC: 66,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
@ -48,6 +50,7 @@ ReadyToCharge.args = {
charging: false,
vehicleSoC: 66,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
@ -60,6 +63,7 @@ Charging.args = {
charging: true,
vehicleSoC: 66,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
@ -81,6 +85,7 @@ TargetChargePlanned.args = {
targetTimeActive: false,
targetSoC: 45,
targetTime: hoursFromNow(14),
socBasedCharging: true,
id: 0,
};
@ -96,6 +101,7 @@ TargetChargeActive.args = {
targetTimeActive: true,
targetSoC: 80,
targetTime: hoursFromNow(2),
socBasedCharging: true,
id: 0,
};
@ -109,6 +115,21 @@ MinCharge.args = {
minSoC: 20,
charging: true,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
export const VehicleTargetSoc = Template.bind({});
VehicleTargetSoc.args = {
vehicleTitle: "Mein Auto",
enabled: true,
connected: true,
vehiclePresent: true,
vehicleTargetSoC: 80,
vehicleSoC: 66,
charging: true,
targetSoC: 90,
socBasedCharging: true,
id: 0,
};
@ -125,6 +146,7 @@ TimerPvEnable.args = {
phaseRemainingInterpolated: 0,
pvAction: "enable",
pvRemainingInterpolated: 32,
socBasedCharging: true,
id: 0,
};
@ -141,16 +163,17 @@ TimerPvDisable.args = {
phaseRemainingInterpolated: 0,
pvAction: "disable",
pvRemainingInterpolated: 155,
socBasedCharging: true,
id: 0,
};
export const UnknownVehicleConnected = Template.bind({});
UnknownVehicleConnected.args = {
vehicleTitle: "Mein Auto",
enabled: false,
connected: true,
vehiclePresent: false,
targetSoC: 90,
socBasedCharging: false,
id: 0,
};
@ -162,6 +185,7 @@ UnknownVehicleReadyToCharge.args = {
vehiclePresent: false,
charging: false,
targetSoC: 100,
socBasedCharging: false,
id: 0,
};
@ -173,6 +197,37 @@ UnknownVehicleCharging.args = {
vehiclePresent: false,
charging: true,
targetSoC: 90,
socBasedCharging: false,
id: 0,
};
export const OfflineVehicleCharging = Template.bind({});
OfflineVehicleCharging.args = {
vehicleTitle: "Polestar 2",
enabled: true,
connected: true,
vehiclePresent: true,
vehicleFeatureOffline: true,
vehicleCapacity: 72,
chargedEnergy: 14123,
charging: true,
targetSoC: 90,
socBasedCharging: false,
id: 0,
};
export const OfflineVehicleTargetEnergy = Template.bind({});
OfflineVehicleTargetEnergy.args = {
vehicleTitle: "Polestar 2",
enabled: true,
connected: true,
vehiclePresent: true,
vehicleFeatureOffline: true,
vehicleCapacity: 72,
chargedEnergy: 14123,
targetEnergy: 30,
charging: true,
socBasedCharging: false,
id: 0,
};

View file

@ -12,44 +12,65 @@
@target-soc-updated="targetSocUpdated"
@target-soc-drag="targetSocDrag"
/>
<div v-if="vehiclePresent">
<div class="details d-flex flex-wrap justify-content-between">
<LabelAndValue
class="flex-grow-1"
:label="$t('main.vehicle.vehicleSoC')"
:value="vehicleSoC ? `${vehicleSoC}%` : '--'"
:extraValue="vehicleRange ? `${vehicleRange} km` : null"
align="start"
/>
<TargetCharge
class="flex-grow-1 text-center target-charge"
v-bind="targetCharge"
:disabled="targetChargeDisabled"
@target-time-updated="setTargetTime"
@target-time-removed="removeTargetTime"
/>
<TargetSoCSelect
class="flex-grow-1 text-end"
:target-soc="displayTargetSoC"
:range-per-soc="rangePerSoC"
@target-soc-updated="targetSocUpdated"
/>
</div>
<div v-if="$hiddenFeatures" class="d-flex justify-content-start">
<small>vor 5 Stunden</small>
</div>
<div class="details d-flex flex-wrap justify-content-between">
<LabelAndValue
v-if="socBasedCharging"
class="flex-grow-1"
:label="$t('main.vehicle.vehicleSoC')"
:value="vehicleSoC ? `${vehicleSoC}%` : '--'"
:extraValue="vehicleRange ? `${vehicleRange} km` : null"
align="start"
/>
<LabelAndValue
v-else
class="flex-grow-1"
:label="$t('main.loadpoint.charged')"
:value="fmtKWh(chargedEnergy)"
:extraValue="chargedSoC"
align="start"
/>
<TargetCharge
v-if="socBasedCharging"
class="flex-grow-1 text-center target-charge"
v-bind="targetCharge"
:disabled="targetChargeDisabled"
@target-time-updated="setTargetTime"
@target-time-removed="removeTargetTime"
/>
<TargetSoCSelect
v-if="socBasedCharging"
class="flex-grow-1 text-end"
:target-soc="displayTargetSoC"
:range-per-soc="rangePerSoC"
@target-soc-updated="targetSocUpdated"
/>
<TargetEnergySelect
v-else
class="flex-grow-1 text-end"
:target-energy="targetEnergy"
:soc-per-kwh="socPerKwh"
:charged-energy="chargedEnergy"
:vehicle-capacity="vehicleCapacity"
@target-energy-updated="targetEnergyUpdated"
/>
</div>
<div v-if="$hiddenFeatures" class="d-flex justify-content-start">
<small>vor 5 Stunden</small>
</div>
</div>
</template>
<script>
import collector from "../mixins/collector";
import formatter from "../mixins/formatter";
import LabelAndValue from "./LabelAndValue.vue";
import VehicleTitle from "./VehicleTitle.vue";
import VehicleSoc from "./VehicleSoc.vue";
import VehicleStatus from "./VehicleStatus.vue";
import TargetCharge from "./TargetCharge.vue";
import TargetSoCSelect from "./TargetSoCSelect.vue";
import TargetEnergySelect from "./TargetEnergySelect.vue";
export default {
name: "Vehicle",
@ -60,8 +81,9 @@ export default {
LabelAndValue,
TargetCharge,
TargetSoCSelect,
TargetEnergySelect,
},
mixins: [collector],
mixins: [collector, formatter],
props: {
id: [String, Number],
connected: Boolean,
@ -74,10 +96,14 @@ export default {
vehicleDetectionActive: Boolean,
vehicleRange: Number,
vehicleTitle: String,
vehicleCapacity: Number,
socBasedCharging: Boolean,
targetTimeActive: Boolean,
targetTime: String,
targetTimeProjectedStart: String,
targetSoC: Number,
targetEnergy: Number,
chargedEnergy: Number,
mode: String,
phaseAction: String,
phaseRemainingInterpolated: Number,
@ -90,6 +116,7 @@ export default {
"target-time-removed",
"target-time-updated",
"target-soc-updated",
"target-energy-updated",
"change-vehicle",
"remove-vehicle",
],
@ -117,6 +144,16 @@ export default {
}
return null;
},
socPerKwh: function () {
if (this.vehicleCapacity > 0) {
return 100 / this.vehicleCapacity;
}
return null;
},
chargedSoC: function () {
const value = this.socPerKwh * (this.chargedEnergy / 1e3);
return value > 1 ? `+${Math.round(value)}%` : null;
},
targetChargeDisabled: function () {
return !this.connected || !["pv", "minpv"].includes(this.mode);
},
@ -134,6 +171,9 @@ export default {
this.displayTargetSoC = targetSoC;
this.$emit("target-soc-updated", targetSoC);
},
targetEnergyUpdated: function (targetEnergy) {
this.$emit("target-energy-updated", targetEnergy);
},
setTargetTime: function (targetTime) {
this.$emit("target-time-updated", targetTime);
},

View file

@ -2,7 +2,7 @@
<div class="vehicle-soc">
<div class="progress">
<div
v-if="connected || parked"
v-if="connected"
class="progress-bar"
role="progressbar"
:class="{
@ -31,7 +31,7 @@
</div>
<div class="target">
<input
v-if="vehiclePresent && (connected || parked)"
v-if="socBasedCharging && connected"
type="range"
min="0"
max="100"
@ -63,7 +63,9 @@ export default {
charging: Boolean,
minSoC: Number,
targetSoC: Number,
parked: Boolean,
targetEnergy: Number,
chargedEnergy: Number,
socBasedCharging: Boolean,
},
emits: ["target-soc-drag", "target-soc-updated"],
data: function () {
@ -75,16 +77,23 @@ export default {
},
computed: {
vehicleSoCDisplayWidth: function () {
if (this.vehiclePresent && this.vehicleSoC >= 0) {
return this.vehicleSoC;
if (this.socBasedCharging) {
if (this.vehicleSoC >= 0) {
return this.vehicleSoC;
}
return 100;
} else {
if (this.targetEnergy) {
return (100 / this.targetEnergy) * (this.chargedEnergy / 1e3);
}
return 100;
}
return 100;
},
vehicleTargetSoCActive: function () {
return this.vehicleTargetSoC > 0 && this.vehicleTargetSoC > this.vehicleSoC;
},
targetSliderActive: function () {
return !this.visibleTargetSoC || this.visibleTargetSoC <= this.vehicleTargetSoC;
return !this.vehicleTargetSoC || this.visibleTargetSoC <= this.vehicleTargetSoC;
},
progressColor: function () {
if (this.minSoCActive) {
@ -96,16 +105,23 @@ export default {
return this.minSoC > 0 && this.vehicleSoC < this.minSoC;
},
remainingSoCWidth: function () {
if (this.vehicleSoCDisplayWidth === 100) {
return null;
}
if (this.minSoCActive) {
return this.minSoC - this.vehicleSoC;
}
let targetSoC = this.targetSliderActive ? this.visibleTargetSoC : this.vehicleTargetSoC;
if (targetSoC > this.vehicleSoC) {
return targetSoC - this.vehicleSoC;
if (this.socBasedCharging) {
if (this.vehicleSoCDisplayWidth === 100) {
return null;
}
if (this.minSoCActive) {
return this.minSoC - this.vehicleSoC;
}
let targetSoC = this.targetSliderActive
? this.visibleTargetSoC
: this.vehicleTargetSoC;
if (targetSoC > this.vehicleSoC) {
return targetSoC - this.vehicleSoC;
}
} else {
return 100 - this.vehicleSoCDisplayWidth;
}
return null;
},
visibleTargetSoC: function () {

View file

@ -28,6 +28,9 @@
{{ name }}
</span>
</VehicleOptions>
<span v-else class="flex-grow-1 text-truncate vehicle-name">
{{ name }}
</span>
</h4>
</div>
</template>
@ -117,9 +120,11 @@ export default {
margin-right: -0.75rem;
}
.vehicle-name {
text-decoration: underline;
text-decoration-color: var(--evcc-gray);
}
.options .vehicle-name {
text-decoration: underline;
}
.spin {
animation: rotation 1s infinite cubic-bezier(0.37, 0, 0.63, 1);
}

View file

@ -167,6 +167,10 @@ export default {
Bitte melde unerwartetes Verhalten in unseren
`,
},
targetEnergy: {
label: "Ladeziel",
noLimit: "keins",
},
},
startupError: {
title: "Fehler beim Starten",

View file

@ -166,6 +166,10 @@ export default {
Please report unexpected behaviour in our
`,
},
targetEnergy: {
label: "Limit",
noLimit: "none",
},
},
startupError: {
title: "Startup Error",

View file

@ -192,6 +192,11 @@ vehicles:
capacity: 80
onidentify:
targetsoc: 75
- name: vehicle_3
type: template
template: offline
title: grüner Honda e
capacity: 26
site:
title: Zuhause

View file

@ -113,6 +113,7 @@ type LoadPoint struct {
Enable, Disable ThresholdConfig
ResetOnDisconnect bool `mapstructure:"resetOnDisconnect"`
onDisconnect api.ActionConfig
targetEnergy int // Target charge energy for dumb vehicles
MinCurrent float64 // PV mode: start current Min+PV mode: min current
MaxCurrent float64 // Max allowed current. Physically ensured by the charger
@ -683,6 +684,18 @@ func (lp *LoadPoint) setStatus(status api.ChargeStatus) {
lp.status = status
}
// targetEnergyReached checks if target is configured and reached
func (lp *LoadPoint) targetEnergyReached() bool {
v, ok := lp.vehicle.(api.FeatureDescriber)
if ok {
ok = v.Has(api.Offline)
}
return (v == nil || ok) &&
lp.targetEnergy > 0 &&
lp.chargedEnergy/1e3 >= float64(lp.targetEnergy)
}
// targetSocReached checks if target is configured and reached.
// If vehicle is not configured this will always return false
func (lp *LoadPoint) targetSocReached() bool {
@ -731,6 +744,17 @@ func (lp *LoadPoint) climateActive() bool {
return false
}
// disableUnlessClimater disables the charger unless climate is active
func (lp *LoadPoint) disableUnlessClimater() error {
var current float64 // zero disables
if lp.climateActive() {
lp.log.DEBUG.Println("climater active")
current = lp.GetMinCurrent()
}
lp.socTimer.Reset() // once SoC is reached, the target charge request is removed
return lp.setLimit(current, true)
}
// remoteControlled returns true if remote control status is active
func (lp *LoadPoint) remoteControlled(demand loadpoint.RemoteDemand) bool {
lp.Lock()
@ -864,6 +888,9 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
lp.publish(vehicleOdometer, 0.0)
}
// reset target energy
lp.setTargetEnergy(0)
// re-publish vehicle settings
lp.Unlock()
lp.publish(phasesActive, lp.activePhases())
@ -898,6 +925,17 @@ func (lp *LoadPoint) unpublishVehicle() {
lp.publish(vehicleTargetSoC, 0.0)
lp.setRemainingDuration(-1)
lp.vehiclePublishFeature(api.Offline)
}
// vehiclePublishFeature availability of vehicle features
func (lp *LoadPoint) vehiclePublishFeature(f api.Feature) {
v, ok := lp.vehicle.(api.FeatureDescriber)
if ok {
ok = v.Has(f)
}
lp.publish("vehicleFeature"+f.String(), ok)
}
// vehicleUnidentified returns true if there are associated vehicles and detection is running.
@ -1633,15 +1671,13 @@ func (lp *LoadPoint) Update(sitePower float64, cheap, batteryBuffered bool) {
lp.log.DEBUG.Printf("switched phases: %dp", lp.ConfiguredPhases)
}
case lp.targetEnergyReached():
lp.log.DEBUG.Printf("targetEnergy reached: %.0fkWh > %dkWh", lp.chargedEnergy/1e3, lp.targetEnergy)
err = lp.disableUnlessClimater()
case lp.targetSocReached():
lp.log.DEBUG.Printf("targetSoC reached: %.1f > %d", lp.vehicleSoc, lp.SoC.target)
var targetCurrent float64 // zero disables
if lp.climateActive() {
lp.log.DEBUG.Println("climater active")
targetCurrent = lp.GetMinCurrent()
}
err = lp.setLimit(targetCurrent, true)
lp.socTimer.Reset() // once SoC is reached, the target charge request is removed
lp.log.DEBUG.Printf("targetSoC reached: %.1f%% > %d%%", lp.vehicleSoc, lp.SoC.target)
err = lp.disableUnlessClimater()
// OCPP has priority over target charging
case lp.remoteControlled(loadpoint.RemoteHardDisable):

View file

@ -31,6 +31,10 @@ type API interface {
GetMode() api.ChargeMode
// SetMode sets the charge mode
SetMode(api.ChargeMode)
// GetTargetEnergy returns the charge target energy
GetTargetEnergy() int
// SetTargetEnergy sets the charge target energy
SetTargetEnergy(int)
// GetTargetSoC returns the charge target soc
GetTargetSoC() int
// SetTargetSoC sets the charge target soc

View file

@ -49,6 +49,38 @@ func (lp *LoadPoint) SetMode(mode api.ChargeMode) {
}
}
// GetTargetSoC returns loadpoint charge target energy
func (lp *LoadPoint) GetTargetEnergy() int {
lp.Lock()
defer lp.Unlock()
return lp.SoC.target
}
// setTargetEnergy sets loadpoint charge target energy (no mutex)
func (lp *LoadPoint) setTargetEnergy(energy int) {
lp.targetEnergy = energy
// test guard
if lp.socTimer != nil {
// TODO soctimer
// lp.socTimer.Energy = energy
}
lp.publish("targetEnergy", energy)
}
// SetTargetEnergy sets loadpoint charge target energy
func (lp *LoadPoint) SetTargetEnergy(energy int) {
lp.Lock()
defer lp.Unlock()
lp.log.DEBUG.Println("set target energy:", energy)
// apply immediately
if lp.targetEnergy != energy {
lp.setTargetEnergy(energy)
lp.requestUpdate()
}
}
// GetTargetSoC returns loadpoint charge target soc
func (lp *LoadPoint) GetTargetSoC() int {
lp.Lock()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View file

@ -20,8 +20,8 @@
<meta name="theme-color" content="#020318" />
<title>evcc</title>
<script type="module" crossorigin src="./assets/index.a6e7d28f.js"></script>
<link rel="stylesheet" href="./assets/index.682a2576.css">
<script type="module" crossorigin src="./assets/index.019e1c40.js"></script>
<link rel="stylesheet" href="./assets/index.5b6a2832.css">
</head>
<body>

224
package-lock.json generated
View file

@ -78,30 +78,30 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz",
"integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.3.tgz",
"integrity": "sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz",
"integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz",
"integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.13",
"@babel/helper-compilation-targets": "^7.18.9",
"@babel/helper-module-transforms": "^7.18.9",
"@babel/helpers": "^7.18.9",
"@babel/parser": "^7.18.13",
"@babel/generator": "^7.19.3",
"@babel/helper-compilation-targets": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.0",
"@babel/helpers": "^7.19.0",
"@babel/parser": "^7.19.3",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.18.13",
"@babel/types": "^7.18.13",
"@babel/traverse": "^7.19.3",
"@babel/types": "^7.19.3",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -117,12 +117,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz",
"integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz",
"integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==",
"dev": true,
"dependencies": {
"@babel/types": "^7.18.13",
"@babel/types": "^7.19.3",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
},
@ -170,14 +170,14 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz",
"integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz",
"integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.18.8",
"@babel/compat-data": "^7.19.3",
"@babel/helper-validator-option": "^7.18.6",
"browserslist": "^4.20.2",
"browserslist": "^4.21.3",
"semver": "^6.3.0"
},
"engines": {
@ -263,13 +263,13 @@
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
"dev": true,
"dependencies": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/types": "^7.19.0"
},
"engines": {
"node": ">=6.9.0"
@ -312,9 +312,9 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz",
"integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz",
"integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.18.9",
@ -322,9 +322,9 @@
"@babel/helper-simple-access": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
},
"engines": {
"node": ">=6.9.0"
@ -431,9 +431,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -464,14 +464,14 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz",
"integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz",
"integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==",
"dev": true,
"dependencies": {
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
},
"engines": {
"node": ">=6.9.0"
@ -492,9 +492,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@ -1923,19 +1923,19 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz",
"integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.3.tgz",
"integrity": "sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.13",
"@babel/generator": "^7.19.3",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.18.13",
"@babel/types": "^7.18.13",
"@babel/parser": "^7.19.3",
"@babel/types": "^7.19.3",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@ -1944,13 +1944,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz",
"integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
@ -24603,27 +24603,27 @@
}
},
"@babel/compat-data": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz",
"integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.3.tgz",
"integrity": "sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==",
"dev": true
},
"@babel/core": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz",
"integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz",
"integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.13",
"@babel/helper-compilation-targets": "^7.18.9",
"@babel/helper-module-transforms": "^7.18.9",
"@babel/helpers": "^7.18.9",
"@babel/parser": "^7.18.13",
"@babel/generator": "^7.19.3",
"@babel/helper-compilation-targets": "^7.19.3",
"@babel/helper-module-transforms": "^7.19.0",
"@babel/helpers": "^7.19.0",
"@babel/parser": "^7.19.3",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.18.13",
"@babel/types": "^7.18.13",
"@babel/traverse": "^7.19.3",
"@babel/types": "^7.19.3",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -24632,12 +24632,12 @@
}
},
"@babel/generator": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz",
"integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz",
"integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==",
"dev": true,
"requires": {
"@babel/types": "^7.18.13",
"@babel/types": "^7.19.3",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
},
@ -24675,14 +24675,14 @@
}
},
"@babel/helper-compilation-targets": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz",
"integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz",
"integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.18.8",
"@babel/compat-data": "^7.19.3",
"@babel/helper-validator-option": "^7.18.6",
"browserslist": "^4.20.2",
"browserslist": "^4.21.3",
"semver": "^6.3.0"
}
},
@ -24741,13 +24741,13 @@
}
},
"@babel/helper-function-name": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
"dev": true,
"requires": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/types": "^7.19.0"
}
},
"@babel/helper-hoist-variables": {
@ -24778,9 +24778,9 @@
}
},
"@babel/helper-module-transforms": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz",
"integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz",
"integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==",
"dev": true,
"requires": {
"@babel/helper-environment-visitor": "^7.18.9",
@ -24788,9 +24788,9 @@
"@babel/helper-simple-access": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
}
},
"@babel/helper-optimise-call-expression": {
@ -24867,9 +24867,9 @@
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
@ -24891,14 +24891,14 @@
}
},
"@babel/helpers": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz",
"integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz",
"integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==",
"dev": true,
"requires": {
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9"
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
}
},
"@babel/highlight": {
@ -24913,9 +24913,9 @@
}
},
"@babel/parser": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg=="
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
@ -25861,31 +25861,31 @@
}
},
"@babel/traverse": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz",
"integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.3.tgz",
"integrity": "sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.13",
"@babel/generator": "^7.19.3",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.18.13",
"@babel/types": "^7.18.13",
"@babel/parser": "^7.19.3",
"@babel/types": "^7.19.3",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.18.13",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
"version": "7.19.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz",
"integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},

View file

@ -109,6 +109,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
routes := map[string]route{
"mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", chargeModeHandler(lp)},
"targetenergy": {[]string{"POST", "OPTIONS"}, "/targetenergy/{value:[0-9]+}", intHandler(pass(lp.SetTargetEnergy), lp.GetTargetEnergy)},
"targetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{value:[0-9]+}", intHandler(pass(lp.SetTargetSoC), lp.GetTargetSoC)},
"minsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{value:[0-9]+}", intHandler(pass(lp.SetMinSoC), lp.GetMinSoC)},
"mincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9]+}", floatHandler(pass(lp.SetMinCurrent), lp.GetMinCurrent)},

View file

@ -20,6 +20,7 @@ params:
valuetype: stringlist
render: |
type: custom
features: ["offline"]
{{- if ne .title "" }}
title: {{ .title }}
{{- end }}

View file

@ -1,12 +1,16 @@
package vehicle
import "github.com/evcc-io/evcc/api"
import (
"github.com/evcc-io/evcc/api"
"golang.org/x/exp/slices"
)
type embed struct {
Title_ string `mapstructure:"title"`
Capacity_ int64 `mapstructure:"capacity"`
Phases_ int `mapstructure:"phases"`
Identifiers_ []string `mapstructure:"identifiers"`
Features_ []api.Feature `mapstructure:"features"`
OnIdentify api.ActionConfig `mapstructure:"onIdentify"`
}
@ -34,3 +38,15 @@ func (v *embed) Identifiers() []string {
func (v *embed) OnIdentified() api.ActionConfig {
return v.OnIdentify
}
var _ api.FeatureDescriber = (*embed)(nil)
// Features implements the api.Describer interface
func (v *embed) Features() []api.Feature {
return v.Features_
}
// Features implements the api.Describer interface
func (v *embed) Has(f api.Feature) bool {
return slices.Contains(v.Features_, f)
}