Loadpoint: get soc or temp limit from integrated device (#19397)

This commit is contained in:
andig 2025-03-05 17:16:38 +01:00 • committed by GitHub
parent 86c7364c7d
commit 1e7ca8f16d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 4881 additions and 210 deletions

View file

@ -0,0 +1,17 @@
<template>
<svg :style="svgStyle" viewBox="0 0 32 32">
<path
d="M16,28c-1.844,0 -3.416,-0.65 -4.716,-1.951c-1.3,-1.3 -1.95,-2.872 -1.951,-4.716c0,-1.066 0.234,-2.061 0.7,-2.984c0.467,-0.922 1.123,-1.705 1.967,-2.349l0,-8c0,-1.111 0.389,-2.056 1.167,-2.833c0.777,-0.778 1.722,-1.167 2.833,-1.167c1.111,0 2.056,0.389 2.833,1.167c0.778,0.777 1.167,1.722 1.167,2.833l0,8c0.844,0.644 1.5,1.428 1.967,2.351c0.466,0.922 0.7,1.917 0.7,2.982c-0,1.845 -0.651,3.417 -1.951,4.718c-1.3,1.3 -2.872,1.95 -4.716,1.949m-1.333,-18l2.666,0l0,-2c0,-0.378 -0.128,-0.694 -0.384,-0.949c-0.256,-0.255 -0.572,-0.383 -0.949,-0.384c-0.377,-0.001 -0.693,0.127 -0.949,0.384c-0.256,0.257 -0.384,0.573 -0.384,0.949l-0,2Z"
fill="currentColor"
/>
</svg>
</template>
<script>
import icon from "../../mixins/icon";
export default {
name: "TempLimit",
mixins: [icon],
};
</script>

View file

@ -33,68 +33,82 @@
<div class="tabular">{{ fmtDuration(phaseRemainingInterpolated) }}</div>
</div>
<!-- vehicle -->
<button
v-if="minSocVisible"
ref="minSoc"
type="button"
class="entry text-danger text-decoration-underline"
data-testid="vehicle-status-minsoc"
data-bs-toggle="tooltip"
@click="openMinSocSettings"
>
<VehicleMinSocIcon />
{{ fmtPercentage(minSoc) }}
</button>
<div
v-else-if="vehicleLimitVisible"
ref="vehicleLimit"
class="entry"
:class="vehicleLimitClass"
data-bs-toggle="tooltip"
data-testid="vehicle-status-limit"
:role="vehicleLimitWarning ? 'button' : null"
@click="vehicleLimitClicked"
>
<component :is="vehicleLimitIconComponent" />
{{ fmtPercentage(vehicleLimitSoc) }}
</div>
<div
v-if="vehicleClimaterActive"
ref="vehicleClimater"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-climater"
>
<ClimaterIcon />
</div>
<div
v-if="vehicleWelcomeActive"
ref="vehicleWelcome"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-welcome"
>
<WelcomeIcon />
</div>
<div
v-if="awaitingAuthorizationVisible"
ref="awaitingAuthorization"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-awaiting-authorization"
>
<RfidWaitIcon />
</div>
<div
v-if="disconnectRequiredVisible"
ref="disconnectRequired"
class="entry text-warning"
data-bs-toggle="tooltip"
data-testid="vehicle-status-disconnect-required"
>
<ReconnectIcon />
</div>
<template v-if="heating">
<div
v-if="tempLimitVisible"
ref="tempLimit"
class="entry"
data-bs-toggle="tooltip"
data-testid="vehicle-status-limit"
>
<TempLimitIcon />
{{ fmtTemperature(vehicleLimitSoc) }}
</div>
</template>
<template v-else>
<!-- vehicle -->
<button
v-if="minSocVisible"
ref="minSoc"
type="button"
class="entry text-danger text-decoration-underline"
data-testid="vehicle-status-minsoc"
data-bs-toggle="tooltip"
@click="openMinSocSettings"
>
<VehicleMinSocIcon />
{{ fmtPercentage(minSoc) }}
</button>
<div
v-else-if="vehicleLimitVisible"
ref="vehicleLimit"
class="entry"
:class="vehicleLimitClass"
data-bs-toggle="tooltip"
data-testid="vehicle-status-limit"
:role="vehicleLimitWarning ? 'button' : null"
@click="vehicleLimitClicked"
>
<component :is="vehicleLimitIconComponent" />
{{ fmtPercentage(vehicleLimitSoc) }}
</div>
<div
v-if="vehicleClimaterActive"
ref="vehicleClimater"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-climater"
>
<ClimaterIcon />
</div>
<div
v-if="vehicleWelcomeActive"
ref="vehicleWelcome"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-welcome"
>
<WelcomeIcon />
</div>
<div
v-if="awaitingAuthorizationVisible"
ref="awaitingAuthorization"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-awaiting-authorization"
>
<RfidWaitIcon />
</div>
<div
v-if="disconnectRequiredVisible"
ref="disconnectRequired"
class="entry text-warning"
data-bs-toggle="tooltip"
data-testid="vehicle-status-disconnect-required"
>
<ReconnectIcon />
</div>
</template>
<!-- smart cost -->
<button
@ -179,6 +193,7 @@ import ReconnectIcon from "./MaterialIcon/Reconnect.vue";
import RfidWaitIcon from "./MaterialIcon/RfidWait.vue";
import SunDownIcon from "./MaterialIcon/SunDown.vue";
import SunUpIcon from "./MaterialIcon/SunUp.vue";
import TempLimitIcon from "./MaterialIcon/TempLimit.vue";
import Tooltip from "bootstrap/js/dist/tooltip";
import VehicleLimitIcon from "./MaterialIcon/VehicleLimit.vue";
import VehicleLimitReachedIcon from "./MaterialIcon/VehicleLimitReached.vue";
@ -203,6 +218,7 @@ export default {
VehicleLimitIcon,
VehicleMinSocIcon,
WelcomeIcon,
TempLimitIcon,
BatteryBoostIcon,
},
mixins: [formatter],
@ -252,6 +268,7 @@ export default {
vehicleWelcomeTooltip: null,
smartCostTooltip: null,
vehicleLimitTooltip: null,
tempLimitTooltip: null,
awaitingAuthorizationTooltip: null,
disconnectRequiredTooltip: null,
batteryBoostTooltip: null,
@ -329,6 +346,12 @@ export default {
}
return this.$t("main.vehicleStatus.vehicleLimit");
},
tempLimitTooltipContent() {
if (!this.tempLimitVisible) {
return "";
}
return this.$t("main.heatingStatus.vehicleLimit");
},
minSocVisible() {
return this.connected && this.minSoc > 0 && this.vehicleSoc < this.minSoc;
},
@ -357,6 +380,9 @@ export default {
}
return "";
},
tempLimitVisible() {
return this.heating && this.vehicleLimitSoc > 0;
},
vehicleLimitIconComponent() {
if (this.vehicleLimitReached) {
return VehicleLimitReachedIcon;
@ -523,6 +549,9 @@ export default {
vehicleLimitTooltipContent() {
this.$nextTick(this.updateVehicleLimitTooltip);
},
tempLimitTooltipContent() {
this.$nextTick(this.updateTempLimitTooltip);
},
awaitingAuthorizationTooltipContent() {
this.$nextTick(this.updateAwaitingAuthorizationTooltip);
},
@ -552,6 +581,7 @@ export default {
this.updateVehicleWelcomeTooltip();
this.updateSmartCostTooltip();
this.updateVehicleLimitTooltip();
this.updateTempLimitTooltip();
this.updateAwaitingAuthorizationTooltip();
this.updateDisconnectRequiredTooltip();
this.updateDurations();
@ -684,6 +714,13 @@ export default {
true
);
},
updateTempLimitTooltip() {
this.tempLimitTooltip = this.updateTooltip(
this.tempLimitTooltip,
this.tempLimitTooltipContent,
this.$refs.tempLimit
);
},
updateAwaitingAuthorizationTooltip() {
this.awaitingAuthorizationTooltip = this.updateTooltip(
this.awaitingAuthorizationTooltip,

View file

@ -25,7 +25,7 @@ func init() {
registry.AddCtx(api.Custom, NewConfigurableFromConfig)
}
//go:generate go tool decorate -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)" -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)"
//go:generate go tool decorate -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)" -t "api.SocLimiter,GetLimitSoc,func() (int64, error)" -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)"
// NewConfigurableFromConfig creates a new configurable charger
func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
@ -36,6 +36,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
Identify, Phases1p3p *plugin.Config
Wakeup *plugin.Config
Soc *plugin.Config
LimitSoc *plugin.Config
Tos bool
measurement.Energy `mapstructure:",squash"` // optional
meter.Phases `mapstructure:",squash"` // optional
@ -119,6 +120,12 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
return nil, fmt.Errorf("soc: %w", err)
}
// decorate limitsoc
limitsoc, err := cc.LimitSoc.IntGetter(ctx)
if err != nil {
return nil, fmt.Errorf("limitsoc: %w", err)
}
// decorate measurements
powerG, energyG, err := cc.Energy.Configure(ctx)
if err != nil {
@ -130,7 +137,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
return nil, err
}
return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc, powerG, energyG, currentsG, voltagesG), nil
return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc, limitsoc, powerG, energyG, currentsG, voltagesG), nil
}
// NewConfigurable creates a new charger

File diff suppressed because it is too large Load diff

View file

@ -1635,7 +1635,14 @@ func (lp *Loadpoint) publishSocAndRange() {
// vehicle target soc
// TODO take vehicle api limits into account
apiLimitSoc := 100
if vs, ok := lp.GetVehicle().(api.SocLimiter); ok {
// integrated device with charger limit
vs, ok := lp.charger.(api.SocLimiter)
if !ok {
// vehicle limit
vs, ok = lp.GetVehicle().(api.SocLimiter)
}
if ok {
if limit, err := vs.GetLimitSoc(); err == nil {
apiLimitSoc = int(limit)
lp.log.DEBUG.Printf("vehicle soc limit: %d%%", limit)

View file

@ -560,6 +560,7 @@ selfConsumption = "Eigenverbrauch"
[main.heatingStatus]
charging = "Heize …"
vehicleLimit = "Heizungslimit"
waitForVehicle = "Bereit. Warte auf Heizung …"
[main.loadpoint]
@ -711,6 +712,7 @@ scale3p = "Erhöhe gleich auf dreiphasiges Laden."
targetChargeActive = "Ladeplan aktiv. Geplantes Ende in {duration}."
targetChargePlanned = "Ladeplan startet in {duration}."
targetChargeWaitForVehicle = "Ladeplan bereit. Warte auf Fahrzeug …"
tempLimit = "Heizungslimit."
unknown = ""
vehicleLimit = "Fahrzeuglimit."
vehicleLimitReached = "Fahrzeuglimit erreicht."

View file

@ -559,6 +559,7 @@ selfConsumption = "Self-consumption"
[main.heatingStatus]
charging = "Heating…"
vehicleLimit = "Heater limit"
waitForVehicle = "Ready. Waiting for heater…"
[main.loadpoint]