From 1d80af32f05f75aca6b2594ebfed5f260ee88dcd Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Oct 2025 12:17:52 +0100 Subject: [PATCH 1/3] distinguish between plans added/removed in theme or settings --- packages/helpermodules/command.py | 67 ++++++++++++++++++------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index 2d1b79d96c..78664b5770 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -16,7 +16,7 @@ from control.chargepoint import chargepoint from control.chargepoint.chargepoint_template import get_chargepoint_template_default -from control.ev.charge_template import get_new_charge_template +from control.ev.charge_template import ChargeTemplate, get_new_charge_template from control.ev.ev_template import EvTemplateData from helpermodules import pub from helpermodules.abstract_plans import AutolockPlan, ScheduledChargingPlan, TimeChargingPlan @@ -488,13 +488,34 @@ def removeChargeTemplate(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Lade-Profil mit ID 0 darf nicht gelöscht werden.", MessageType.ERROR) + def _get_charge_template_by_source(self, payload: dict) -> ChargeTemplate: + """ gibt das ChargeTemplate-Objekt zurück, je nachdem ob es sich um ein temporäres Template handelt oder nicht. + """ + if payload["data"]["changed_in_theme"] == True: + charge_template = data.data.cp_data[f"cp{payload['data']['chargepoint']}"].data.set.charge_template + else: + charge_template = data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'] + return charge_template + + def _pub_charge_template_to_source(self, payload: dict, charge_template: ChargeTemplate) -> None: + """ veröffentlicht das ChargeTemplate-Objekt, je nachdem ob es sich um ein temporäres Template handelt oder nicht. + """ + if payload["data"]["changed_in_theme"] == True: + Pub().pub( + f'openWB/set/chargepoint/{payload["data"]["chargepoint"]}/set/charge_template', + dataclass_utils.asdict(charge_template.data)) + else: + Pub().pub( + f'openWB/set/vehicle/template/charge_template/{payload["data"]["template"]}', + dataclass_utils.asdict(charge_template.data)) + def addChargeTemplateSchedulePlan(self, connection_id: str, payload: dict) -> None: """ sendet das Topic, zu dem ein neuer Zielladen-Plan erstellt werden soll. """ + charge_template = self._get_charge_template_by_source(payload) # check if "payload" contains "data.copy" if "data" in payload and "copy" in payload["data"]: - for plan in data.data.ev_charge_template_data[ - f'ct{payload["data"]["template"]}'].data.chargemode.scheduled_charging.plans: + for plan in charge_template.data.chargemode.scheduled_charging.plans: if plan.id == payload["data"]["copy"]: new_charge_template_schedule_plan = copy.deepcopy(plan) break @@ -503,12 +524,8 @@ def addChargeTemplateSchedulePlan(self, connection_id: str, payload: dict) -> No new_charge_template_schedule_plan = ScheduledChargingPlan() new_id = self.max_id_charge_template_scheduled_plan + 1 new_charge_template_schedule_plan.id = new_id - data.data.ev_charge_template_data[ - f'ct{payload["data"]["template"]}'].data.chargemode.scheduled_charging.plans.append( - new_charge_template_schedule_plan) - Pub().pub( - f'openWB/set/vehicle/template/charge_template/{payload["data"]["template"]}', - dataclass_utils.asdict(data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data)) + charge_template.data.chargemode.scheduled_charging.plans.append(new_charge_template_schedule_plan) + self._pub_charge_template_to_source(payload, charge_template) self.max_id_charge_template_scheduled_plan = new_id Pub().pub( "openWB/set/command/max_id/charge_template_scheduled_plan", new_id) @@ -521,21 +538,17 @@ def addChargeTemplateSchedulePlan(self, connection_id: str, payload: dict) -> No def removeChargeTemplateSchedulePlan(self, connection_id: str, payload: dict) -> None: """ löscht einen Zielladen-Plan. """ + charge_template = self._get_charge_template_by_source(payload) if self.max_id_charge_template_scheduled_plan < payload["data"]["plan"]: log.error( payload, connection_id, f'Die ID \'{payload["data"]["plan"]}\' ist größer als die maximal vergebene ' f'ID \'{self.max_id_charge_template_scheduled_plan}\'.', MessageType.ERROR) - for plan in data.data.ev_charge_template_data[ - f'ct{payload["data"]["template"]}'].data.chargemode.scheduled_charging.plans: + for plan in charge_template.data.chargemode.scheduled_charging.plans: if plan.id == payload["data"]["plan"]: - data.data.ev_charge_template_data[ - f'ct{payload["data"]["template"]}'].data.chargemode.scheduled_charging.plans.remove( - plan) + charge_template.data.chargemode.scheduled_charging.plans.remove(plan) break - Pub().pub( - f'openWB/vehicle/template/charge_template/{payload["data"]["template"]}', - dataclass_utils.asdict(data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data)) + self._pub_charge_template_to_source(payload, charge_template) pub_user_message( payload, connection_id, f'Zielladen-Plan mit ID \'{payload["data"]["plan"]}\' von Profil ' @@ -545,9 +558,10 @@ def removeChargeTemplateSchedulePlan(self, connection_id: str, payload: dict) -> def addChargeTemplateTimeChargingPlan(self, connection_id: str, payload: dict) -> None: """ sendet das Topic, zu dem ein neuer Zeitladen-Plan erstellt werden soll. """ + charge_template = self._get_charge_template_by_source(payload) # check if "payload" contains "data.copy" if "data" in payload and "copy" in payload["data"]: - for plan in data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data.time_charging.plans: + for plan in charge_template.data.time_charging.plans: if plan.id == payload["data"]["copy"]: new_time_charging_plan = copy.deepcopy(plan) break @@ -556,11 +570,8 @@ def addChargeTemplateTimeChargingPlan(self, connection_id: str, payload: dict) - new_time_charging_plan = TimeChargingPlan() new_id = self.max_id_charge_template_time_charging_plan + 1 new_time_charging_plan.id = new_id - data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data.time_charging.plans.append( - new_time_charging_plan) - Pub().pub( - f'openWB/set/vehicle/template/charge_template/{payload["data"]["template"]}', - dataclass_utils.asdict(data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data)) + charge_template.data.time_charging.plans.append(new_time_charging_plan) + self._pub_charge_template_to_source(payload, charge_template) self.max_id_charge_template_time_charging_plan = new_id Pub().pub( "openWB/set/command/max_id/charge_template_time_charging_plan", new_id) @@ -572,17 +583,15 @@ def addChargeTemplateTimeChargingPlan(self, connection_id: str, payload: dict) - def removeChargeTemplateTimeChargingPlan(self, connection_id: str, payload: dict) -> None: """ löscht einen Zeitladen-Plan. """ + charge_template = self._get_charge_template_by_source(payload) if self.max_id_charge_template_time_charging_plan < payload["data"]["plan"]: log.error(payload, connection_id, "Die ID ist größer als die maximal vergebene ID.", MessageType.ERROR) - for plan in data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data.time_charging.plans: + for plan in charge_template.data.time_charging.plans: if plan.id == payload["data"]["plan"]: - data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data.time_charging.plans.remove( - plan) + charge_template.data.time_charging.plans.remove(plan) break - Pub().pub( - f'openWB/vehicle/template/charge_template/{payload["data"]["template"]}', - dataclass_utils.asdict(data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'].data)) + self._pub_charge_template_to_source(payload, charge_template) pub_user_message( payload, connection_id, f'Zeitladen-Plan mit ID \'{payload["data"]["plan"]}\' zu Profil ' From ccbc3120884c6ffa01e670989dc22e8066eae986 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Oct 2025 12:19:07 +0100 Subject: [PATCH 2/3] fix --- packages/helpermodules/command.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index 78664b5770..ba43da1506 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -489,18 +489,20 @@ def removeChargeTemplate(self, connection_id: str, payload: dict) -> None: MessageType.ERROR) def _get_charge_template_by_source(self, payload: dict) -> ChargeTemplate: - """ gibt das ChargeTemplate-Objekt zurück, je nachdem ob es sich um ein temporäres Template handelt oder nicht. + """ gibt das ChargeTemplate-Objekt zurück, je nachdem ob es sich um das persistente Ladeprofil oder das + Ladeprofil des Ladepunkts handelt. """ - if payload["data"]["changed_in_theme"] == True: + if payload["data"]["changed_in_theme"]: charge_template = data.data.cp_data[f"cp{payload['data']['chargepoint']}"].data.set.charge_template else: charge_template = data.data.ev_charge_template_data[f'ct{payload["data"]["template"]}'] return charge_template def _pub_charge_template_to_source(self, payload: dict, charge_template: ChargeTemplate) -> None: - """ veröffentlicht das ChargeTemplate-Objekt, je nachdem ob es sich um ein temporäres Template handelt oder nicht. + """ veröffentlicht das ChargeTemplate-Objekt, je nachdem ob es sich um das persistente Ladeprofil oder das + Ladeprofil des Ladepunkts handelt. """ - if payload["data"]["changed_in_theme"] == True: + if payload["data"]["changed_in_theme"]: Pub().pub( f'openWB/set/chargepoint/{payload["data"]["chargepoint"]}/set/charge_template', dataclass_utils.asdict(charge_template.data)) From ab30b0858a3ccea6f71c8323dd564f907e3be36e Mon Sep 17 00:00:00 2001 From: BrettS <168732306+Brett-S-OWB@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:19:57 +0100 Subject: [PATCH 3/3] Koala - Add Input dialog for editing scheduled and time plans (#2893) * Add scheduled details component * Add / remove scheduled charging plan - details as dialog * Add time plan details - adjust backend command * Backend command * Add DC charging inputs * Display bidi settings in scheduled plan based on vehicle profile bidi enabled * Fix DC power values for scheduled and time planes * Close plan dialog upon plan deletion * Plan energy amount watts to kilowatts * Formatting fixes * Charging current title AC or DC * Formatting fix * Change add plan button outlined to filled * Formatting Delete plan button * Add kW label to DC power input field * Format input fields * Format input toggles --- .../ChargePointScheduledPlanButton.vue | 2 +- .../ChargePointScheduledPlanDetails.vue | 404 ++++++++++++ .../ChargePointScheduledSettings.vue | 41 +- .../ChargePointTimeChargingPlanButton.vue | 2 +- .../ChargePointTimeChargingPlanDetails.vue | 300 +++++++++ .../ChargePointTimeChargingSettings.vue | 47 +- .../source/src/stores/mqtt-store-model.ts | 1 + .../koala/source/src/stores/mqtt-store.ts | 606 +++++++++++++++++- 8 files changed, 1381 insertions(+), 22 deletions(-) create mode 100644 packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanDetails.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanDetails.vue diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanButton.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanButton.vue index 935b8171c4..25341a1365 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanButton.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanButton.vue @@ -4,7 +4,7 @@ align="center" class="cursor-pointer" :color="planActive.value ? 'positive' : 'negative'" - @click="planActive.value = !planActive.value" + @click="$emit('editPlan', plan)" >
{{ plan.name }}
diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanDetails.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanDetails.vue new file mode 100644 index 0000000000..f8f308fdc8 --- /dev/null +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledPlanDetails.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledSettings.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledSettings.vue index 78a604b9ef..972dbac27b 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledSettings.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointScheduledSettings.vue @@ -2,6 +2,16 @@
Termine Zielladen:
+
+ +
+
+ + +
diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanButton.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanButton.vue index e478ff49b1..3ace7ee5bb 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanButton.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanButton.vue @@ -4,7 +4,7 @@ align="center" class="cursor-pointer" :color="planActive.value ? 'positive' : 'negative'" - @click="planActive.value = !planActive.value" + @click="$emit('editPlan', plan)" >
{{ plan.name }}
diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanDetails.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanDetails.vue new file mode 100644 index 0000000000..0dbf91f998 --- /dev/null +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingPlanDetails.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingSettings.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingSettings.vue index e4f34e0558..921db87d8e 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingSettings.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointTimeChargingSettings.vue @@ -3,12 +3,25 @@ class="row items-center q-ma-none q-pa-none no-wrap items-center justify-between" >
Zeitladen
+
-
+
Termine Zeitladen:
+
+ +
+ + + +
diff --git a/packages/modules/web_themes/koala/source/src/stores/mqtt-store-model.ts b/packages/modules/web_themes/koala/source/src/stores/mqtt-store-model.ts index 6fc5ef8958..38b2c50e1d 100644 --- a/packages/modules/web_themes/koala/source/src/stores/mqtt-store-model.ts +++ b/packages/modules/web_themes/koala/source/src/stores/mqtt-store-model.ts @@ -135,6 +135,7 @@ export interface ScheduledChargingPlan { name: string; active: boolean; bidi_charging_enabled: boolean; + bidi_power: number; et_active: boolean; current: number; dc_current: number; diff --git a/packages/modules/web_themes/koala/source/src/stores/mqtt-store.ts b/packages/modules/web_themes/koala/source/src/stores/mqtt-store.ts index 2659be64c4..7f5048bef5 100644 --- a/packages/modules/web_themes/koala/source/src/stores/mqtt-store.ts +++ b/packages/modules/web_themes/koala/source/src/stores/mqtt-store.ts @@ -1659,6 +1659,106 @@ export const useMqttStore = defineStore('mqtt', () => { }); }; + /** * Get the charge point connected vehicle bidi enabled state from vehicle template identified by the charge point id + * @param chargePointId charge point id + * @returns boolean + */ + const chargePointConnectedVehicleBidiEnabled = (chargePointId: number) => { + return computed(() => { + const connectedVehicleEvTemplateId = + chargePointConnectedVehicleConfig(chargePointId).value.ev_template; + return getValue.value( + `openWB/vehicle/template/ev_template/${connectedVehicleEvTemplateId}`, + 'bidi', + ) as boolean; + }); + }; + + /** + * Get or set the plan name for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanName = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.name; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic(chargePointId, planId, 'name', newValue); + }, + }); + }; + + /** + * Add a new time charging plan for a charge point + * @param chargePointId charge point id + * @returns void + */ + const addTimeChargingPlanForChargePoint = (chargePointId: number) => { + const templateId = + chargePointConnectedVehicleChargeTemplate(chargePointId).value?.id; + if (templateId !== undefined) { + sendSystemCommand('addChargeTemplateTimeChargingPlan', { + template: templateId, + chargepoint: chargePointId, + changed_in_theme: true, + }); + } + }; + + /** + * Remove a time charging plan for a charge point + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns void + */ + const removeTimeChargingPlanForChargePoint = ( + chargePointId: number, + planId: number, + ) => { + const templateId = + chargePointConnectedVehicleChargeTemplate(chargePointId).value?.id; + sendSystemCommand('removeChargeTemplateTimeChargingPlan', { + template: templateId, + plan: planId, + chargepoint: chargePointId, + changed_in_theme: true, + }); + }; + + /** + * Helper function to update a subtopic of a time charging plan + * @param chargePointId charge point id + * @param planId time charging plan id + * @param propertyPath path to the property to update + * @param newValue new value to set + * @returns void + */ + const updateTimeChargingPlanSubtopic = ( + chargePointId: number, + planId: number, + propertyPath: string, + newValue: T, + ): void => { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const planIndex = plans.findIndex((plan) => plan.id === planId); + if (planIndex === -1) return; + const objectPath = `time_charging.plans.${planIndex}.${propertyPath}`; + updateTopic( + `openWB/chargepoint/${chargePointId}/set/charge_template`, + newValue, + objectPath, + true, + ); + }; + /** * Get or set the active state of the time charging plan identified by the time charge plan id * @param chargePointId charge point id @@ -1676,15 +1776,345 @@ export const useMqttStore = defineStore('mqtt', () => { return plan?.active; }, set(newValue: boolean) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'active', + newValue, + ); + }, + }); + }; + + /** + * Get or set the start time for the time charging plan identified by the time charge plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanStartTime = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { const plans = vehicleTimeChargingPlans.value(chargePointId); - const planIndex = plans.findIndex((plan) => plan.id === planId); - if (planIndex === -1) return; - const objectPath = `time_charging.plans.${planIndex}.active`; - updateTopic( - `openWB/chargepoint/${chargePointId}/set/charge_template`, + const plan = plans.find((plane) => plane.id === planId); + return plan?.time?.[0]; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'time.0', + newValue, + ); + }, + }); + }; + + /** + * Get or set the start time for the time charging plan identified by the time charge plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanEndTime = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.time?.[1]; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'time.1', + newValue, + ); + }, + }); + }; + + /** + * Get or set the current for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns number | undefined + */ + const vehicleTimeChargingPlanCurrent = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.current; + }, + set(newValue: number) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'current', + newValue, + ); + }, + }); + }; + + /** + * Get or set the limit selected mode for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanLimitSelected = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.limit?.selected; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'limit.selected', + newValue, + ); + }, + }); + }; + + /** + * Get or set the SoC limit for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns number | undefined + */ + const vehicleTimeChargingPlanSocLimit = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.limit?.soc; + }, + set(newValue: number) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'limit.soc', + newValue, + ); + }, + }); + }; + + /** + * Get or set the energy amount limit for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns number | undefined + */ + const vehicleTimeChargingPlanEnergyAmount = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + const amount = plan?.limit?.amount; + if (amount === undefined) { + return; + } + const valueObject = getValueObject.value(amount, 'Wh', '', true); + return valueObject.scaledValue; + }, + set(newValue: number) { + const amountKiloWattHours = newValue * 1000; + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'limit.amount', + amountKiloWattHours, + ); + }, + }); + }; + + /** + * Get or set the frequency mode for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanFrequencySelected = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.frequency?.selected; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'frequency.selected', + newValue, + ); + }, + }); + }; + + /** + * Get or set the "valid from" date for the time charging plan identified by the time charging plan id (once) + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanOnceDateStart = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.frequency?.once?.[0]; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'frequency.once.0', + newValue, + ); + }, + }); + }; + + /** + * Get or set the "valid to" date for the time charging plan identified by the time charging plan id (once) + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns string | undefined + */ + const vehicleTimeChargingPlanOnceDateEnd = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.frequency?.once?.[1]; + }, + set(newValue: string) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'frequency.once.1', + newValue, + ); + }, + }); + }; + + /** + * Get or set the number of phases for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns number | undefined + */ + const vehicleTimeChargingPlanPhases = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.phases_to_use; + }, + set(newValue: number) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'phases_to_use', + newValue, + ); + }, + }); + }; + + /** + * Get or set the weekly days array for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns boolean[] | undefined + */ + const vehicleTimeChargingPlanWeeklyDays = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + return plan?.frequency?.weekly ?? Array(7).fill(false); + }, + set(newValue: boolean[]) { + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'frequency.weekly', newValue, - objectPath, - true, + ); + }, + }); + }; + + /** + * Get or set the DC charging power for the time charging plan identified by the time charging plan id + * @param chargePointId charge point id + * @param planId time charging plan id + * @returns boolean[] | undefined + */ + const vehicleTimeChargingPlanDcPower = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleTimeChargingPlans.value(chargePointId); + const plan = plans.find((plane) => plane.id === planId); + const current = plan?.dc_current; + const power = convertDcCurrentToPower(current); + const valueObject = getValueObject.value(power, 'W', '', true); + return valueObject.scaledValue; + }, + set(newValue) { + const current = convertPowerToDcCurrent(newValue); + updateTimeChargingPlanSubtopic( + chargePointId, + planId, + 'dc_current', + current, ); }, }); @@ -2171,6 +2601,38 @@ export const useMqttStore = defineStore('mqtt', () => { }; }); + function addScheduledChargingPlanForChargePoint(chargePointId: number) { + const templateId = + chargePointConnectedVehicleChargeTemplate(chargePointId).value?.id; + if (templateId !== undefined) { + sendSystemCommand('addChargeTemplateSchedulePlan', { + template: templateId, + chargepoint: chargePointId, + changed_in_theme: true, + }); + } else { + console.warn('Kein Template für ChargePoint gefunden:', chargePointId); + } + } + + function removeScheduledChargingPlanForChargePoint( + chargePointId: number, + planId: number, + ) { + const templateId = + chargePointConnectedVehicleChargeTemplate(chargePointId).value?.id; + if (templateId !== undefined) { + sendSystemCommand('removeChargeTemplateSchedulePlan', { + template: templateId, + plan: planId, + chargepoint: chargePointId, + changed_in_theme: true, + }); + } else { + console.warn('Kein Template für ChargePoint gefunden:', chargePointId); + } + } + /** * Get time charging plan/s data identified by the charge point id * @param chargePointId charge point id @@ -2418,11 +2880,12 @@ export const useMqttStore = defineStore('mqtt', () => { return valueObject.scaledValue; }, set(newValue: number) { + const amountKiloWattHours = newValue * 1000; updateScheduledChargingPlanSubtopic( chargePointId, planId, 'limit.amount', - newValue, + amountKiloWattHours, ); }, }); @@ -2441,7 +2904,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.name; }, set(newValue: string) { @@ -2468,7 +2931,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.time; }, set(newValue: string) { @@ -2495,7 +2958,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.frequency.selected; }, set(newValue: 'once' | 'daily' | 'weekly') { @@ -2522,7 +2985,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.frequency.once; }, set(newValue: string) { @@ -2549,7 +3012,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.frequency.weekly; }, set(newValue: boolean[]) { @@ -2576,7 +3039,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.limit.soc_limit; }, set(newValue: number) { @@ -2603,7 +3066,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.limit.soc_scheduled; }, set(newValue: number) { @@ -2629,7 +3092,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.phases_to_use; }, set(newValue: number) { @@ -2655,7 +3118,7 @@ export const useMqttStore = defineStore('mqtt', () => { return computed({ get() { const plans = vehicleScheduledChargingPlans.value(chargePointId); - const plan = plans.find((p) => p.id === planId); + const plan = plans.find((plan) => plan.id === planId); return plan?.phases_to_use_pv; }, set(newValue: number) { @@ -2669,6 +3132,94 @@ export const useMqttStore = defineStore('mqtt', () => { }); }; + /** + * Get or set the bidirectional charging enabled state for a scheduled charging plan + * @param chargePointId charge point id + * @param planId scheduled charging plan id + * @returns boolean | undefined + */ + const vehicleScheduledChargingPlanBidiEnabled = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleScheduledChargingPlans.value(chargePointId); + const plan = plans.find((plan) => plan.id === planId); + return plan?.bidi_charging_enabled; + }, + set(newValue: boolean) { + updateScheduledChargingPlanSubtopic( + chargePointId, + planId, + 'bidi_charging_enabled', + newValue, + ); + }, + }); + }; + + /** + * Get or set the bidirectional charging enabled state for a scheduled charging plan + * @param chargePointId charge point id + * @param planId scheduled charging plan id + * @returns boolean | undefined + */ + const vehicleScheduledChargingPlanBidiPower = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleScheduledChargingPlans.value(chargePointId); + const plan = plans.find((plan) => plan.id === planId); + const power = plan?.bidi_power; + const valueObject = getValueObject.value(power, 'W', '', true); + return valueObject.scaledValue; + }, + set(newValue: number) { + const watts = Math.round(newValue * 1000); + updateScheduledChargingPlanSubtopic( + chargePointId, + planId, + 'bidi_power', + watts, + ); + }, + }); + }; + + /** + * Get or set the DC charging power for a scheduled charging plan (plan.dc_current) + * @param chargePointId charge point id + * @param planId scheduled charging plan id + * @returns number | undefined (in kW) + */ + const vehicleScheduledChargingPlanDcPower = ( + chargePointId: number, + planId: number, + ) => { + return computed({ + get() { + const plans = vehicleScheduledChargingPlans.value(chargePointId); + const plan = plans.find((plan) => plan.id === planId); + const current = plan?.dc_current; + const power = convertDcCurrentToPower(current); + const valueObject = getValueObject.value(power, 'W', '', true); + return valueObject.scaledValue; + }, + set(newValue: number) { + const current = convertPowerToDcCurrent(newValue); + updateScheduledChargingPlanSubtopic( + chargePointId, + planId, + 'dc_current', + current, + ); + }, + }); + }; + /////////////////////////////// Grid Data ///////////////////////////////////// /** @@ -2898,6 +3449,7 @@ export const useMqttStore = defineStore('mqtt', () => { chargePointConnectedVehiclePriority, chargePointConnectedVehicleTimeCharging, chargePointConnectedVehicleChargeTemplate, + chargePointConnectedVehicleBidiEnabled, // vehicle data vehicleList, chargePointConnectedVehicleConfig, @@ -2910,6 +3462,8 @@ export const useMqttStore = defineStore('mqtt', () => { chargePointConnectedVehicleSoc, vehicleActivePlan, vehicleChargeTarget, + addScheduledChargingPlanForChargePoint, + removeScheduledChargingPlanForChargePoint, vehicleScheduledChargingPlans, vehicleScheduledChargingPlanActive, vehicleScheduledChargingPlanEtActive, @@ -2925,8 +3479,26 @@ export const useMqttStore = defineStore('mqtt', () => { vehicleScheduledChargingPlanSocScheduled, vehicleScheduledChargingPlanPhases, vehicleScheduledChargingPlanPhasesPv, + vehicleScheduledChargingPlanBidiEnabled, + vehicleScheduledChargingPlanBidiPower, + vehicleScheduledChargingPlanDcPower, vehicleTimeChargingPlans, + vehicleTimeChargingPlanName, + addTimeChargingPlanForChargePoint, + removeTimeChargingPlanForChargePoint, vehicleTimeChargingPlanActive, + vehicleTimeChargingPlanStartTime, + vehicleTimeChargingPlanEndTime, + vehicleTimeChargingPlanCurrent, + vehicleTimeChargingPlanLimitSelected, + vehicleTimeChargingPlanSocLimit, + vehicleTimeChargingPlanEnergyAmount, + vehicleTimeChargingPlanFrequencySelected, + vehicleTimeChargingPlanOnceDateStart, + vehicleTimeChargingPlanOnceDateEnd, + vehicleTimeChargingPlanPhases, + vehicleTimeChargingPlanWeeklyDays, + vehicleTimeChargingPlanDcPower, chargePointConnectedVehicleSocType, chargePointConnectedVehicleSocManual, // Battery data