diff --git a/api/api.go b/api/api.go
index 7cdcba1e6..85ff24b5b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -2,88 +2,13 @@ package api
import (
"context"
- "fmt"
"io"
"net/http"
- "strings"
"time"
)
//go:generate mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,PhaseSwitcher,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery,Tariff,BatteryController
-// ChargeMode is the charge operation mode. Valid values are off, now, minpv and pv
-type ChargeMode string
-
-// Charge modes
-const (
- ModeEmpty ChargeMode = ""
- ModeOff ChargeMode = "off"
- ModeNow ChargeMode = "now"
- ModeMinPV ChargeMode = "minpv"
- ModePV ChargeMode = "pv"
-)
-
-// String implements Stringer
-func (c ChargeMode) String() string {
- return string(c)
-}
-
-// ChargeStatus is the EV's charging status from A to F
-type ChargeStatus string
-
-// Charging states
-const (
- StatusNone ChargeStatus = ""
- StatusA ChargeStatus = "A" // Fzg. angeschlossen: nein Laden aktiv: nein Ladestation betriebsbereit, Fahrzeug getrennt
- StatusB ChargeStatus = "B" // Fzg. angeschlossen: ja Laden aktiv: nein Fahrzeug verbunden, Netzspannung liegt nicht an
- StatusC ChargeStatus = "C" // Fzg. angeschlossen: ja Laden aktiv: ja Fahrzeug lädt, Netzspannung liegt an
- StatusD ChargeStatus = "D" // Fzg. angeschlossen: ja Laden aktiv: ja Fahrzeug lädt mit externer Belüfungsanforderung (für Blei-Säure-Batterien)
- StatusE ChargeStatus = "E" // Fzg. angeschlossen: ja Laden aktiv: nein Fehler Fahrzeug / Kabel (CP-Kurzschluss, 0V)
- StatusF ChargeStatus = "F" // Fzg. angeschlossen: ja Laden aktiv: nein Fehler EVSE oder Abstecken simulieren (CP-Wake-up, -12V)
-)
-
-var StatusEasA = map[ChargeStatus]ChargeStatus{StatusE: StatusA}
-
-// ChargeStatusString converts a string to ChargeStatus
-func ChargeStatusString(status string) (ChargeStatus, error) {
- s := strings.ToUpper(strings.Trim(status, "\x00 "))
-
- if len(s) == 0 {
- return StatusNone, fmt.Errorf("invalid status: %s", status)
- }
-
- switch s1 := s[:1]; s1 {
- case "A", "B":
- return ChargeStatus(s1), nil
-
- case "C", "D":
- if s == "C1" || s == "D1" {
- return StatusB, nil
- }
- return StatusC, nil
-
- case "E", "F":
- return ChargeStatus(s1), fmt.Errorf("invalid status: %s", s)
-
- default:
- return StatusNone, fmt.Errorf("invalid status: %s", status)
- }
-}
-
-// ChargeStatusStringWithMapping converts a string to ChargeStatus. In case of error, mapping is applied.
-func ChargeStatusStringWithMapping(s string, m map[ChargeStatus]ChargeStatus) (ChargeStatus, error) {
- status, err := ChargeStatusString(s)
- if mappedStatus, ok := m[status]; ok && err != nil {
- return mappedStatus, nil
- }
- return status, err
-}
-
-// String implements Stringer
-func (c ChargeStatus) String() string {
- return string(c)
-}
-
// Meter provides total active power in W
type Meter interface {
CurrentPower() (float64, error)
@@ -187,6 +112,7 @@ type Vehicle interface {
Battery
BatteryCapacity
IconDescriber
+ FeatureDescriber
Title() string
SetTitle(string)
Phases() int
diff --git a/api/chargemodestatus.go b/api/chargemodestatus.go
new file mode 100644
index 000000000..5fe98baff
--- /dev/null
+++ b/api/chargemodestatus.go
@@ -0,0 +1,79 @@
+package api
+
+import (
+ "fmt"
+ "strings"
+)
+
+// ChargeMode is the charge operation mode. Valid values are off, now, minpv and pv
+type ChargeMode string
+
+// Charge modes
+const (
+ ModeEmpty ChargeMode = ""
+ ModeOff ChargeMode = "off"
+ ModeNow ChargeMode = "now"
+ ModeMinPV ChargeMode = "minpv"
+ ModePV ChargeMode = "pv"
+)
+
+// String implements Stringer
+func (c ChargeMode) String() string {
+ return string(c)
+}
+
+// ChargeStatus is the EV's charging status from A to F
+type ChargeStatus string
+
+// Charging states
+const (
+ StatusNone ChargeStatus = ""
+ StatusA ChargeStatus = "A" // Fzg. angeschlossen: nein Laden aktiv: nein Ladestation betriebsbereit, Fahrzeug getrennt
+ StatusB ChargeStatus = "B" // Fzg. angeschlossen: ja Laden aktiv: nein Fahrzeug verbunden, Netzspannung liegt nicht an
+ StatusC ChargeStatus = "C" // Fzg. angeschlossen: ja Laden aktiv: ja Fahrzeug lädt, Netzspannung liegt an
+ StatusD ChargeStatus = "D" // Fzg. angeschlossen: ja Laden aktiv: ja Fahrzeug lädt mit externer Belüfungsanforderung (für Blei-Säure-Batterien)
+ StatusE ChargeStatus = "E" // Fzg. angeschlossen: ja Laden aktiv: nein Fehler Fahrzeug / Kabel (CP-Kurzschluss, 0V)
+ StatusF ChargeStatus = "F" // Fzg. angeschlossen: ja Laden aktiv: nein Fehler EVSE oder Abstecken simulieren (CP-Wake-up, -12V)
+)
+
+var StatusEasA = map[ChargeStatus]ChargeStatus{StatusE: StatusA}
+
+// ChargeStatusString converts a string to ChargeStatus
+func ChargeStatusString(status string) (ChargeStatus, error) {
+ s := strings.ToUpper(strings.Trim(status, "\x00 "))
+
+ if len(s) == 0 {
+ return StatusNone, fmt.Errorf("invalid status: %s", status)
+ }
+
+ switch s1 := s[:1]; s1 {
+ case "A", "B":
+ return ChargeStatus(s1), nil
+
+ case "C", "D":
+ if s == "C1" || s == "D1" {
+ return StatusB, nil
+ }
+ return StatusC, nil
+
+ case "E", "F":
+ return ChargeStatus(s1), fmt.Errorf("invalid status: %s", s)
+
+ default:
+ return StatusNone, fmt.Errorf("invalid status: %s", status)
+ }
+}
+
+// ChargeStatusStringWithMapping converts a string to ChargeStatus. In case of error, mapping is applied.
+func ChargeStatusStringWithMapping(s string, m map[ChargeStatus]ChargeStatus) (ChargeStatus, error) {
+ status, err := ChargeStatusString(s)
+ if mappedStatus, ok := m[status]; ok && err != nil {
+ return mappedStatus, nil
+ }
+ return status, err
+}
+
+// String implements Stringer
+func (c ChargeStatus) String() string {
+ return string(c)
+}
diff --git a/api/mock.go b/api/mock.go
index 205167343..c3f79d525 100644
--- a/api/mock.go
+++ b/api/mock.go
@@ -361,6 +361,20 @@ func (mr *MockVehicleMockRecorder) Capacity() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capacity", reflect.TypeOf((*MockVehicle)(nil).Capacity))
}
+// Features mocks base method.
+func (m *MockVehicle) Features() []Feature {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Features")
+ ret0, _ := ret[0].([]Feature)
+ return ret0
+}
+
+// Features indicates an expected call of Features.
+func (mr *MockVehicleMockRecorder) Features() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Features", reflect.TypeOf((*MockVehicle)(nil).Features))
+}
+
// Icon mocks base method.
func (m *MockVehicle) Icon() string {
m.ctrl.T.Helper()
diff --git a/assets/js/components/ChargingPlan.vue b/assets/js/components/ChargingPlan.vue
index 1c819dd88..c69b90739 100644
--- a/assets/js/components/ChargingPlan.vue
+++ b/assets/js/components/ChargingPlan.vue
@@ -133,7 +133,7 @@ export default {
socBasedCharging: Boolean,
socPerKwh: Number,
vehicle: Object,
- vehicleCapacity: Number,
+ capacity: Number,
vehicleSoc: Number,
vehicleTargetSoc: Number,
},
@@ -208,7 +208,7 @@ export default {
}
return fmtEnergy(
this.planEnergy,
- optionStep(this.vehicleCapacity || 100),
+ optionStep(this.capacity || 100),
this.fmtKWh,
this.$t("main.targetEnergy.noLimit")
);
diff --git a/assets/js/components/ChargingPlanSettings.vue b/assets/js/components/ChargingPlanSettings.vue
index 8f0373fa3..f86fa220f 100644
--- a/assets/js/components/ChargingPlanSettings.vue
+++ b/assets/js/components/ChargingPlanSettings.vue
@@ -6,7 +6,7 @@
:id="`${id}_0`"
class="mb-2"
v-bind="plans[0] || {}"
- :vehicle-capacity="vehicleCapacity"
+ :capacity="capacity"
:range-per-soc="rangePerSoc"
:soc-per-kwh="socPerKwh"
:soc-based-planning="socBasedPlanning"
@@ -58,7 +58,7 @@ export default {
smartCostType: String,
currency: String,
mode: String,
- vehicleCapacity: Number,
+ capacity: Number,
vehicle: Object,
vehicleTargetSoc: Number,
},
@@ -161,7 +161,7 @@ export default {
this.$emit("plan-updated", {
time: this.defaultDate(),
soc: 100,
- energy: this.vehicleCapacity || 10,
+ energy: this.capacity || 10,
});
},
removePlan: function (index) {
diff --git a/assets/js/components/ChargingPlanSettingsEntry.vue b/assets/js/components/ChargingPlanSettingsEntry.vue
index e3cacb726..5d83c410d 100644
--- a/assets/js/components/ChargingPlanSettingsEntry.vue
+++ b/assets/js/components/ChargingPlanSettingsEntry.vue
@@ -147,7 +147,7 @@ export default {
time: String,
rangePerSoc: Number,
socPerKwh: Number,
- vehicleCapacity: Number,
+ capacity: Number,
socBasedPlanning: Boolean,
},
emits: ["plan-updated", "plan-removed", "plan-preview"],
@@ -173,7 +173,7 @@ export default {
energyOptions: function () {
const options = energyOptions(
0,
- this.vehicleCapacity || 100,
+ this.capacity || 100,
this.socPerKwh,
this.fmtKWh,
"-"
@@ -241,7 +241,7 @@ export default {
}
if (!this.selectedEnergy) {
this.selectedEnergy =
- window.localStorage[LAST_ENERGY_GOAL_KEY] || this.vehicleCapacity || 10;
+ window.localStorage[LAST_ENERGY_GOAL_KEY] || this.capacity || 10;
}
let time = this.time;
diff --git a/assets/js/components/ChargingSessionModal.vue b/assets/js/components/ChargingSessionModal.vue
index 405a03066..6721878ac 100644
--- a/assets/js/components/ChargingSessionModal.vue
+++ b/assets/js/components/ChargingSessionModal.vue
@@ -38,7 +38,6 @@
:id="session.vehicle"
class="options"
:vehicles="vehicles"
- :is-unknown="false"
connected
@change-vehicle="changeVehicle"
@remove-vehicle="removeVehicle"
diff --git a/assets/js/components/LimitEnergySelect.vue b/assets/js/components/LimitEnergySelect.vue
index e55d0bb79..f976c95c4 100644
--- a/assets/js/components/LimitEnergySelect.vue
+++ b/assets/js/components/LimitEnergySelect.vue
@@ -47,21 +47,21 @@ export default {
limitEnergy: Number,
socPerKwh: Number,
chargedEnergy: Number,
- vehicleCapacity: Number,
+ capacity: Number,
},
emits: ["limit-energy-updated"],
computed: {
options: function () {
return energyOptions(
this.chargedEnergy,
- this.vehicleCapacity || 100,
+ this.capacity || 100,
this.socPerKwh,
this.fmtKWh,
this.$t("main.targetEnergy.noLimit")
);
},
step() {
- return optionStep(this.vehicleCapacity || 100);
+ return optionStep(this.capacity || 100);
},
estimated: function () {
return estimatedSoc(this.limitEnergy, this.socPerKwh);
diff --git a/assets/js/components/Loadpoint.story.vue b/assets/js/components/Loadpoint.story.vue
index 6c5ae56a6..be8bb5b55 100644
--- a/assets/js/components/Loadpoint.story.vue
+++ b/assets/js/components/Loadpoint.story.vue
@@ -8,8 +8,8 @@ const state = reactive({
chargePower: 2800,
chargedEnergy: 11e3,
chargeDuration: 95 * 60,
- vehiclePresent: true,
vehicleTitle: "Mein Auto",
+ vehicleName: "meinauto",
enabled: true,
connected: true,
mode: "pv",
@@ -29,14 +29,14 @@ const state = reactive({
-
+
0;
},
socBasedPlanning: function () {
- return this.socBasedCharging && this.vehicleCapacity > 0;
+ return this.socBasedCharging && this.vehicle?.capacity > 0;
},
},
watch: {
diff --git a/assets/js/components/Loadpoints.story.vue b/assets/js/components/Loadpoints.story.vue
index a789dad6c..8c90bb77e 100644
--- a/assets/js/components/Loadpoints.story.vue
+++ b/assets/js/components/Loadpoints.story.vue
@@ -9,7 +9,7 @@ function loadpoint(opts) {
chargePower: 2800,
chargedEnergy: 11e3,
chargeDuration: 95 * 60,
- vehiclePresent: true,
+ vehicleName: "tesla",
vehicleTitle: "Tesla Model 3",
enabled: true,
connected: true,
diff --git a/assets/js/components/Site.vue b/assets/js/components/Site.vue
index aec1d25dd..86f1f26aa 100644
--- a/assets/js/components/Site.vue
+++ b/assets/js/components/Site.vue
@@ -111,7 +111,8 @@ export default {
},
loadpointsCompact: function () {
return this.loadpoints.map((lp) => {
- const icon = lp.chargerIcon || lp.vehicleIcon || "car";
+ const vehicleIcon = this.vehicles?.[lp.vehicleName]?.icon;
+ const icon = lp.chargerIcon || vehicleIcon || "car";
const charging = lp.charging;
const power = lp.chargePower || 0;
return { icon, charging, power };
diff --git a/assets/js/components/Vehicle.story.vue b/assets/js/components/Vehicle.story.vue
index 29f37ca3c..b3a7f67a0 100644
--- a/assets/js/components/Vehicle.story.vue
+++ b/assets/js/components/Vehicle.story.vue
@@ -3,14 +3,13 @@ import { reactive } from "vue";
import Vehicle from "./Vehicle.vue";
const state = reactive({
- vehicleTitle: "Mein Auto",
+ vehicle: { title: "Mein Auto", icon: "car", capacity: 72, features: [] },
enabled: false,
connected: true,
- vehiclePresent: true,
+ vehicleName: "meinauto",
vehicleSoc: 42.742,
vehicleRange: 231,
limitSoc: 90,
- vehicleCapacity: 72,
chargedEnergy: 14123,
socBasedCharging: true,
id: 0,
@@ -45,9 +44,9 @@ const hoursFromNow = function (hours) {
v-bind="state"
enabled
charging
- :vehiclePresent="false"
+ vehicleName=""
:socBasedCharging="false"
- :vehicleCapacity="null"
+ :vehicle="{ ...state.vehicle, capacity: null }"
mode="pv"
/>
@@ -56,10 +55,13 @@ const hoursFromNow = function (hours) {
v-bind="state"
enabled
charging
- vehicleTitle="Opel Corsa-e"
:socBasedCharging="false"
- :vehicleCapacity="72"
- vehicleFeatureOffline
+ :vehicle="{
+ ...state.vehicle,
+ title: 'Opel Corsa-e',
+ capacity: 72,
+ features: ['Offline'],
+ }"
mode="pv"
/>
@@ -68,11 +70,14 @@ const hoursFromNow = function (hours) {
v-bind="state"
enabled
charging
- vehicleTitle="Opel Corsa-e"
:socBasedCharging="false"
- :vehicleCapacity="72"
+ :vehicle="{
+ ...state.vehicle,
+ title: 'Opel Corsa-e',
+ capacity: 72,
+ features: ['Offline'],
+ }"
:targetEnergy="30"
- vehicleFeatureOffline
mode="pv"
/>
diff --git a/assets/js/components/Vehicle.vue b/assets/js/components/Vehicle.vue
index f78bcd159..cae5566ab 100644
--- a/assets/js/components/Vehicle.vue
+++ b/assets/js/components/Vehicle.vue
@@ -52,7 +52,7 @@
:limit-energy="limitEnergy"
:soc-per-kwh="socPerKwh"
:charged-energy="chargedEnergy"
- :vehicle-capacity="vehicleCapacity"
+ :capacity="capacity"
@limit-energy-updated="limitEnergyUpdated"
/>
@@ -117,16 +117,12 @@ export default {
tariffCo2: Number,
tariffGrid: Number,
vehicle: Object,
- vehicleCapacity: Number,
vehicleDetectionActive: Boolean,
- vehicleIcon: String,
vehicleName: String,
- vehiclePresent: Boolean,
vehicleRange: Number,
vehicles: Array,
vehicleSoc: Number,
vehicleTargetSoc: Number,
- vehicleTitle: String,
},
emits: ["limit-soc-updated", "limit-energy-updated", "change-vehicle", "remove-vehicle"],
data() {
@@ -135,6 +131,15 @@ export default {
};
},
computed: {
+ title: function () {
+ return this.vehicle?.title || "";
+ },
+ capacity: function () {
+ return this.vehicle?.capacity || 0;
+ },
+ icon: function () {
+ return this.vehicle?.icon || "";
+ },
minSoc: function () {
return this.vehicle?.minSoc || 0;
},
@@ -178,8 +183,8 @@ export default {
return null;
},
socPerKwh: function () {
- if (this.vehicleCapacity > 0) {
- return 100 / this.vehicleCapacity;
+ if (this.capacity > 0) {
+ return 100 / this.capacity;
}
return null;
},
diff --git a/assets/js/components/VehicleOptions.vue b/assets/js/components/VehicleOptions.vue
index e86ebf115..84a876e39 100644
--- a/assets/js/components/VehicleOptions.vue
+++ b/assets/js/components/VehicleOptions.vue
@@ -19,7 +19,7 @@
{{ vehicle.title }}
-
+