Skip to content

Commit 8b178d4

Browse files
committed
mini-widgets: battery-indicator: add color based voltage level indicator
Signed-off-by: Arturo Manzoli <arturomanzoli@gmail.com>
1 parent e5868db commit 8b178d4

3 files changed

Lines changed: 194 additions & 3 deletions

File tree

src/assets/defaults.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { MavType } from '@/libs/connection/m2r/messages/mavlink2rest-enum'
22
import { OverlayGrid } from '@/libs/sensors-logging'
33
import { DistanceDisplayUnit } from '@/libs/units'
4+
import { BatteryLevel, BatteryLevelThresholds } from '@/types/general'
45
import { JoystickCalibration } from '@/types/joystick'
56
import {
67
type MiniWidgetProfile,
@@ -926,3 +927,19 @@ export const defaultVehicleBatteryPack: Record<string, number> = {
926927
MAV_TYPE_SUBMARINE: 236 * 1,
927928
MAV_TYPE_GENERIC: 236 * 2,
928929
}
930+
931+
export const defaultBatteryLevelColorScheme: Record<BatteryLevel, string> = {
932+
high: '#2b7a40',
933+
medium: '#c49a1a',
934+
low: '#c7261e',
935+
critical: '#7e1b1b',
936+
unknown: '#aaaaaa99',
937+
}
938+
939+
// Values based on Blue Robotics standard 4s Li-Ion battery pack voltages
940+
export const defaultBatteryLevelThresholds: BatteryLevelThresholds = {
941+
high: 15.2,
942+
medium: 14.0,
943+
low: 12.8,
944+
critical: 12.0,
945+
}

src/components/mini-widgets/BatteryIndicator.vue

Lines changed: 173 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
v-tooltip="`Battery remaining: ${remainingDisplayValue < 0 ? 'No Data' : remainingDisplayValue + '%'}`"
44
class="flex items-center w-[95px] h-12 text-white justify-center"
55
>
6-
<div class="relative w-[1.5rem] battery-icon">
6+
<div v-if="remainingDisplayValue >= 0" class="relative w-[1.5rem] battery-icon">
77
<i class="mdi mdi-battery-outline"></i>
88

99
<i
@@ -12,12 +12,35 @@
1212
backgroundImage: `linear-gradient(to top, white ${remainingDisplayValue}%, transparent ${remainingDisplayValue}%)`,
1313
}"
1414
/>
15+
<span
16+
v-if="remainingDisplayValue < 0"
17+
class="absolute text-sm text-white -bottom-[3px] left-[4px] mdi mdi-circle"
18+
></span>
1519
<span
1620
v-if="remainingDisplayValue < 0"
1721
class="absolute text-sm text-yellow-400 -bottom-[3px] left-[4px] mdi mdi-alert-circle"
1822
></span>
1923
</div>
2024

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+
2144
<div class="relative right-0 flex flex-col w-[4rem] select-none text-sm font-semibold leading-4 text-end -mr-2">
2245
<div class="w-full">
2346
<div class="flex justify-end gap-x-1 items-center">
@@ -42,10 +65,22 @@
4265
</div>
4366
</div>
4467
<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">
4669
<v-card-title class="text-center">Battery Indicator Config</v-card-title>
4770
<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" />
4984
<v-checkbox v-model="miniWidget.options.showPower" label="Show Power" hide-details />
5085
<v-text-field
5186
v-model.number="userSetToggleInterval"
@@ -55,21 +90,118 @@
5590
step="100"
5691
density="compact"
5792
variant="outlined"
93+
hide-details
5894
:disabled="!miniWidget.options.showCurrent || !miniWidget.options.showPower"
5995
/>
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+
60188
<p class="text-red-500 text-center text-sm w-[full]">{{ errorMessage }}</p>
61189
</v-card-text>
62190
</v-card>
63191
</v-dialog>
64192
</template>
65193

66194
<script setup lang="ts">
195+
import { useDebounce } from '@vueuse/core'
67196
import { computed, onBeforeMount, onUnmounted, ref, toRefs, watch } from 'vue'
68197
198+
import { defaultBatteryLevelColorScheme, defaultBatteryLevelThresholds } from '@/assets/defaults'
199+
import { useBlueOsStorage } from '@/composables/settingsSyncer'
69200
import { datalogger, DatalogVariable } from '@/libs/sensors-logging'
70201
import { useAppInterfaceStore } from '@/stores/appInterface'
71202
import { useMainVehicleStore } from '@/stores/mainVehicle'
72203
import { useWidgetManagerStore } from '@/stores/widgetManager'
204+
import { BatteryLevel, BatteryLevelThresholds } from '@/types/general'
73205
import type { MiniWidget } from '@/types/widgets'
74206
75207
/**
@@ -100,6 +232,44 @@ const errorMessage = ref('')
100232
const errorMessageTimeout = ref<ReturnType<typeof setTimeout> | undefined>(undefined)
101233
const userSetToggleInterval = ref(miniWidget.value.options.toggleInterval ?? defaultOptions.toggleInterval)
102234
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+
103273
const voltageDisplayValue = computed(() => {
104274
if (store?.powerSupply?.voltage === undefined) return '--'
105275
return Math.abs(store.powerSupply.voltage) >= 100

src/types/general.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,7 @@ export interface SettingItem {
103103
*/
104104
changed?: boolean
105105
}
106+
107+
export type BatteryLevel = 'critical' | 'low' | 'medium' | 'high' | 'unknown'
108+
109+
export type BatteryLevelThresholds = { high: number; medium: number; low: number; critical: number }

0 commit comments

Comments
 (0)