Skip to content

Commit

Permalink
battery-indicator: Add instantaneous and consumed power
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaellehmkuhl committed Sep 27, 2024
1 parent c397779 commit f37bb6b
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 15 deletions.
127 changes: 112 additions & 15 deletions src/components/mini-widgets/BatteryIndicator.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,83 @@
<template>
<div
v-tooltip="'Your vehicle does not provide state-of-charge. Displaying voltage and current instead.'"
class="flex items-center w-[5.5rem] h-12 text-white justify-center"
>
<div v-tooltip="'Battery information'" class="flex items-center w-[5.5rem] h-12 text-white justify-center">
<span class="relative w-[1.5rem] mdi battery-icon" :class="[batteryIconClass]">
<span class="absolute text-sm text-yellow-400 -bottom-[2px] -right-[7px] mdi mdi-alert-circle"></span>
</span>
<div class="flex flex-col w-[4rem] select-none text-sm font-semibold leading-4 text-end">
<div class="w-full">
<span class="font-mono">{{ voltageDisplayValue }}</span>
<span> V</span>
</div>
<div class="w-full">
<span class="font-mono">{{ currentDisplayValue }}</span>
<span> A</span>
</div>
<template v-if="showVoltageAndCurrent">
<div class="w-full">
<span class="font-mono">{{ voltageDisplayValue }}</span>
<span> V</span>
</div>
<div class="w-full">
<span class="font-mono">{{ currentDisplayValue }}</span>
<span> A</span>
</div>
</template>
<template v-else>
<div class="w-full">
<span class="font-mono">{{ instantaneousWattsDisplayValue }}</span>
<span> W</span>
</div>
<div class="w-full">
<span class="font-mono">{{ totalConsumedWattsDisplayValue }}</span>
<span> Wh</span>
</div>
</template>
</div>
</div>
<v-dialog v-model="widgetStore.miniWidgetManagerVars(miniWidget.hash).configMenuOpen" width="auto">
<v-card class="pa-4 text-white" style="border-radius: 15px" :style="interfaceStore.globalGlassMenuStyles">
<v-card-title class="text-center">Battery Indicator Config</v-card-title>
<v-card-text class="flex flex-col gap-y-4">
<v-checkbox v-model="miniWidget.options.showVoltageAndCurrent" label="Show Voltage and Current" hide-details />
<v-checkbox
v-model="miniWidget.options.showPowerAndConsumption"
label="Show Power and Consumption"
hide-details
/>
<v-text-field
v-if="miniWidget.options.showVoltageAndCurrent && miniWidget.options.showPowerAndConsumption"
v-model.number="miniWidget.options.toggleInterval"
label="Toggle Interval (ms)"
type="number"
min="1000"
step="100"
density="compact"
variant="outlined"
hide-details
/>
</v-card-text>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { computed, onBeforeMount, onUnmounted, ref, toRefs, watch } from 'vue'
import { datalogger, DatalogVariable } from '@/libs/sensors-logging'
import { useAppInterfaceStore } from '@/stores/appInterface'
import { useMainVehicleStore } from '@/stores/mainVehicle'
import { useWidgetManagerStore } from '@/stores/widgetManager'
import type { MiniWidget } from '@/types/widgets'
/**
* Props for the BatteryIndicator component
*/
const props = defineProps<{
/**
* Configuration of the widget
*/
miniWidget: MiniWidget
}>()
const miniWidget = toRefs(props).miniWidget
const store = useMainVehicleStore()
const widgetStore = useWidgetManagerStore()
const interfaceStore = useAppInterfaceStore()
const showVoltageAndCurrent = ref(true)
const toggleIntervaler = ref<ReturnType<typeof setInterval> | undefined>(undefined)
const voltageDisplayValue = computed(() => {
if (store?.powerSupply?.voltage === undefined) return NaN
Expand All @@ -40,11 +93,55 @@ const currentDisplayValue = computed(() => {
: store.powerSupply.current.toFixed(1)
})
// TODO: With a proper percentage model in hand, we should use different icons for each battery level.
// TODO: We can also allow the user to set it's 100% and 0% voltages and use a linear fit accordingly
const instantaneousWattsDisplayValue = computed(() => {
return store.instantaneousWatts ? store.instantaneousWatts.toFixed(1) : NaN
})
const totalConsumedWattsDisplayValue = computed(() => {
return store.totalConsumedWatts.toFixed(1)
})
const batteryIconClass = computed(() => {
return 'mdi-battery'
})
const setupToggleInterval = (): void => {
if (toggleIntervaler.value) {
clearInterval(toggleIntervaler.value)
}
if (miniWidget.value.options.showVoltageAndCurrent && miniWidget.value.options.showPowerAndConsumption) {
toggleIntervaler.value = setInterval(() => {
showVoltageAndCurrent.value = !showVoltageAndCurrent.value
}, miniWidget.value.options.toggleInterval)
} else {
showVoltageAndCurrent.value = miniWidget.value.options.showVoltageAndCurrent
}
}
watch(() => miniWidget.value.options, setupToggleInterval, { deep: true })
onBeforeMount(() => {
// Set default options if not already set
const defaultOptions = {
showVoltageAndCurrent: true,
showPowerAndConsumption: true,
toggleInterval: 3000,
}
miniWidget.value.options = Object.assign({}, defaultOptions, miniWidget.value.options)
setupToggleInterval()
// Register new variables for logging
datalogger.registerUsage('Instantaneous Watts' as DatalogVariable)
datalogger.registerUsage('Total Consumed Wh' as DatalogVariable)
})
onUnmounted(() => {
if (toggleIntervaler.value) {
clearInterval(toggleIntervaler.value)
}
})
</script>

<style>
Expand Down
4 changes: 4 additions & 0 deletions src/libs/sensors-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export enum DatalogVariable {
missionName = 'Mission name',
time = 'Time',
date = 'Date',
instantaneousPower = 'Instantaneous power',
totalConsumedWh = 'Consumed power',
}

const logDateFormat = 'LLL dd, yyyy'
Expand Down Expand Up @@ -275,6 +277,8 @@ class DataLogger {
[DatalogVariable.missionName]: { value: missionStore.missionName || 'Cockpit', hideLabel: true, ...timeNowObj },
[DatalogVariable.time]: { value: format(timeNow, 'HH:mm:ss O'), hideLabel: true, ...timeNowObj },
[DatalogVariable.date]: { value: format(timeNow, 'LLL dd, yyyy'), hideLabel: true, ...timeNowObj },
[DatalogVariable.instantaneousPower]: { value: `${vehicleStore.instantaneousWatts?.toFixed(1)} W` || 'Unknown', ...timeNowObj },
[DatalogVariable.totalConsumedWh]: { value: `${vehicleStore.totalConsumedWatts.toFixed(1)} Wh`, ...timeNowObj },
}

/* eslint-enable vue/max-len, prettier/prettier, max-len */
Expand Down
16 changes: 16 additions & 0 deletions src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
const attitude: Attitude = reactive({} as Attitude)
const coordinates: Coordinates = reactive({} as Coordinates)
const powerSupply: PowerSupply = reactive({} as PowerSupply)
const instantaneousWatts = ref<number | undefined>(undefined)
const totalConsumedWatts = ref<number>(0)
const lastUpdateTime = ref<number>(Date.now())
const velocity: Velocity = reactive({} as Velocity)
const mainVehicle = ref<ArduPilot | undefined>(undefined)
const isArmed = ref<boolean | undefined>(undefined)
Expand Down Expand Up @@ -395,6 +398,17 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
})
mainVehicle.value.onPowerSupply.add((newPowerSupply: PowerSupply) => {
Object.assign(powerSupply, newPowerSupply)

const currentTime = Date.now()
const timeDiff = (currentTime - lastUpdateTime.value) / 1000 // Convert to seconds

instantaneousWatts.value =
powerSupply.voltage && powerSupply.current ? powerSupply.voltage * powerSupply.current : undefined
totalConsumedWatts.value += (instantaneousWatts.value || 0 * timeDiff) / 3600

if (instantaneousWatts.value !== undefined) {
lastUpdateTime.value = currentTime
}
})
mainVehicle.value.onStatusText.add((newStatusText: StatusText) => {
Object.assign(statusText, newStatusText)
Expand Down Expand Up @@ -575,6 +589,8 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
coordinates,
velocity,
powerSupply,
instantaneousWatts,
totalConsumedWatts,
statusText,
statusGPS,
mode,
Expand Down

0 comments on commit f37bb6b

Please sign in to comment.