|
3 | 3 | v-tooltip="`Battery remaining: ${remainingDisplayValue < 0 ? 'No Data' : remainingDisplayValue + '%'}`" |
4 | 4 | class="flex items-center w-[95px] h-12 text-white justify-center" |
5 | 5 | > |
6 | | - <div class="relative w-[1.5rem] battery-icon"> |
| 6 | + <div v-if="remainingDisplayValue >= 0" class="relative w-[1.5rem] battery-icon"> |
7 | 7 | <i class="mdi mdi-battery-outline"></i> |
8 | 8 |
|
9 | 9 | <i |
|
12 | 12 | backgroundImage: `linear-gradient(to top, white ${remainingDisplayValue}%, transparent ${remainingDisplayValue}%)`, |
13 | 13 | }" |
14 | 14 | /> |
| 15 | + <span |
| 16 | + v-if="remainingDisplayValue < 0" |
| 17 | + class="absolute text-sm text-white -bottom-[3px] left-[4px] mdi mdi-circle" |
| 18 | + ></span> |
15 | 19 | <span |
16 | 20 | v-if="remainingDisplayValue < 0" |
17 | 21 | class="absolute text-sm text-yellow-400 -bottom-[3px] left-[4px] mdi mdi-alert-circle" |
18 | 22 | ></span> |
19 | 23 | </div> |
20 | 24 |
|
| 25 | + <div v-else class="relative flex flex-col justify-center items-center h-full w-[1.5rem] battery-icon"> |
| 26 | + <i |
| 27 | + class="absolute mdi mdi-battery" |
| 28 | + :style="{ color: miniWidget.options.useVoltageToColor ? currentBatteryColor : 'transparent' }" |
| 29 | + ></i> |
| 30 | + <i |
| 31 | + v-if="currentBatteryLevel === 'critical'" |
| 32 | + class="absolute mdi mdi-battery animate-ping" |
| 33 | + :style="{ color: currentBatteryColor }" |
| 34 | + ></i> |
| 35 | + <i class="absolute mdi mdi-battery-outline text-[#CCCCCC88]"></i> |
| 36 | + |
| 37 | + <span v-if="remainingDisplayValue < 0" class="absolute text-sm text-white bottom-0 left-0 mdi mdi-circle"></span> |
| 38 | + <span |
| 39 | + v-if="remainingDisplayValue < 0" |
| 40 | + class="absolute text-sm text-yellow-400 bottom-0 left-0 mdi mdi-alert-circle" |
| 41 | + ></span> |
| 42 | + </div> |
| 43 | + |
21 | 44 | <div class="relative right-0 flex flex-col w-[4rem] select-none text-sm font-semibold leading-4 text-end -mr-2"> |
22 | 45 | <div class="w-full"> |
23 | 46 | <div class="flex justify-end gap-x-1 items-center"> |
|
42 | 65 | </div> |
43 | 66 | </div> |
44 | 67 | <v-dialog v-model="widgetStore.miniWidgetManagerVars(miniWidget.hash).configMenuOpen" width="auto"> |
45 | | - <v-card class="pa-4 text-white w-[20rem]" style="border-radius: 15px" :style="interfaceStore.globalGlassMenuStyles"> |
| 68 | + <v-card class="pa-4 text-white w-[400px]" style="border-radius: 15px" :style="interfaceStore.globalGlassMenuStyles"> |
46 | 69 | <v-card-title class="text-center">Battery Indicator Config</v-card-title> |
47 | 70 | <v-card-text class="flex flex-col gap-y-4"> |
48 | | - <v-checkbox v-model="miniWidget.options.showCurrent" label="Show Current" hide-details /> |
| 71 | + <div class="absolute top-2 right-2 z-10"> |
| 72 | + <v-btn |
| 73 | + icon |
| 74 | + size="30" |
| 75 | + variant="text" |
| 76 | + class="text-white text-[22px]" |
| 77 | + aria-label="Close" |
| 78 | + @click="widgetStore.miniWidgetManagerVars(miniWidget.hash).configMenuOpen = false" |
| 79 | + > |
| 80 | + <i class="mdi mdi-close"></i> |
| 81 | + </v-btn> |
| 82 | + </div> |
| 83 | + <v-checkbox v-model="miniWidget.options.showCurrent" label="Show Current" hide-details class="-mb-5" /> |
49 | 84 | <v-checkbox v-model="miniWidget.options.showPower" label="Show Power" hide-details /> |
50 | 85 | <v-text-field |
51 | 86 | v-model.number="userSetToggleInterval" |
|
55 | 90 | step="100" |
56 | 91 | density="compact" |
57 | 92 | variant="outlined" |
| 93 | + hide-details |
58 | 94 | :disabled="!miniWidget.options.showCurrent || !miniWidget.options.showPower" |
59 | 95 | /> |
| 96 | + |
| 97 | + <v-divider class="mt-2" /> |
| 98 | + <div class="flex justify-between items-center -mt-1"> |
| 99 | + <v-checkbox |
| 100 | + v-model="miniWidget.options.useVoltageToColor" |
| 101 | + label="Use voltage to color scheme" |
| 102 | + hide-details |
| 103 | + class="mr-1" |
| 104 | + /> |
| 105 | + <v-tooltip |
| 106 | + location="top" |
| 107 | + text="Configure these voltage levels according to your battery pack. Defaults are based on Blue Robotics' 4S Li-ion battery pack." |
| 108 | + > |
| 109 | + <template #activator="{ props: infoProps }"> |
| 110 | + <v-icon |
| 111 | + v-bind="infoProps" |
| 112 | + icon="mdi-information-outline" |
| 113 | + class="ml-1 text-yellow-300 cursor-pointer" |
| 114 | + size="18" |
| 115 | + /> |
| 116 | + </template> |
| 117 | + </v-tooltip> |
| 118 | + </div> |
| 119 | + |
| 120 | + <div class="flex items-center gap-x-2"> |
| 121 | + <v-text-field |
| 122 | + v-model.number="userBatteryThresholds.critical" |
| 123 | + label="Critical voltage (V)" |
| 124 | + type="number" |
| 125 | + density="compact" |
| 126 | + variant="outlined" |
| 127 | + hide-details |
| 128 | + /> |
| 129 | + <input |
| 130 | + v-model="voltageToColorScheme.critical" |
| 131 | + type="color" |
| 132 | + theme="dark" |
| 133 | + class="w-8 h-8 rounded border border-white/30 bg-transparent cursor-pointer" |
| 134 | + /> |
| 135 | + </div> |
| 136 | + |
| 137 | + <div class="flex items-center gap-x-2"> |
| 138 | + <v-text-field |
| 139 | + v-model.number="userBatteryThresholds.low" |
| 140 | + label="Low voltage (V)" |
| 141 | + type="number" |
| 142 | + density="compact" |
| 143 | + variant="outlined" |
| 144 | + hide-details |
| 145 | + /> |
| 146 | + <input |
| 147 | + v-model="voltageToColorScheme.low" |
| 148 | + type="color" |
| 149 | + theme="dark" |
| 150 | + class="w-8 h-8 rounded border border-white/30 bg-transparent cursor-pointer" |
| 151 | + /> |
| 152 | + </div> |
| 153 | + |
| 154 | + <div class="flex items-center gap-x-2"> |
| 155 | + <v-text-field |
| 156 | + v-model.number="userBatteryThresholds.medium" |
| 157 | + label="Medium voltage (V)" |
| 158 | + type="number" |
| 159 | + density="compact" |
| 160 | + variant="outlined" |
| 161 | + hide-details |
| 162 | + /> |
| 163 | + <input |
| 164 | + v-model="voltageToColorScheme.medium" |
| 165 | + type="color" |
| 166 | + theme="dark" |
| 167 | + class="w-8 h-8 rounded border border-white/30 bg-transparent cursor-pointer" |
| 168 | + /> |
| 169 | + </div> |
| 170 | + |
| 171 | + <div class="flex items-center gap-x-2"> |
| 172 | + <v-text-field |
| 173 | + v-model.number="userBatteryThresholds.high" |
| 174 | + label="High voltage (V)" |
| 175 | + type="number" |
| 176 | + density="compact" |
| 177 | + variant="outlined" |
| 178 | + hide-details |
| 179 | + /> |
| 180 | + <input |
| 181 | + v-model="voltageToColorScheme.high" |
| 182 | + type="color" |
| 183 | + theme="dark" |
| 184 | + class="w-8 h-8 rounded border border-white/30 bg-transparent cursor-pointer" |
| 185 | + /> |
| 186 | + </div> |
| 187 | + |
60 | 188 | <p class="text-red-500 text-center text-sm w-[full]">{{ errorMessage }}</p> |
61 | 189 | </v-card-text> |
62 | 190 | </v-card> |
63 | 191 | </v-dialog> |
64 | 192 | </template> |
65 | 193 |
|
66 | 194 | <script setup lang="ts"> |
| 195 | +import { useDebounce } from '@vueuse/core' |
67 | 196 | import { computed, onBeforeMount, onUnmounted, ref, toRefs, watch } from 'vue' |
68 | 197 |
|
| 198 | +import { defaultBatteryLevelColorScheme, defaultBatteryLevelThresholds } from '@/assets/defaults' |
| 199 | +import { useBlueOsStorage } from '@/composables/settingsSyncer' |
69 | 200 | import { datalogger, DatalogVariable } from '@/libs/sensors-logging' |
70 | 201 | import { useAppInterfaceStore } from '@/stores/appInterface' |
71 | 202 | import { useMainVehicleStore } from '@/stores/mainVehicle' |
72 | 203 | import { useWidgetManagerStore } from '@/stores/widgetManager' |
| 204 | +import { BatteryLevel, BatteryLevelThresholds } from '@/types/general' |
73 | 205 | import type { MiniWidget } from '@/types/widgets' |
74 | 206 |
|
75 | 207 | /** |
@@ -100,6 +232,44 @@ const errorMessage = ref('') |
100 | 232 | const errorMessageTimeout = ref<ReturnType<typeof setTimeout> | undefined>(undefined) |
101 | 233 | const userSetToggleInterval = ref(miniWidget.value.options.toggleInterval ?? defaultOptions.toggleInterval) |
102 | 234 |
|
| 235 | +miniWidget.value.options.useVoltageToColor ??= false |
| 236 | +
|
| 237 | +const voltageToColorScheme = useBlueOsStorage<Record<BatteryLevel, string>>( |
| 238 | + 'cockpit-user-voltage-color-scheme', |
| 239 | + defaultBatteryLevelColorScheme |
| 240 | +) |
| 241 | +
|
| 242 | +const userBatteryThresholds = useBlueOsStorage<BatteryLevelThresholds>( |
| 243 | + 'cockpit-user-battery-levels', |
| 244 | + defaultBatteryLevelThresholds |
| 245 | +) |
| 246 | +
|
| 247 | +const batteryThresholds = computed<BatteryLevelThresholds>(() => userBatteryThresholds.value) |
| 248 | +
|
| 249 | +const rawVoltage = computed<number | null>(() => store?.powerSupply?.voltage ?? null) |
| 250 | +// Keeps a stable voltage reading for 4 seconds to avoid rapid battery level changes |
| 251 | +const debouncedVoltage = useDebounce(rawVoltage, 4000) |
| 252 | +
|
| 253 | +const currentBatteryLevel = computed<BatteryLevel>(() => { |
| 254 | + const voltage = debouncedVoltage.value |
| 255 | +
|
| 256 | + if (voltage == null) return 'unknown' |
| 257 | +
|
| 258 | + const { critical, low, medium, high } = batteryThresholds.value |
| 259 | +
|
| 260 | + if (voltage >= high) return 'high' |
| 261 | + if (voltage >= medium) return 'medium' |
| 262 | + if (voltage >= low) return 'low' |
| 263 | + if (voltage >= critical) return 'critical' |
| 264 | + if (voltage < critical) return 'critical' |
| 265 | + return 'unknown' |
| 266 | +}) |
| 267 | +
|
| 268 | +const currentBatteryColor = computed(() => { |
| 269 | + const level = currentBatteryLevel.value |
| 270 | + return voltageToColorScheme.value[level] ?? 'transparent' |
| 271 | +}) |
| 272 | +
|
103 | 273 | const voltageDisplayValue = computed(() => { |
104 | 274 | if (store?.powerSupply?.voltage === undefined) return '--' |
105 | 275 | return Math.abs(store.powerSupply.voltage) >= 100 |
|
0 commit comments