From bc0343ab7da185d55715c3ecccd2b4308c811da2 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 20 Jan 2024 12:19:41 +0100 Subject: [PATCH] Vehicles: move more properties to /vehicles (BC) (#11181) --- api/api.go | 76 +----------------- api/chargemodestatus.go | 79 +++++++++++++++++++ api/mock.go | 14 ++++ assets/js/components/ChargingPlan.vue | 4 +- assets/js/components/ChargingPlanSettings.vue | 6 +- .../components/ChargingPlanSettingsEntry.vue | 6 +- assets/js/components/ChargingSessionModal.vue | 1 - assets/js/components/LimitEnergySelect.vue | 6 +- assets/js/components/Loadpoint.story.vue | 6 +- assets/js/components/Loadpoint.vue | 6 +- assets/js/components/Loadpoints.story.vue | 2 +- assets/js/components/Site.vue | 3 +- assets/js/components/Vehicle.story.vue | 27 ++++--- assets/js/components/Vehicle.vue | 19 +++-- assets/js/components/VehicleOptions.vue | 3 +- assets/js/components/VehicleSoc.vue | 1 - assets/js/components/VehicleTitle.vue | 19 +++-- core/keys/loadpoint.go | 5 +- core/loadpoint.go | 2 - core/loadpoint_test.go | 5 +- core/loadpoint_vehicle.go | 11 --- core/loadpoint_vehicle_test.go | 23 +++--- core/site_vehicles.go | 13 ++- push/hub.go | 5 +- vehicle/embed.go | 1 - vehicle/wrapper/wrapper.go | 1 + 26 files changed, 182 insertions(+), 162 deletions(-) create mode 100644 api/chargemodestatus.go 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 }} -
  • +