Skip to content

Commit df03954

Browse files
core: frontend: create MAVLink-inspector
1 parent 8c40d30 commit df03954

File tree

7 files changed

+279
-0
lines changed

7 files changed

+279
-0
lines changed

core/frontend/src/App.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ export default Vue.extend({
263263
icon: 'mdi-console',
264264
route: '/tools/web-terminal',
265265
},
266+
{
267+
title: 'MAVLink Inspector',
268+
icon: 'mdi-chart-areaspline',
269+
route: '/tools/mavlink-inspector',
270+
},
266271
{
267272
title: 'Version-chooser',
268273
icon: 'mdi-cellphone-arrow-down',
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<template>
2+
<v-container fluid>
3+
<v-row>
4+
<v-col
5+
cols="12"
6+
sm="2"
7+
>
8+
<v-sheet
9+
rounded="lg"
10+
min-height="268"
11+
>
12+
<v-card
13+
class="mx-auto height-limited"
14+
max-height="700px"
15+
>
16+
<v-list shaped>
17+
<v-list-item-group
18+
v-model="selected_message_types"
19+
multiple
20+
>
21+
<template v-for="(item, i) in message_types">
22+
<v-list-item
23+
:key="i"
24+
:value="item"
25+
active-class="deep-purple--text text--accent-4"
26+
>
27+
<template #default="{ active }">
28+
<v-list-item-content>
29+
<v-list-item-title v-text="item" />
30+
</v-list-item-content>
31+
32+
<v-list-item-action>
33+
<v-checkbox
34+
:input-value="active"
35+
color="deep-purple accent-4"
36+
/>
37+
</v-list-item-action>
38+
</template>
39+
</v-list-item>
40+
</template>
41+
</v-list-item-group>
42+
</v-list>
43+
</v-card>
44+
</v-sheet>
45+
</v-col>
46+
47+
<v-col
48+
cols="12"
49+
sm="8"
50+
>
51+
<v-sheet
52+
min-height="70vh"
53+
rounded="lg"
54+
>
55+
<v-card>
56+
<v-virtual-scroll
57+
:items="messages_in_view"
58+
:item-height="40"
59+
height="700px"
60+
>
61+
<template #default="{ item }">
62+
<v-list-item
63+
@click="showDetailed(item)"
64+
>
65+
<v-list-item-content>
66+
<v-list-item-title>{{ item | prettyPrint }}</v-list-item-title>
67+
</v-list-item-content>
68+
</v-list-item>
69+
</template>
70+
</v-virtual-scroll>
71+
</v-card>
72+
</v-sheet>
73+
</v-col>
74+
75+
<v-col
76+
cols="12"
77+
sm="2"
78+
>
79+
<v-sheet
80+
rounded="lg"
81+
min-height="268"
82+
>
83+
<v-card v-if="detailed_message">
84+
<p
85+
v-for="(item, name, index) in detailed_message"
86+
:key="index"
87+
>
88+
{{ name }}: {{ item }}
89+
</p>
90+
</v-card>
91+
</v-sheet>
92+
</v-col>
93+
</v-row>
94+
</v-container>
95+
</template>
96+
<script lang="ts">
97+
import Vue from 'vue'
98+
99+
import mavlink2rest from '@/libs/MAVLink2Rest'
100+
import { Dictionary } from '@/types/common'
101+
import prettify from '@/utils/mavlink_prettifier'
102+
103+
class MAVLinkMessageTable {
104+
tables: Dictionary<Array<Dictionary<any>>> = {}
105+
106+
messageTypes: string[] = []
107+
108+
size_limit = 100 // do not store more than 100 of each message
109+
110+
constructor() {
111+
this.tables = {}
112+
}
113+
114+
add(message: Dictionary<any>): void {
115+
const message_timed = message
116+
message_timed.timestamp = new Date()
117+
if (message.type in this.tables) {
118+
this.tables[message.type].push(message_timed)
119+
if (this.tables[message.type].length > this.size_limit) {
120+
this.tables[message.type].shift()
121+
}
122+
} else {
123+
this.tables[message.type] = [message_timed]
124+
}
125+
}
126+
127+
getTypes(): string[] {
128+
return Object.keys(this.tables).sort()
129+
}
130+
131+
get(types: string[]): any[] {
132+
let result: any[] = []
133+
for (const type of types) {
134+
result = [...result, ...this.tables[type]]
135+
}
136+
return result.sort((x, y) => x.timestamp - y.timestamp)
137+
}
138+
}
139+
140+
export default Vue.extend({
141+
name: 'MavlinkInspector',
142+
components: {
143+
},
144+
filters: {
145+
prettyPrint(message: any) {
146+
return prettify(message)
147+
},
148+
},
149+
data() {
150+
return {
151+
message_types: [] as string[],
152+
message_table: new MAVLinkMessageTable(),
153+
message_type_interval: 0,
154+
messages_in_view_interval: 0,
155+
messages_in_view: [] as any[],
156+
selected_message_types: [],
157+
detailed_message: null as (null | any),
158+
}
159+
},
160+
computed: {
161+
},
162+
mounted() {
163+
this.setupWs()
164+
},
165+
beforeDestroy() {
166+
clearInterval(this.message_type_interval)
167+
clearInterval(this.messages_in_view_interval)
168+
},
169+
methods: {
170+
update_messages_in_view() {
171+
this.messages_in_view = this.message_table.get(this.selected_message_types)
172+
},
173+
showDetailed(message: any) {
174+
this.detailed_message = message
175+
},
176+
setupWs() {
177+
this.messages_in_view_interval = setInterval(() => this.update_messages_in_view(), 500)
178+
this.message_type_interval = setInterval(() => { this.message_types = this.message_table.getTypes() }, 1000)
179+
mavlink2rest.startListening('').setCallback((receivedMessage) => {
180+
this.message_table.add(receivedMessage)
181+
}).setFrequency(0)
182+
},
183+
},
184+
})
185+
</script>
186+
<style>
187+
.height-limited {
188+
overflow-y: auto;
189+
max-height: 700px;
190+
}
191+
</style>

core/frontend/src/libs/MAVLink2Rest/Endpoint.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export default class Endpoint {
3232
const socket = new WebSocket(url)
3333
socket.onmessage = (message: MessageEvent): void => {
3434
this.latestData = JSON.parse(message.data)
35+
for (const listener of this.listeners) {
36+
listener.onNewData(JSON.parse(message.data))
37+
}
3538
}
3639
return socket
3740
}

core/frontend/src/libs/MAVLink2Rest/Listener.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export default class Listener {
3434
*/
3535
setFrequency(frequency: number): Listener {
3636
clearInterval(this.interval)
37+
this.frequency = frequency
38+
if (frequency === 0) {
39+
return this
40+
}
3741
this.interval = window.setInterval(() => {
3842
if (this.parent.latestData !== null) {
3943
this.callback(this.parent.latestData)
@@ -42,6 +46,15 @@ export default class Listener {
4246
return this
4347
}
4448

49+
/**
50+
* If frequency is set to zero, consume data as soon as received
51+
*/
52+
onNewData(data: any): void {
53+
if (this.frequency === 0) {
54+
this.callback(data)
55+
}
56+
}
57+
4558
discard(): void {
4659
clearInterval(this.interval)
4760
this.parent.removeListener(this)

core/frontend/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Firmware from '../views/FirmwareView.vue'
99
import GeneralAutopilot from '../views/GeneralAutopilot.vue'
1010
import LogView from '../views/LogView.vue'
1111
import Main from '../views/MainView.vue'
12+
import MavlinkInspectorView from '../views/MavlinkInspectorView.vue'
1213
import NMEAInjectorView from '../views/NMEAInjectorView.vue'
1314
import SystemInformationView from '../views/SystemInformationView.vue'
1415
import TerminalView from '../views/TerminalView.vue'
@@ -83,6 +84,11 @@ const routes: Array<RouteConfig> = [
8384
name: 'SystemInformation',
8485
component: SystemInformationView,
8586
},
87+
{
88+
path: '/tools/mavlink-inspector',
89+
name: 'MavlinkInspector',
90+
component: MavlinkInspectorView,
91+
},
8692
]
8793

8894
const router = new VueRouter({
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Dictionary } from '@/types/common'
2+
3+
const formatters = {
4+
STATUSTEXT(message: any): string {
5+
return `${message.severity.type.replace('MAV_SEVERITY_', '')}: ${message.text.join('')}`
6+
},
7+
ATTITUDE(message: any): string {
8+
return `ATTITUDE - Roll: ${message.roll.toFixed(2)} rad, Pitch: `
9+
+ `${message.pitch.toFixed(2)} rad, Yaw: ${message.yaw.toFixed(2)} rad`
10+
},
11+
AHRS2(message: any) {
12+
return `AHRS2 - Roll: ${message.roll.toFixed(2)} rad, Pitch: `
13+
+ `${message.pitch.toFixed(2)} rad, Yaw: ${message.yaw.toFixed(2)} rad`
14+
},
15+
BATTERY_STATUS(message: any) {
16+
return `BATTERY_STATUS - ${message.voltages[0] / 1000} V ${message.current_consumed} mAh consumed`
17+
},
18+
GLOBAL_POSITION_INT(message: any) {
19+
return `GLOBAL_POSITION_INT - Lat: ${message.lat.toFixed(5)} Lon: ${message.lon.toFixed(5)}`
20+
},
21+
HEARTBEAT(message: any) {
22+
return `HEATBEAT - ${message.mavtype.type}`
23+
},
24+
NAMED_VALUE_FLOAT(message: any) {
25+
return `NAMED_VALUE_FLOAT - ${message.name.join('')} = ${message.value.toFixed(2)}`
26+
},
27+
SYS_STATUS(message: any) {
28+
return `SYS_STATUS - Batt: (${message.current_battery / 100} A, ${message.voltage_battery / 1000} V)`
29+
},
30+
COMMAND_ACK(message: any) {
31+
return `COMMAND_ACK - ${message.command.type.replace('MAV_CMD_', '')}`
32+
+ ` - ${message.result.type.replace('MAV_RESULT_', '')}`
33+
},
34+
} as Dictionary<(message: any) => string>
35+
36+
export default function prettify(message: any): string {
37+
if (message.type === undefined) {
38+
return 'N/A'
39+
}
40+
const message_type = message.type as string
41+
if (message_type in formatters) {
42+
return formatters[message_type](message)
43+
}
44+
return message.type as string
45+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<mavlink-inspector />
3+
</template>
4+
5+
<script lang="ts">
6+
import Vue from 'vue'
7+
8+
import MavlinkInspector from '@/components/mavlink-inspector/MavlinkInspector.vue'
9+
10+
export default Vue.extend({
11+
name: 'MavlinkInspectorView',
12+
components: {
13+
'mavlink-inspector': MavlinkInspector,
14+
},
15+
})
16+
</script>

0 commit comments

Comments
 (0)