Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GenericIndicator mini-widget to display generic data #411

Merged
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@mdi/font": "^7.0.96",
"@mdi/js": "^7.2.96",
"@vue-leaflet/vue-leaflet": "^0.6.1",
"@vueuse/core": "9.8.1",
"@vueuse/math": "^10.1.2",
Expand All @@ -43,6 +44,7 @@
"vue": "^3.3.4",
"vue-draggable-plus": "^0.2.0-beta.2",
"vue-router": "^4.0.14",
"vue-virtual-scroller": "^2.0.0-beta.8",
"vuetify": "^3.1.13",
"webfontloader": "^1.0.0",
"webrtc-adapter": "^8.2.0"
Expand Down
10 changes: 10 additions & 0 deletions src/components/MiniWidgetContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
<template v-if="item.component === MiniWidgetType.DepthIndicator">
<DepthIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.GenericIndicator">
<GenericIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.JoystickCommIndicator">
<JoystickCommIndicator :options="item.options" />
</template>
Expand Down Expand Up @@ -77,6 +80,9 @@
<template v-if="item.component === MiniWidgetType.DepthIndicator">
<DepthIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.GenericIndicator">
<GenericIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.JoystickCommIndicator">
<JoystickCommIndicator :options="item.options" />
</template>
Expand Down Expand Up @@ -133,6 +139,9 @@
<template v-if="item.component === MiniWidgetType.DepthIndicator">
<DepthIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.GenericIndicator">
<GenericIndicator :options="item.options" />
</template>
<template v-if="item.component === MiniWidgetType.JoystickCommIndicator">
<JoystickCommIndicator :options="item.options" />
</template>
Expand Down Expand Up @@ -168,6 +177,7 @@ import ArmerButton from './mini-widgets/ArmerButton.vue'
import BaseCommIndicator from './mini-widgets/BaseCommIndicator.vue'
import BatteryIndicator from './mini-widgets/BatteryIndicator.vue'
import DepthIndicator from './mini-widgets/DepthIndicator.vue'
import GenericIndicator from './mini-widgets/GenericIndicator.vue'
import JoystickCommIndicator from './mini-widgets/JoystickCommIndicator.vue'
import MiniVideoRecorder from './mini-widgets/MiniVideoRecorder.vue'
import ModeSelector from './mini-widgets/ModeSelector.vue'
Expand Down
128 changes: 128 additions & 0 deletions src/components/mini-widgets/GenericIndicator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<template>
<div
class="flex items-center w-[6.25rem] h-12 py-1 text-white justify-center cursor-pointer hover:bg-slate-100/20 transition-all"
@click="showConfigurationMenu = !showConfigurationMenu"
>
<span class="relative w-[2rem] mdi icon-symbol" :class="[options.iconName]"></span>
<div class="flex flex-col items-start justify-center ml-1 min-w-[4rem] max-w-[6rem] select-none">
<span class="text-xl font-semibold leading-6 w-fit">{{ parsedState }} {{ options.variableUnit }}</span>
<span class="w-full text-sm font-semibold leading-4 whitespace-nowrap">{{ options.variableName }}</span>
</div>
</div>
<Dialog v-model:show="showConfigurationMenu" class="w-72">
<div class="w-full h-full">
<div class="flex flex-col items-center justify-around">
<div class="flex items-center justify-between w-full my-1">
<span class="mr-1 text-slate-100">Name</span>
<div class="w-40">
<Dropdown v-model="options.variableName" :options="Object.keys(store.genericVariables)" />
</div>
</div>
<div class="flex items-center justify-between w-full my-1">
<span class="mr-1 text-slate-100">Unit</span>
<input v-model="options.variableUnit" class="w-40 px-2 py-1 rounded-md bg-slate-200" />
</div>
<div class="flex items-center justify-between w-full my-1">
<span class="mr-1 text-slate-100">Multiplier</span>
<input v-model="options.variableMultiplier" class="w-40 px-2 py-1 rounded-md bg-slate-200" />
</div>
<div class="flex items-center justify-between w-full my-1">
<span class="mr-1 text-slate-100">Icon</span>
<div class="relative w-40">
<input v-model="options.iconName" class="w-full py-1 pl-2 pr-8 rounded-md bg-slate-200" />
<span
class="absolute right-0.5 m-1 text-2xl -translate-y-1 cursor-pointer text-slate-500 mdi"
:class="[options.iconName]"
/>
</div>
</div>
<div class="flex items-center justify-center w-full mt-2">
<input
v-model="iconSearchString"
class="w-40 px-2 py-1 rounded-md bg-slate-200"
placeholder="Search icons..."
/>
</div>
<RecycleScroller
v-if="iconSearchString === ''"
v-slot="{ item }"
class="w-full h-40 mt-3"
:items="iconsNames.filter((name) => name.includes(iconSearchString))"
:item-size="40"
:grid-items="6"
>
<span class="m-1 text-white cursor-pointer mdi icon-symbol" :class="[item]" @click="options.iconName = item">
</span>
</RecycleScroller>
<div v-else class="grid w-full h-40 grid-cols-6 mt-3 overflow-x-hidden overflow-y-scroll">
<span
v-for="icon in iconsNames.filter((name) => name.includes(iconSearchString))"
:key="icon"
class="m-1 text-white cursor-pointer mdi icon-symbol"
:class="[icon]"
@click="options.iconName = icon"
/>
</div>
</div>
</div>
</Dialog>
</template>

<script setup lang="ts">
import * as MdiExports from '@mdi/js/mdi'
import { toReactive } from '@vueuse/core'
import { computed, onBeforeMount, ref, toRefs, watch } from 'vue'

import Dropdown from '@/components/Dropdown.vue'
import { round } from '@/libs/utils'
import { useMainVehicleStore } from '@/stores/mainVehicle'

import Dialog from '../Dialog.vue'

const props = defineProps<{
/**
* Configuration of the widget
*/
options: Record<string, unknown>
}>()
const options = toReactive(toRefs(props).options)

onBeforeMount(() => {
// Set initial widget options if they don't exist
if (Object.keys(options).length === 0) {
Object.assign(options, {
variableName: '',
iconName: 'mdi-help-box',
variableUnit: '%',
variableMultiplier: 1,
})
}

iconsNames = Object.keys(MdiExports).map((originalName) => {
return originalName.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())
})
})

const store = useMainVehicleStore()

const currentState = ref<unknown>(0)
const parsedState = computed(() => round(Number(options.variableMultiplier) * Number(currentState.value)))

const updateVariableState = (): void => {
currentState.value = store.genericVariables[options.variableName as string]
}
watch(store.genericVariables, updateVariableState)
watch(options, updateVariableState)

let iconsNames: string[] = []

const showConfigurationMenu = ref(false)
const iconSearchString = ref('')
</script>

<style>
.icon-symbol {
font-size: 2rem;
line-height: 2rem;
}
</style>
8 changes: 8 additions & 0 deletions src/libs/vehicle/ardupilot/ardupilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ export abstract class ArduPilotVehicle<Modes> extends Vehicle.AbstractVehicle<Mo
break
}

case MAVLinkType.NAMED_VALUE_FLOAT: {
const namedValueFloatMessage = mavlink_message.message as Message.NamedValueFloat
const messageName = namedValueFloatMessage.name.join('').replaceAll('\x00', '')
this._genericVariables[messageName] = namedValueFloatMessage.value
this.onGenericVariables.emit()
break
}

default:
break
}
Expand Down
14 changes: 14 additions & 0 deletions src/libs/vehicle/vehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,17 @@ export abstract class AbstractVehicle<Modes> {
_cockpitRegistrationUUID: string
_funnyName: string

// Used to store generic data that does not belong to any specific subclass
// Usually used for development purposes (e.g.: test the implementation of new sensors)
_genericVariables: Record<string, unknown> = {}

// Signals
onArm = new Signal<boolean>()
onAttitude = new Signal<Attitude>()
onAltitude = new Signal<Altitude>()
onBatteries = new Signal<Battery[]>()
onCpuLoad = new Signal<number>()
onGenericVariables = new Signal<Record<string, unknown>>()
onMode = new Signal<Modes>()
onPosition = new Signal<Coordinates>()
onPowerSupply = new Signal<PowerSupply>()
Expand Down Expand Up @@ -103,6 +108,7 @@ export abstract class AbstractVehicle<Modes> {
this.onAttitude.register_caller(() => this.attitude())
this.onBatteries.register_caller(() => this.batteries())
this.onCpuLoad.register_caller(() => this.cpuLoad())
this.onGenericVariables.register_caller(() => this.genericVariables())
this.onMode.register_caller(() => this.mode())
this.onPosition.register_caller(() => this.position())
this.onPowerSupply.register_caller(() => this.powerSupply())
Expand All @@ -127,6 +133,14 @@ export abstract class AbstractVehicle<Modes> {
return this._type
}

/**
* Return the generic variables hashmap of the vehicle
* @returns {Record<string, unknown>}
*/
genericVariables(): Record<string, unknown> {
return this._genericVariables
}

/**
* Return the icon based on the vehicle type
* @returns {string}
Expand Down
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'floating-vue/dist/style.css'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

import { library } from '@fortawesome/fontawesome-svg-core'
import { far } from '@fortawesome/free-regular-svg-icons'
Expand All @@ -7,6 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import FloatingVue from 'floating-vue'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import VueVirtualScroller from 'vue-virtual-scroller'

import App from './App.vue'
import vuetify from './plugins/vuetify'
Expand All @@ -18,5 +20,5 @@ loadFonts()

const app = createApp(App)
app.component('FontAwesomeIcon', FontAwesomeIcon)
app.use(router).use(vuetify).use(createPinia()).use(FloatingVue)
app.use(router).use(vuetify).use(createPinia()).use(FloatingVue).use(VueVirtualScroller)
app.mount('#app')
5 changes: 5 additions & 0 deletions src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
const configurationPages = ref<PageDescription[]>([])
const timeNow = useTimestamp({ interval: 100 })
const statusText: StatusText = reactive({} as StatusText)
const genericVariables: Record<string, unknown> = reactive({})

const mode = ref<string | undefined>(undefined)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -276,6 +277,9 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
mainVehicle.value.onStatusText.add((newStatusText: StatusText) => {
Object.assign(statusText, newStatusText)
})
mainVehicle.value.onGenericVariables.add((newGenericVariablesState: Record<string, unknown>) => {
Object.assign(genericVariables, newGenericVariablesState)
})
mainVehicle.value.onMAVLinkMessage.add(MAVLinkType.HEARTBEAT, (pack: Package) => {
if (pack.header.system_id != 1 || pack.header.component_id != 1) {
return
Expand Down Expand Up @@ -464,5 +468,6 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
currentParameters,
configurationPages,
rtcConfiguration,
genericVariables,
}
})
1 change: 1 addition & 0 deletions src/types/miniWidgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum MiniWidgetType {
BaseCommIndicator = 'BaseCommIndicator',
BatteryIndicator = 'BatteryIndicator',
DepthIndicator = 'DepthIndicator',
GenericIndicator = 'GenericIndicator',
JoystickCommIndicator = 'JoystickCommIndicator',
MiniVideoRecorder = 'MiniVideoRecorder',
ModeSelector = 'ModeSelector',
Expand Down
21 changes: 21 additions & 0 deletions src/types/shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
/* eslint-disable jsdoc/require-jsdoc */
declare module '@vue-leaflet/vue-leaflet'
declare module 'gamepad.js'
declare module 'vuetify'
declare module 'vuetify/lib/components'
declare module 'vuetify/lib/directives'

declare module 'vue-virtual-scroller' {
import Vue, { ComponentOptions, PluginObject, Component } from 'vue'
interface PluginOptions {
installComponents?: boolean
componentsPrefix?: string
}

const plugin: PluginObject<PluginOptions> & {
version: string
}

export const RecycleScroller: Component<unknown, unknown, unknown, unknown>
export const DynamicScroller: Component<unknown, unknown, unknown, unknown>
export const DynamicScrollerItem: Component<unknown, unknown, unknown, unknown>

export function IdState(options?: { idProp?: (vm: unknown) => unknown }): ComponentOptions<Vue> | typeof Vue

export default plugin
}
24 changes: 24 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,11 @@
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-7.0.96.tgz#9853c222623072f5575b4039c8c195ea929b61fc"
integrity sha512-rzlxTfR64hqY8yiBzDjmANfcd8rv+T5C0Yedv/TWk2QyAQYdc66e0kaN1ipmnYU3RukHRTRcBARHzzm+tIhL7w==

"@mdi/js@^7.2.96":
version "7.2.96"
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-7.2.96.tgz#a2a20be740a75e65c8b8b9e12a2b699c06a9208f"
integrity sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA==

"@nodelib/[email protected]":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
Expand Down Expand Up @@ -4668,6 +4673,11 @@ minizlib@^2.1.1:
minipass "^3.0.0"
yallist "^4.0.0"

mitt@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.1.0.tgz#f740577c23176c6205b121b2973514eade1b2230"
integrity sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==

mkdirp@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
Expand Down Expand Up @@ -6202,6 +6212,11 @@ vue-eslint-parser@^8.0.0, vue-eslint-parser@^8.0.1:
lodash "^4.17.21"
semver "^7.3.5"

vue-observe-visibility@^2.0.0-alpha.1:
version "2.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz#1e4eda7b12562161d58984b7e0dea676d83bdb13"
integrity sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==

vue-resize@^2.0.0-alpha.1:
version "2.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a"
Expand Down Expand Up @@ -6230,6 +6245,15 @@ vue-tsc@^1.0.9:
"@volar/vue-language-core" "1.0.9"
"@volar/vue-typescript" "1.0.9"

vue-virtual-scroller@^2.0.0-beta.8:
version "2.0.0-beta.8"
resolved "https://registry.yarnpkg.com/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz#eeceda57e4faa5ba1763994c873923e2a956898b"
integrity sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==
dependencies:
mitt "^2.1.0"
vue-observe-visibility "^2.0.0-alpha.1"
vue-resize "^2.0.0-alpha.1"

vue@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"
Expand Down