From 38e8988f75294cbe8b36f84140e7bc55937abe86 Mon Sep 17 00:00:00 2001 From: BrettS Date: Tue, 17 Jun 2025 11:36:34 +0200 Subject: [PATCH 1/2] Koala updates - tables - dc charging - history chart legend --- .../koala/source/src/components/BaseTable.vue | 185 ++++++++++---- .../source/src/components/BatteryCard.vue | 44 ++-- .../source/src/components/ChargePointCard.vue | 43 ++-- .../src/components/ChargePointEcoSettings.vue | 17 ++ .../src/components/ChargePointInformation.vue | 166 +++++++++---- .../components/ChargePointInstantSettings.vue | 18 ++ .../source/src/components/ChargePointMode.vue | 31 +++ .../src/components/ChargePointPowerData.vue | 29 +++ .../src/components/ChargePointPvSettings.vue | 25 +- .../src/components/ChargePointSettings.vue | 1 - .../src/components/ChargePointStateIcon.vue | 28 ++- .../source/src/components/ManualSocDialog.vue | 129 ++++++++++ .../source/src/components/VehicleCard.vue | 110 ++++----- .../components/VehicleConnectionStateIcon.vue | 51 ++++ .../src/components/VehicleInformation.vue | 94 +++---- .../charts/historyChart/HistoryChart.vue | 41 +++- .../historyChart/HistoryChartLegend.vue | 147 +++++++++++ .../src/components/models/table-model.ts | 40 +++ .../source/src/composables/useChargeModes.ts | 2 +- .../source/src/css/quasar.variables.scss | 24 ++ .../source/src/stores/mqtt-store-model.ts | 4 +- .../koala/source/src/stores/mqtt-store.ts | 230 +++++++++++++++--- 22 files changed, 1153 insertions(+), 306 deletions(-) create mode 100644 packages/modules/web_themes/koala/source/src/components/ChargePointMode.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/ChargePointPowerData.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/ManualSocDialog.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/VehicleConnectionStateIcon.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/charts/historyChart/HistoryChartLegend.vue create mode 100644 packages/modules/web_themes/koala/source/src/components/models/table-model.ts diff --git a/packages/modules/web_themes/koala/source/src/components/BaseTable.vue b/packages/modules/web_themes/koala/source/src/components/BaseTable.vue index 8d4391321c..902af8e808 100644 --- a/packages/modules/web_themes/koala/source/src/components/BaseTable.vue +++ b/packages/modules/web_themes/koala/source/src/components/BaseTable.vue @@ -5,6 +5,7 @@ :rows="mappedRows" :columns="mappedColumns" row-key="id" + v-model:expanded="expanded" :filter="filterModel" :filter-method="customFilterMethod" virtual-scroll @@ -16,7 +17,8 @@ :pagination="{ rowsPerPage: 0 }" hide-bottom > - diff --git a/packages/modules/web_themes/koala/source/src/components/charts/historyChart/HistoryChartLegend.vue b/packages/modules/web_themes/koala/source/src/components/charts/historyChart/HistoryChartLegend.vue new file mode 100644 index 0000000000..393509c390 --- /dev/null +++ b/packages/modules/web_themes/koala/source/src/components/charts/historyChart/HistoryChartLegend.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/packages/modules/web_themes/koala/source/src/components/models/table-model.ts b/packages/modules/web_themes/koala/source/src/components/models/table-model.ts new file mode 100644 index 0000000000..411594f309 --- /dev/null +++ b/packages/modules/web_themes/koala/source/src/components/models/table-model.ts @@ -0,0 +1,40 @@ +import { QTableColumn } from 'quasar'; + +export type ColumnConfiguration = { + field: string; + label: string; + align?: 'left' | 'right' | 'center'; + expandField?: boolean; +}; + +export interface BodySlotProps { + key: string | number; + row: T; + cols: QTableColumn[]; + expand: boolean; +} + +export interface ChargePointRow extends Record { + id: number; + name: string | undefined; + vehicle: string; + plugged: boolean; + chargeMode: string | undefined; + timeCharging: boolean | undefined; + soc: string; + power: string; + phaseNumber: number; + current: string; + powerColumn: ''; + charged: string; +} + +export interface VehicleRow extends Record { + id: number; + name: string; + manufacturer: string; + model: string; + plugState: boolean; + chargeState: boolean; + vehicleSocValue: string; +} diff --git a/packages/modules/web_themes/koala/source/src/composables/useChargeModes.ts b/packages/modules/web_themes/koala/source/src/composables/useChargeModes.ts index 340cd59a79..60e9238e0f 100644 --- a/packages/modules/web_themes/koala/source/src/composables/useChargeModes.ts +++ b/packages/modules/web_themes/koala/source/src/composables/useChargeModes.ts @@ -3,7 +3,7 @@ export const useChargeModes = () => { { value: 'instant_charging', label: 'Sofort', color: 'negative' }, { value: 'pv_charging', label: 'PV', color: 'positive' }, { value: 'scheduled_charging', label: 'Ziel', color: 'primary' }, - { value: 'eco_charging', label: 'Eco', color: 'secondary' }, + { value: 'eco_charging', label: 'Eco', color: 'accent' }, { value: 'stop', label: 'Stop', color: 'light' }, ]; return { diff --git a/packages/modules/web_themes/koala/source/src/css/quasar.variables.scss b/packages/modules/web_themes/koala/source/src/css/quasar.variables.scss index 001ade7642..57aa50cd25 100644 --- a/packages/modules/web_themes/koala/source/src/css/quasar.variables.scss +++ b/packages/modules/web_themes/koala/source/src/css/quasar.variables.scss @@ -191,6 +191,26 @@ $battery: #ba7128; scrollbar-color: var(--q-primary) var(--q-secondary); } } + + //table padding + .q-table th, + .q-table td { + padding: 2px 6px !important; + } + + .q-table th:first-child, + .q-table td:first-child { + padding-left: 12px !important; + } + + .q-table th:last-child, + .q-table td:last-child { + padding-right: 12px !important; + } + // Scroll area styling + .q-scrollarea { + border: 1px solid var(--q-secondary) !important; + } } // Dark Theme Base Colors $dark-page: #000000; // This overrides Quasar's default dark page color @@ -376,4 +396,8 @@ $dark-tab-icon: #d7d9e0; scrollbar-color: var(--q-primary) var(--q-secondary); } } + + .q-scrollarea { + border: 1px solid var(--q-secondary) !important; + } } 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 906c80e817..42552ae729 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 @@ -122,12 +122,12 @@ export interface Vehicle { id: number; name: string; } -export interface vehicleInfo { +export interface VehicleInfo { manufacturer: string; model: string; } -export interface vehicleSocModule { +export interface VehicleSocModuleConfig { name?: string; type: string | null; official?: boolean; 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 c9db6e7f23..a3b55345cc 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 @@ -15,8 +15,8 @@ import type { ValueObject, ChargePointConnectedVehicleInfo, Vehicle, - vehicleInfo, - vehicleSocModule, + VehicleInfo, + VehicleSocModuleConfig, ScheduledChargingPlan, ChargePointConnectedVehicleSoc, GraphDataPoint, @@ -491,6 +491,7 @@ export const useMqttStore = defineStore('mqtt', () => { * @param scale flag to scale the value, default is true * @param inverted flag to invert the value, default is false * @param defaultString default string to use, default is '---' + * @param decimalPlaces number of decimal places to use, default is 0 or 2 for scaled values * @returns object */ const getValueObject = computed(() => { @@ -501,6 +502,7 @@ export const useMqttStore = defineStore('mqtt', () => { scale: boolean = true, inverted: boolean = false, defaultString: string = '---', + decimalPlaces: number = 0, ) => { let scaled = false; let scaledValue = value; @@ -534,9 +536,16 @@ export const useMqttStore = defineStore('mqtt', () => { break; } } + let outputDecimalPlaces = 0; + if (scaled) { + outputDecimalPlaces = decimalPlaces > 0 ? decimalPlaces : 2; + } else { + const hasDecimalPlaces = scaledValue !== Math.floor(scaledValue); + outputDecimalPlaces = hasDecimalPlaces ? decimalPlaces : 0; + } textValue = scaledValue.toLocaleString(undefined, { - minimumFractionDigits: scaled ? 2 : 0, - maximumFractionDigits: scaled ? 2 : 0, + minimumFractionDigits: outputDecimalPlaces, + maximumFractionDigits: outputDecimalPlaces, }); } return { @@ -806,16 +815,25 @@ export const useMqttStore = defineStore('mqtt', () => { }; }); - /** - * Get the charge point charging current identified by the charge point id - */ const chargePointChargingCurrent = computed(() => { - return (chargePointId: number) => { - return ( + return (chargePointId: number, returnType: string = 'textValue') => { + const current = (getValue.value( `openWB/chargepoint/${chargePointId}/set/current`, - ) as number) || 0 + ) as number) || 0; + const valueObject = getValueObject.value( + current, + 'A', + '', + true, + false, + '---', + 2, ); + if (Object.hasOwn(valueObject, returnType)) { + return valueObject[returnType as keyof ValueObject]; + } + console.error('returnType not found!', returnType, current); }; }); @@ -941,6 +959,46 @@ export const useMqttStore = defineStore('mqtt', () => { }); }; + /** + * Get boolean value for DC charging enabled / disabled + * @returns boolean + */ + const dcChargingEnabled = computed(() => { + return (getValue.value('openWB/optional/dc_charging') as boolean) || 0; + }); + + /** + * Get or set the charge point connected vehicle instant charging DC power identified by the charge point id + * @param chargePointId charge point id + * @returns number + */ + const chargePointConnectedVehicleInstantDcChargePower = ( + chargePointId: number, + ) => { + return computed({ + get() { + const dcCurrent = + chargePointConnectedVehicleChargeTemplate(chargePointId).value + ?.chargemode?.instant_charging?.dc_current; + if (dcCurrent !== undefined) { + return (dcCurrent * 3 * 230) / 1000; + } else { + return 0; + } + }, + set(newValue: number) { + console.debug('set instant charging power', newValue, chargePointId); + const newPower = (newValue * 1000) / 230 / 3; + return updateTopic( + `openWB/chargepoint/${chargePointId}/set/charge_template`, + newPower, + 'chargemode.instant_charging.dc_current', + true, + ); + }, + }); + }; + /** * Get or set the charge point connected vehicle instant charging phases identified by the charge point id * @param chargePointId charge point id @@ -1052,7 +1110,7 @@ export const useMqttStore = defineStore('mqtt', () => { * @param chargePointId charge point id * @returns object | undefined */ - const chargePointConnectedVehiclePVChargeMinCurrent = ( + const chargePointConnectedVehiclePvChargeMinCurrent = ( chargePointId: number, ) => { return computed({ @@ -1072,12 +1130,44 @@ export const useMqttStore = defineStore('mqtt', () => { }); }; + /** + * Get or set the charge point connected vehicle PV charging DC power identified by the charge point id + * @param chargePointId charge point id + * @returns number + */ + const chargePointConnectedVehiclePvDcChargePower = ( + chargePointId: number, + ) => { + return computed({ + get() { + const dcMinCurrent = + chargePointConnectedVehicleChargeTemplate(chargePointId).value + ?.chargemode?.pv_charging?.dc_min_current; + if (dcMinCurrent !== undefined) { + return (dcMinCurrent * 3 * 230) / 1000; + } else { + return 0; + } + }, + set(newValue: number) { + console.debug('set instant charging power', newValue, chargePointId); + const newPower = (newValue * 1000) / 230 / 3; + return updateTopic( + `openWB/chargepoint/${chargePointId}/set/charge_template`, + newPower, + 'chargemode.pv_charging.dc_min_current', + true, + ); + }, + }); + }; + /** * Get or set the charge point connected vehicle pv min SoC identified by the charge point id * @param chargePointId charge point id * @returns object | undefined */ - const chargePointConnectedVehiclePVChargeMinSoc = (chargePointId: number) => { + const chargePointConnectedVehiclePvChargeMinSoc = (chargePointId: number) => { return computed({ get() { return chargePointConnectedVehicleChargeTemplate(chargePointId).value @@ -1100,7 +1190,7 @@ export const useMqttStore = defineStore('mqtt', () => { * @param chargePointId charge point id * @returns object | undefined */ - const chargePointConnectedVehiclePVChargeMinSocCurrent = ( + const chargePointConnectedVehiclePvChargeMinSocCurrent = ( chargePointId: number, ) => { return computed({ @@ -1256,7 +1346,7 @@ export const useMqttStore = defineStore('mqtt', () => { * @param chargePointId charge point id * @returns object | undefined */ - const chargePointConnectedVehiclePVChargeFeedInLimit = ( + const chargePointConnectedVehiclePvChargeFeedInLimit = ( chargePointId: number, ) => { return computed({ @@ -1301,6 +1391,38 @@ export const useMqttStore = defineStore('mqtt', () => { }); }; + /** + * Get or set the charge point connected vehicle eco charging power identified by the charge point id + * @param chargePointId charge point id + * @returns number + */ + const chargePointConnectedVehicleEcoChargeDcPower = ( + chargePointId: number, + ) => { + return computed({ + get() { + const dcCurrent = + chargePointConnectedVehicleChargeTemplate(chargePointId).value + ?.chargemode?.eco_charging?.dc_current; + if (dcCurrent !== undefined) { + return (dcCurrent * 3 * 230) / 1000; + } else { + return 0; + } + }, + set(newValue: number) { + console.debug('set instant charging power', newValue, chargePointId); + const newPower = (newValue * 1000) / 230 / 3; + return updateTopic( + `openWB/chargepoint/${chargePointId}/set/charge_template`, + newPower, + 'chargemode.eco_charging.dc_current', + true, + ); + }, + }); + }; + /** * Get or set the charge point connected vehicle eco charging phases identified by the charge point id * @param chargePointId charge point id @@ -1638,7 +1760,7 @@ export const useMqttStore = defineStore('mqtt', () => { /** * Get the battery power identified by the battery point id - * @param batteryId battery point id + * @param batteryId battery ID * @param returnType type of return value, 'textValue', 'absoluteTextValue', 'value', 'scaledValue', 'scaledUnit' or 'object' * @returns string | number | ValueObject */ @@ -1668,7 +1790,7 @@ export const useMqttStore = defineStore('mqtt', () => { /** * Get the battery daily imported energy of a given battery id - * @param batteryId charge point id + * @param batteryId battery ID * @param returnType type of return value, 'textValue', 'value', 'scaledValue', 'scaledUnit' or 'object' * @returns string | number | ValueObject */ @@ -1693,7 +1815,7 @@ export const useMqttStore = defineStore('mqtt', () => { /** * Get the battery daily exported energy of a given battery id - * @param batteryId charge point id + * @param batteryId battery ID * @param returnType type of return value, 'textValue', 'value', 'scaledValue', 'scaledUnit' or 'object' * @returns string | number | ValueObject */ @@ -1855,21 +1977,21 @@ export const useMqttStore = defineStore('mqtt', () => { */ const vehicleInfo = computed(() => { return (vehicleId: number) => { - return getValue.value(`openWB/vehicle/${vehicleId}/info`) as vehicleInfo; + return getValue.value(`openWB/vehicle/${vehicleId}/info`) as VehicleInfo; }; }); /** - * Get vehicle SoC module name identified by the vehicle id + * Get vehicle SoC module configuration identified by the vehicle id * @param vehicleId vehicle id - * @returns string + * @returns vehicleSocModule */ - const vehicleSocModuleName = computed(() => { + const vehicleSocModule = computed(() => { return (vehicleId: number) => { const socModule = getValue.value( `openWB/vehicle/${vehicleId}/soc_module/config`, - ) as vehicleSocModule; - return socModule?.name; + ) as VehicleSocModuleConfig; + return socModule; }; }); @@ -1886,6 +2008,52 @@ export const useMqttStore = defineStore('mqtt', () => { }; }); + /** + * Get or set the manual SoC by vehicle id + * @param vehicleId vehicle id + * @param chargePointId charge point id + * @returns number | undefined + */ + const vehicleSocManualValue = ( + vehicleId: number | undefined, + chargePointId?: number, + ) => { + return computed({ + get() { + const topic = `openWB/vehicle/${vehicleId}/soc_module/calculated_soc_state`; + const socState = getValue.value(topic) as + | CalculatedSocState + | undefined; + return socState?.manual_soc ?? socState?.soc_start ?? 0; + }, + set(newValue: number) { + doPublish( + `openWB/set/vehicle/${vehicleId}/soc_module/calculated_soc_state/manual_soc`, + newValue, + ); + // Also update the charge point connected vehicle soc to prevent long delay in display update + if (chargePointId !== undefined) { + const cpTopic = `openWB/chargepoint/${chargePointId}/get/connected_vehicle/soc`; + const cpSoc = getValue.value(cpTopic) as { soc?: number }; + if (cpSoc && cpSoc.soc !== undefined) { + updateTopic(cpTopic, newValue, 'soc', true); + } + } + }, + }); + }; + + /** + * trigger a force SOC update for the vehicle by vehicle id + */ + const vehicleForceSocUpdate = (vehicleId: number) => { + if (vehicleId !== undefined) { + const topic = `openWB/set/vehicle/${vehicleId}/get/force_soc_update`; + console.log(topic); + sendTopicToBroker(topic, 1); + } + }; + /** * Get vehicle state identified by the vehicle id * @param vehicleId vehicle id @@ -2576,24 +2744,28 @@ export const useMqttStore = defineStore('mqtt', () => { chargePointStateMessage, chargePointFaultState, chargePointFaultMessage, + dcChargingEnabled, chargePointConnectedVehicleInfo, chargePointConnectedVehicleForceSocUpdate, chargePointConnectedVehicleChargeMode, chargePointConnectedVehicleInstantChargeCurrent, + chargePointConnectedVehicleInstantDcChargePower, chargePointConnectedVehicleInstantChargePhases, chargePointConnectedVehicleInstantChargeLimit, chargePointConnectedVehicleInstantChargeLimitSoC, chargePointConnectedVehicleInstantChargeLimitEnergy, - chargePointConnectedVehiclePVChargeMinCurrent, + chargePointConnectedVehiclePvChargeMinCurrent, chargePointConnectedVehiclePvChargePhases, chargePointConnectedVehiclePvChargeLimit, chargePointConnectedVehiclePvChargeLimitSoC, chargePointConnectedVehiclePvChargeLimitEnergy, - chargePointConnectedVehiclePVChargeMinSoc, - chargePointConnectedVehiclePVChargeMinSocCurrent, + chargePointConnectedVehiclePvChargeMinSoc, + chargePointConnectedVehiclePvChargeMinSocCurrent, + chargePointConnectedVehiclePvDcChargePower, chargePointConnectedVehiclePvChargePhasesMinSoc, - chargePointConnectedVehiclePVChargeFeedInLimit, + chargePointConnectedVehiclePvChargeFeedInLimit, chargePointConnectedVehicleEcoChargeCurrent, + chargePointConnectedVehicleEcoChargeDcPower, chargePointConnectedVehicleEcoChargePhases, chargePointConnectedVehicleEcoChargeLimit, chargePointConnectedVehicleEcoChargeLimitSoC, @@ -2607,8 +2779,10 @@ export const useMqttStore = defineStore('mqtt', () => { chargePointConnectedVehicleConfig, vehicleInfo, vehicleConnectionState, - vehicleSocModuleName, + vehicleSocModule, vehicleSocValue, + vehicleSocManualValue, + vehicleForceSocUpdate, chargePointConnectedVehicleSoc, vehicleActivePlan, vehicleChargeTarget, From f1aaf716d456876127f63293a9b5f5fdd18cd47f Mon Sep 17 00:00:00 2001 From: benderl Date: Tue, 17 Jun 2025 12:15:04 +0200 Subject: [PATCH 2/2] Update packages/modules/web_themes/koala/source/src/components/ChargePointInformation.vue --- .../koala/source/src/components/ChargePointInformation.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/web_themes/koala/source/src/components/ChargePointInformation.vue b/packages/modules/web_themes/koala/source/src/components/ChargePointInformation.vue index 07c272d32c..a969a3a58e 100644 --- a/packages/modules/web_themes/koala/source/src/components/ChargePointInformation.vue +++ b/packages/modules/web_themes/koala/source/src/components/ChargePointInformation.vue @@ -79,7 +79,7 @@ - +