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 MAVLink inspection tool #1483

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/components/development/MAVLinkInspector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="flex flex-col gap-4">
<div class="flex flex-row gap-4 mb-4">
<div class="flex-1">
<div class="text-lg mb-2">Available Message Types</div>
<div class="mb-2">
<input
v-model="searchQuery"
type="text"
placeholder="Search messages..."
class="w-full px-3 py-2 bg-[#FFFFFF22] rounded-md text-white placeholder-gray-400 outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div class="bg-[#FFFFFF11] rounded-md p-2 max-h-[300px] overflow-y-auto">
<div
v-for="type in filteredMessageTypes"
:key="type"
class="cursor-pointer hover:bg-[#FFFFFF22] p-1 rounded"
:class="{ 'bg-[#FFFFFF33]': trackedMessageTypes.has(type) }"
@click="toggleMessageTracking(type)"
>
{{ type }}
</div>
<div v-if="filteredMessageTypes.length === 0" class="text-gray-400 text-center p-2">No messages found</div>
</div>
</div>
<div class="flex-1">
<div class="text-lg mb-2">Message Values</div>
<div class="bg-[#FFFFFF11] rounded-md p-2 max-h-[340px] overflow-y-auto">
<div v-for="type in trackedMessageTypes" :key="type" class="mb-2">
<div class="font-bold">{{ type }}</div>
<div class="text-xs text-gray-400 mb-1">Incoming Messages:</div>
<pre class="text-sm whitespace-pre-wrap">{{
JSON.stringify(messageValues.get(`in:${type}`), null, 2)
}}</pre>
<div class="text-xs text-gray-400 mt-2 mb-1">Outgoing Messages:</div>
<pre class="text-sm whitespace-pre-wrap">{{
JSON.stringify(messageValues.get(`out:${type}`), null, 2)
}}</pre>
</div>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'

import type { Package } from '@/libs/connection/m2r/messages/mavlink2rest'
import { MAVLinkType } from '@/libs/connection/m2r/messages/mavlink2rest-enum'
import { useMainVehicleStore } from '@/stores/mainVehicle'

const mainVehicleStore = useMainVehicleStore()
const searchQuery = ref('')

const availableMessageTypes = computed(() => {
return Object.values(MAVLinkType).filter((type) => typeof type === 'string')
})

const filteredMessageTypes = computed(() => {
const query = searchQuery.value.toLowerCase()
if (!query) return availableMessageTypes.value
return availableMessageTypes.value.filter((type) => type.toLowerCase().includes(query))
})

const trackedMessageTypes = ref<Set<MAVLinkType>>(new Set())
const messageValues = ref<Map<string, unknown>>(new Map())

const toggleMessageTracking = (type: MAVLinkType): void => {
if (trackedMessageTypes.value.has(type)) {
trackedMessageTypes.value.delete(type)
messageValues.value.delete(`in:${type}`)
messageValues.value.delete(`out:${type}`)
} else {
trackedMessageTypes.value.add(type)
setupMessageListeners(type)
}
}

const setupMessageListeners = (type: MAVLinkType): void => {
try {
mainVehicleStore.listenToIncomingMessages(type, (pack: Package) => {
messageValues.value.set(`in:${type}`, pack.message)
})
mainVehicleStore.listenToOutgoingMessages(type, (pack: Package) => {
messageValues.value.set(`out:${type}`, pack.message)
})
} catch (error) {
console.error(`Failed to setup message listeners for type ${type}:`, error)
}
}

// Set up listeners for any already tracked message types
onMounted(() => {
trackedMessageTypes.value.forEach(setupMessageListeners)
})

// Clean up when component is unmounted
onUnmounted(() => {
trackedMessageTypes.value.clear()
messageValues.value.clear()
})
</script>
4 changes: 4 additions & 0 deletions src/libs/connection/connection-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class ConnectionManager {
// Signals
static onMainConnection = new Signal<WeakRef<Connection.Abstract>>()
static onRead = new Signal<Uint8Array>()
static onWrite = new Signal<Uint8Array>()

/**
* Return the connections available
Expand Down Expand Up @@ -69,7 +70,9 @@ export class ConnectionManager {
}

previousConnection?.onRead?.clear()
previousConnection?.onWrite?.clear()
connection.onRead.add((data: Uint8Array) => this.onRead.emit_value(data))
connection.onWrite.add((data: Uint8Array) => this.onWrite.emit_value(data))
ConnectionManager._mainConnection = new WeakRef(connection)
// There is no constructor and updating the register is not expensive in this function
ConnectionManager.onMainConnection.register_caller(
Expand All @@ -96,6 +99,7 @@ export class ConnectionManager {
*/
static write(data: Uint8Array): boolean {
ConnectionManager.mainConnection()?.write(data)
ConnectionManager.onWrite.emit_value(data)
return true
}
}
1 change: 1 addition & 0 deletions src/libs/connection/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export abstract class Abstract {

// Signals
public onRead = new Signal<Uint8Array>()
public onWrite = new Signal<Uint8Array>()

/**
* Return the connection uri
Expand Down
27 changes: 24 additions & 3 deletions src/libs/vehicle/ardupilot/ardupilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export abstract class ArduPilotVehicle<Modes> extends Vehicle.AbstractVehicle<Mo

_messages: MAVLinkMessageDictionary = new Map()

onMAVLinkMessage = new SignalTyped()
onIncomingMAVLinkMessage = new SignalTyped()
onOutgoingMAVLinkMessage = new SignalTyped()
_flying = false

protected currentSystemId = 1
Expand Down Expand Up @@ -270,11 +271,31 @@ export abstract class ArduPilotVehicle<Modes> extends Vehicle.AbstractVehicle<Mo
]
}

/**
* Log outgoing messages
* @param {Uint8Array} message
*/
onOutgoingMessage(message: Uint8Array): void {
const textDecoder = new TextDecoder()
let mavlink_message: Package
const text_message = textDecoder.decode(message)
try {
mavlink_message = JSON.parse(text_message) as Package
this.onOutgoingMAVLinkMessage.emit_value(mavlink_message.message.type, mavlink_message)
} catch (error) {
const pattern = /Ok\((\d+)\)/
const match = pattern.exec(text_message)
if (match) return
console.error(`Failed to parse mavlink message: ${text_message}`)
return
}
}

/**
* Decode incoming message
* @param {Uint8Array} message
*/
onMessage(message: Uint8Array): void {
onIncomingMessage(message: Uint8Array): void {
const textDecoder = new TextDecoder()
let mavlink_message: Package
const text_message = textDecoder.decode(message)
Expand Down Expand Up @@ -350,7 +371,7 @@ export abstract class ArduPilotVehicle<Modes> extends Vehicle.AbstractVehicle<Mo

// TODO: Maybe create a signal class to deal with MAVLink only
// Where add will use the template argument type to define the lambda argument type
this.onMAVLinkMessage.emit_value(mavlink_message.message.type, mavlink_message)
this.onIncomingMAVLinkMessage.emit_value(mavlink_message.message.type, mavlink_message)

if (Object.keys(this._usedGenericVariablesdMessagePaths).includes(mavlink_message.message.type)) {
this._usedGenericVariablesdMessagePaths[mavlink_message.message.type].forEach((path) => {
Expand Down
3 changes: 2 additions & 1 deletion src/libs/vehicle/vehicle-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export class VehicleFactory {
return undefined
}

ConnectionManager.onRead.add((message) => vehicle?.onMessage(message))
ConnectionManager.onRead.add((message) => vehicle?.onIncomingMessage(message))
ConnectionManager.onWrite.add((message) => vehicle?.onOutgoingMessage(message))

VehicleFactory._vehicles.push(new WeakRef(vehicle))
VehicleFactory.onVehicles.register_caller(this.vehicles)
Expand Down
3 changes: 2 additions & 1 deletion src/libs/vehicle/vehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ export abstract class AbstractVehicle<Modes> {
}
}

abstract onMessage(message: Uint8Array): void
abstract onIncomingMessage(message: Uint8Array): void
abstract onOutgoingMessage(message: Uint8Array): void

abstract arm(): Promise<void>
abstract altitude(): Altitude
Expand Down
18 changes: 17 additions & 1 deletion src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
}
}, 1000)

mainVehicle.value.onMAVLinkMessage.add(MAVLinkType.HEARTBEAT, (pack: Package) => {
mainVehicle.value.onIncomingMAVLinkMessage.add(MAVLinkType.HEARTBEAT, (pack: Package) => {
if (pack.header.component_id != 1) {
return
}
Expand Down Expand Up @@ -533,6 +533,20 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
})
})

const listenToIncomingMessages = (messageType: string, callback: (pack: Package) => void): void => {
if (!mainVehicle.value) {
throw new Error('No vehicle available to listen for incoming messages.')
}
mainVehicle.value?.onIncomingMAVLinkMessage.add(messageType, callback)
}

const listenToOutgoingMessages = (messageType: string, callback: (pack: Package) => void): void => {
if (!mainVehicle.value) {
throw new Error('No vehicle available to listen for outgoing messages.')
}
mainVehicle.value?.onOutgoingMAVLinkMessage.add(messageType, callback)
}

// Allow us to set custom commands to be used in the browser
// Expert mode
watch(mainVehicle, async (newVehicle) => {
Expand Down Expand Up @@ -618,5 +632,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
genericVariables,
availableGenericVariables,
registerUsageOfGenericVariable,
listenToIncomingMessages,
listenToOutgoingMessages,
}
})
7 changes: 7 additions & 0 deletions src/views/ConfigurationDevelopmentView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
thumb-label="hover"
/>
</div>
<ExpansiblePanel :is-expanded="!interfaceStore.isOnPhoneScreen">
<template #title>MAVLink Inspector</template>
<template #content>
<MAVLinkInspector />
</template>
</ExpansiblePanel>
<ExpansiblePanel :is-expanded="!interfaceStore.isOnPhoneScreen">
<template #title>
<div class="flex justify-between">
Expand Down Expand Up @@ -97,6 +103,7 @@ import { saveAs } from 'file-saver'
import { onBeforeMount } from 'vue'
import { ref } from 'vue'

import MAVLinkInspector from '@/components/development/MAVLinkInspector.vue'
import ExpansiblePanel from '@/components/ExpansiblePanel.vue'
import { type SystemLog, cockpitSytemLogsDB } from '@/libs/system-logging'
import { reloadCockpit } from '@/libs/utils'
Expand Down
Loading