Skip to content

Commit

Permalink
Loadpoint: add welcomecharge feature (#2) (#14873)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Jul 21, 2024
1 parent fbfe63f commit dbd4c4f
Show file tree
Hide file tree
Showing 25 changed files with 185 additions and 114 deletions.
1 change: 1 addition & 0 deletions api/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const (
IntegratedDevice
Heating
Retryable
WelcomeCharge
)
12 changes: 8 additions & 4 deletions api/feature_enumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions assets/js/components/Loadpoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export default {
// details
vehicleClimaterActive: Boolean,
vehicleWelcomeActive: Boolean,
chargePower: Number,
chargedEnergy: Number,
chargeRemainingDuration: Number,
Expand Down
17 changes: 17 additions & 0 deletions assets/js/components/MaterialIcon/Welcome.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<svg :style="svgStyle" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M11.875 20q.1 0 .2-.05t.15-.1l8.2-8.2q.3-.3.438-.675t.137-.75q0-.4-.137-.763t-.438-.637l-4.25-4.25q-.275-.3-.638-.437T14.776 4q-.375 0-.75.138t-.675.437l-.275.275l1.85 1.875q.375.35.55.8t.175.95q0 1.05-.712 1.763t-1.763.712q-.5 0-.962-.175t-.813-.525L9.525 8.4L5.15 12.775q-.075.075-.112.163T5 13.125q0 .2.15.363t.35.162q.1 0 .2-.05t.15-.1l3.4-3.4l1.4 1.4l-3.375 3.4q-.075.075-.112.163t-.038.187q0 .2.15.35t.35.15q.1 0 .2-.05t.15-.1l3.4-3.375l1.4 1.4l-3.375 3.4q-.075.05-.112.15t-.038.2q0 .2.15.35t.35.15q.1 0 .188-.038t.162-.112l3.4-3.375l1.4 1.4l-3.4 3.4q-.075.075-.112.162t-.038.188q0 .2.163.35t.362.15m-.025 2q-.925 0-1.637-.612t-.838-1.538q-.85-.125-1.425-.7t-.7-1.425q-.85-.125-1.412-.712T5.15 15.6q-.95-.125-1.55-.825t-.6-1.65q0-.5.188-.962t.537-.813l5.8-5.775L12.8 8.85q.05.075.15.113t.2.037q.225 0 .375-.137t.15-.363q0-.1-.038-.2t-.112-.15L9.95 4.575q-.275-.3-.637-.437T8.55 4q-.375 0-.75.138t-.675.437L3.6 8.125q-.225.225-.375.525t-.2.6t0 .613t.2.587l-1.45 1.45q-.425-.575-.625-1.262T1 9.25t.35-1.362t.825-1.188L5.7 3.175Q6.3 2.6 7.038 2.3T8.55 2t1.513.3t1.312.875l.275.275l.275-.275q.6-.575 1.338-.875t1.512-.3t1.513.3t1.312.875L21.825 7.4q.575.575.875 1.325t.3 1.525t-.3 1.513t-.875 1.312l-8.2 8.175q-.35.35-.812.55t-.963.2M9.375 8"
/>
</svg>
</template>

<script>
import icon from "../../mixins/icon";
export default {
name: "Welcome",
mixins: [icon],
};
</script>
1 change: 1 addition & 0 deletions assets/js/components/Vehicle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default {
chargedEnergy: Number,
charging: Boolean,
vehicleClimaterActive: Boolean,
vehicleWelcomeActive: Boolean,
connected: Boolean,
currency: String,
effectiveLimitSoc: Number,
Expand Down
3 changes: 3 additions & 0 deletions assets/js/components/VehicleStatus.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,8 @@ const planProjectedEnd = getFutureTime(5, 43);
vehicleClimaterActive
/>
</Variant>
<Variant title="welcome charge">
<VehicleStatus connected charging vehicleWelcomeActive />
</Variant>
</Story>
</template>
118 changes: 33 additions & 85 deletions assets/js/components/VehicleStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
>
<ClimaterIcon />
</div>
<div
v-if="vehicleWelcomeActive"
ref="vehicleWelcome"
data-bs-toggle="tooltip"
class="entry"
data-testid="vehicle-status-welcome"
>
<WelcomeIcon />
</div>

<!-- smart cost -->
<button
Expand Down Expand Up @@ -140,6 +149,7 @@ import VehicleMinSocIcon from "./MaterialIcon/VehicleMinSoc.vue";
import SunDownIcon from "./MaterialIcon/SunDown.vue";
import SunUpIcon from "./MaterialIcon/SunUp.vue";
import Tooltip from "bootstrap/js/dist/tooltip";
import WelcomeIcon from "./MaterialIcon/Welcome.vue";
export default {
name: "VehicleStatus",
Expand All @@ -152,6 +162,7 @@ export default {
VehicleMinSocIcon,
SunDownIcon,
SunUpIcon,
WelcomeIcon,
},
mixins: [formatter],
props: {
Expand Down Expand Up @@ -183,6 +194,7 @@ export default {
tariffCo2: Number,
tariffGrid: Number,
vehicleClimaterActive: Boolean,
vehicleWelcomeActive: Boolean,
vehicleLimitSoc: Number,
},
emits: ["open-loadpoint-settings", "open-minsoc-settings", "open-plan-modal"],
Expand All @@ -194,6 +206,7 @@ export default {
planActiveTooltip: null,
minSocTooltip: null,
vehicleClimaterTooltip: null,
vehicleWelcomeTooltip: null,
smartCostTooltip: null,
vehicleLimitTooltip: null,
};
Expand All @@ -205,6 +218,7 @@ export default {
this.updatePhaseTooltip();
this.updatePvTooltip();
this.updateVehicleClimaterTooltip();
this.updateVehicleWelcomeTooltip();
this.updateSmartCostTooltip();
this.updateVehicleLimitTooltip();
},
Expand All @@ -227,6 +241,9 @@ export default {
vehicleClimaterTooltipContent() {
this.$nextTick(this.updateVehicleClimaterTooltip);
},
vehicleWelcomeTooltipContent() {
this.$nextTick(this.updateVehicleWelcomeTooltip);
},
smartCostTooltipContent() {
this.$nextTick(this.updateSmartCostTooltip);
},
Expand Down Expand Up @@ -399,8 +416,17 @@ export default {
return "";
},
vehicleClimaterTooltipContent() {
if (!this.vehicleClimaterActive) {
return "";
}
return this.$t("main.vehicleStatus.climating");
},
vehicleWelcomeTooltipContent() {
if (!this.vehicleWelcomeActive) {
return "";
}
return this.$t("main.vehicleStatus.welcome");
},
chargerStatus() {
const t = (key, data) => {
if (this.heating) {
Expand Down Expand Up @@ -428,91 +454,6 @@ export default {
return t("charging");
}
return t("connected");
},
message: function () {
const t = (key, data) => {
if (this.heating) {
// check for special heating status translation
const name = `main.heatingStatus.${key}`;
if (this.$te(name, DEFAULT_LOCALE)) {
return this.$t(name, data);
}
}
return this.$t(`main.vehicleStatus.${key}`, data);
};
if (!this.connected) {
return t("disconnected");
}
// min charge active
if (this.minSoc > 0 && this.vehicleSoc < this.minSoc) {
return t("minCharge", { soc: this.fmtPercentage(this.minSoc) });
}
// plan
if (!this.chargingPlanDisabled && this.effectivePlanTime) {
if (this.planActive && this.charging) {
return t("targetChargeActive");
}
if (this.planActive && this.enabled) {
return t("targetChargeWaitForVehicle");
}
if (this.planProjectedStart) {
return t("targetChargePlanned", {
time: this.fmtAbsoluteDate(new Date(this.planProjectedStart)),
});
}
}
// clean or cheap energy
if (this.charging && this.smartCostActive) {
return this.smartCostType === CO2_TYPE
? t("cleanEnergyCharging", {
co2: this.fmtCo2Short(this.tariffCo2),
limit: this.fmtCo2Short(this.smartCostLimit),
})
: t("cheapEnergyCharging", {
price: this.fmtPricePerKWh(this.tariffGrid, this.currency, true),
limit: this.fmtPricePerKWh(this.smartCostLimit, this.currency, true),
});
}
if (this.pvTimerActive && !this.enabled && this.pvAction === "enable") {
return t("pvEnable", {
remaining: this.fmtDuration(this.pvRemainingInterpolated),
});
}
if (this.enabled && this.vehicleClimaterActive) {
return t("climating");
}
if (this.enabled && !this.charging) {
if (this.vehicleLimitSoc > 0 && this.vehicleSoc >= this.vehicleLimitSoc - 1) {
return t("vehicleLimitReached", {
soc: this.fmtPercentage(this.vehicleLimitSoc),
});
}
return t("waitForVehicle");
}
if (this.pvTimerActive && this.charging && this.pvAction === "disable") {
return t("pvDisable", {
remaining: this.fmtDuration(this.pvRemainingInterpolated),
});
}
if (this.phaseTimerActive) {
return t(this.phaseAction, {
remaining: this.fmtDuration(this.phaseRemainingInterpolated),
});
}
if (this.charging) {
return t("charging");
}
return t("connected");
},
},
Expand Down Expand Up @@ -587,6 +528,13 @@ export default {
this.$refs.vehicleClimater
);
},
updateVehicleWelcomeTooltip() {
this.vehicleWelcomeTooltip = this.updateTooltip(
this.vehicleWelcomeTooltip,
this.vehicleWelcomeTooltipContent,
this.$refs.vehicleWelcome
);
},
updateSmartCostTooltip() {
this.smartCostTooltip = this.updateTooltip(
this.smartCostTooltip,
Expand Down
19 changes: 13 additions & 6 deletions charger/keba-modbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

// Keba is an api.Charger implementation
type Keba struct {
*embed
log *util.Logger
conn *modbus.Connection
}
Expand Down Expand Up @@ -68,15 +69,20 @@ func init() {

// NewKebaFromConfig creates a new Keba ModbusTCP charger
func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: 255,
cc := struct {
embed `mapstructure:",squash"`
modbus.TcpSettings
}{
TcpSettings: modbus.TcpSettings{
ID: 255,
},
}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

wb, err := NewKeba(cc.URI, cc.ID)
wb, err := NewKeba(cc.embed, cc.URI, cc.ID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -129,7 +135,7 @@ func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
}

// NewKeba creates a new charger
func NewKeba(uri string, slaveID uint8) (*Keba, error) {
func NewKeba(embed embed, uri string, slaveID uint8) (*Keba, error) {
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
if err != nil {
return nil, err
Expand All @@ -143,8 +149,9 @@ func NewKeba(uri string, slaveID uint8) (*Keba, error) {
conn.Logger(log.TRACE)

wb := &Keba{
log: log,
conn: conn,
embed: &embed,
log: log,
conn: conn,
}

return wb, err
Expand Down
4 changes: 2 additions & 2 deletions core/coordinator/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func NewAdapter(lp loadpoint.API, c *Coordinator) API {
}
}

func (a *adapter) GetVehicles() []api.Vehicle {
return a.c.GetVehicles()
func (a *adapter) GetVehicles(availableOnly bool) []api.Vehicle {
return a.c.GetVehicles(availableOnly)
}

func (a *adapter) Owner(v api.Vehicle) loadpoint.API {
Expand Down
4 changes: 2 additions & 2 deletions core/coordinator/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

// API is the coordinator API
type API interface {
// GetVehicles returns the list of all vehicles
GetVehicles() []api.Vehicle
// GetVehicles returns the list of all vehicles, filtered by availability
GetVehicles(availableOnly bool) []api.Vehicle

// Owner returns the loadpoint that currently owns the vehicle
Owner(api.Vehicle) loadpoint.API
Expand Down
12 changes: 9 additions & 3 deletions core/coordinator/coordinator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package coordinator

import (
"slices"
"sync"

"github.com/evcc-io/evcc/api"
Expand All @@ -27,11 +26,18 @@ func New(log *util.Logger, vehicles []api.Vehicle) *Coordinator {
}

// GetVehicles returns the list of all vehicles
func (c *Coordinator) GetVehicles() []api.Vehicle {
func (c *Coordinator) GetVehicles(availableOnly bool) []api.Vehicle {
c.mu.RLock()
defer c.mu.RUnlock()

return slices.Clone(c.vehicles)
res := make([]api.Vehicle, 0, len(c.vehicles))
for _, v := range c.vehicles {
if _, tracked := c.tracked[v]; !availableOnly || availableOnly && !tracked {
res = append(res, v)
}
}

return res
}

// Owner returns the loadpoint that currently owns the vehicle
Expand Down
2 changes: 1 addition & 1 deletion core/coordinator/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func NewDummy() API {
return new(dummy)
}

func (a *dummy) GetVehicles() []api.Vehicle {
func (a *dummy) GetVehicles(_ bool) []api.Vehicle {
return nil
}

Expand Down
Loading

0 comments on commit dbd4c4f

Please sign in to comment.