Skip to content

Commit

Permalink
src: Create global vehicle address, while allowing custom addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
joaoantoniocardoso authored and patrickelectric committed Feb 15, 2023
1 parent 0f0f412 commit ffb51a3
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 34 deletions.
4 changes: 1 addition & 3 deletions src/assets/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as Connection from '@/libs/connection/connection'
import { type Profile, WidgetType } from '@/types/widgets'

export const defaultGlobalAddress = 'blueos.wifi'
export const mavlink2restServerURI = new Connection.URI('ws://blueos.local:6040/ws/mavlink')
export const defaultGlobalAddress = 'blueos.local'
export const widgetProfiles: { [key: string]: Profile } = {
'c2bcf04d-048f-496f-9d78-fc4002608028': {
name: 'Default Cockpit profile',
Expand Down
70 changes: 65 additions & 5 deletions src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useStorage, useTimestamp } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed, reactive, ref, watch } from 'vue'
import { type Ref, computed, reactive, ref, watch } from 'vue'

import { mavlink2restServerURI } from '@/assets/defaults'
import { defaultGlobalAddress } from '@/assets/defaults'
import * as Connection from '@/libs/connection/connection'
import { ConnectionManager } from '@/libs/connection/connection-manager'
import type { Package } from '@/libs/connection/messages/mavlink2rest'
Expand All @@ -17,9 +17,67 @@ import { ProtocolControllerState } from '@/types/joystick'

import { useControllerStore } from './controller'

/**
* This is an abstraction that holds a customizable parameter that can fallback to a default value
*
* @template T The customizable parameter type
*/
class CustomizableParameter<T> {
private _customValue: T
private _defaultValue: () => T
isCustom = false

/**
* @param {Ref<T>} defaultVal The default parameter value
*/
constructor(defaultVal: () => T) {
this._defaultValue = defaultVal
this._customValue = this.defaultValue
}

/**
* Sets the URI to a given custom one
*
* @param {T} val
*/
set val(val: T) {
this._customValue = val
}

/**
* @returns {T} The current configured parameter, whether default or custom
*/
get val(): T {
return this.isCustom ? this._customValue : this.defaultValue
}

/**
* @returns {T} The current configured parameter, whether default or custom
*/
get defaultValue(): T {
return this._defaultValue()
}

/**
* Resets custom to the default value and disables custom
*/
public reset(): void {
this.isCustom = false
this._customValue = this.defaultValue
}
}

export const useMainVehicleStore = defineStore('main-vehicle', () => {
const cpuLoad = ref<number>()
const mainConnectionURI = useStorage('cockpit-main-connection-uri', mavlink2restServerURI)
const globalAddress = useStorage('cockpit-vehicle-address', defaultGlobalAddress)
const _mainConnectionURI = new CustomizableParameter<Connection.URI>(() => {
return new Connection.URI(`ws://${globalAddress.value}:6040/ws/mavlink`)
})
const mainConnectionURI = ref(_mainConnectionURI)
const _webRTCSignallingURI = new CustomizableParameter<Connection.URI>(() => {
return new Connection.URI(`ws://${globalAddress.value}:6021`)
})
const webRTCSignallingURI = ref(_webRTCSignallingURI)
const lastHeartbeat = ref<Date>()
const firmwareType = ref<MavAutopilot>()
const vehicleType = ref<MavType>()
Expand Down Expand Up @@ -102,11 +160,11 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
ConnectionManager.onMainConnection.add(() => {
const newMainConnection = ConnectionManager.mainConnection()
if (newMainConnection !== undefined) {
mainConnectionURI.value = newMainConnection.uri()
mainConnectionURI.value.val = newMainConnection.uri()
}
})

ConnectionManager.addConnection(new Connection.URI(mainConnectionURI.value), Protocol.Type.MAVLink)
ConnectionManager.addConnection(mainConnectionURI.value.val, Protocol.Type.MAVLink)

const getAutoPilot = (vehicles: WeakRef<Vehicle.Abstract>[]): ArduPilot => {
const vehicle = vehicles?.last()?.deref()
Expand Down Expand Up @@ -193,7 +251,9 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
modesAvailable,
setFlightMode,
sendGcsHeartbeat,
globalAddress,
mainConnectionURI,
webRTCSignallingURI,
cpuLoad,
lastHeartbeat,
firmwareType,
Expand Down
195 changes: 169 additions & 26 deletions src/views/ConfigurationGeneralView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,140 @@
<BaseConfigurationView>
<template #title>General configuration</template>
<template #content>
<v-card class="pa-5 pb-2 ma-4" max-width="600px">
<v-icon :icon="'mdi-earth'" class="mr-3" />
<span class="text-h6">Global vehicle address</span>
<v-form
ref="globalAddressForm"
v-model="globalAddressFormValid"
class="d-flex justify-center align-center"
@submit.prevent="setGlobalAddress"
>
<v-text-field
v-model="newGlobalAddress"
variant="underlined"
type="input"
hint="Address of the Vehicle. E.g: blueos.local"
class="uri-input"
:rules="[isValidHostAddress]"
/>

<v-btn v-tooltip.bottom="'Set'" icon="mdi-check" class="pa-0 mx-1 mb-5" rounded="lg" flat type="submit" />
<v-template>
<v-btn
v-tooltip.bottom="'Reset to default'"
:disabled="newGlobalAddress === defaultGlobalAddress"
icon="mdi-refresh"
class="pa-0 mx-1 mb-5"
rounded="lg"
flat
@click="resetGlobalAddress"
/>
</v-template>
</v-form>
<span>Current address: {{ mainVehicleStore.globalAddress }} </span><br />
</v-card>
<v-card class="pa-5 pb-2 ma-4" max-width="600px">
<v-progress-circular v-if="vehicleConnected === undefined" indeterminate size="24" class="mr-3" />
<v-icon v-else :icon="vehicleConnected ? 'mdi-lan-connect' : 'mdi-lan-disconnect'" class="mr-3" />
<span class="text-h6">Vehicle connection</span>
<div class="my-6">
<span class="text-caption font-weight-thin"> Current connection link: </span>
<br />
<span class="text-body-1">
{{ mainVehicleStore.mainConnectionURI }}
</span>
</div>
<span class="text-h6">Mavlink2Rest connection</span>
<v-form
ref="connectionForm"
v-model="connectionFormValid"
class="d-flex justify-center align-center"
@submit.prevent="addNewConnection"
@submit.prevent="addNewVehicleConnection"
>
<v-checkbox
v-model="connectionURI.isCustom"
v-tooltip.bottom="'Enable custom'"
class="pa-0 mx-1 mb-5"
rounded="lg"
hide-details
/>

<v-text-field
v-model="newConnectionURI"
v-model="connectionURI.val"
:disabled="!connectionURI.isCustom"
label="Mavlink2Rest URI"
variant="underlined"
type="input"
hint="URI of a Mavlink2Rest web-socket"
class="uri-input"
:rules="[isValidConnectionURI]"
:rules="[isValidSocketConnectionURI]"
/>

<v-btn v-tooltip.bottom="'Set'" icon="mdi-check" class="pa-0 mx-1 mb-5" rounded="lg" flat type="submit" />
<v-template>
<v-btn
v-tooltip.bottom="'Reset to default'"
:disabled="connectionURI.toString() === connectionURI.defaultValue.toString()"
icon="mdi-refresh"
class="pa-0 mx-1 mb-5"
rounded="lg"
flat
@click="resetVehicleConnection"
/>
</v-template>
</v-form>
<span>Current address: {{ ConnectionManager.mainConnection()?.uri().toString() ?? 'none' }} </span><br />
<span
>Status:
{{
vehicleConnected ? 'connected' : vehicleConnected === undefined ? 'connecting...' : 'failed to connect'
}}</span
>
</v-card>
<v-card class="pa-5 pb-2 ma-4" max-width="600px">
<v-icon :icon="'mdi-lan-pending'" class="mr-3" />
<span class="text-h6">WebRTC connection</span>
<v-form
ref="webRTCSignallingForm"
v-model="webRTCSignallingFormValid"
class="d-flex justify-center align-center"
@submit.prevent="setWebRTCSignallingURI"
>
<v-checkbox
v-model="webRTCSignallingURI.isCustom"
v-tooltip.bottom="'Enable custom'"
class="pa-0 mx-1 mb-5"
rounded="lg"
hide-details
/>

<v-text-field
v-model="webRTCSignallingURI.val"
:disabled="!webRTCSignallingURI.isCustom"
label="WebRTC Signalling Server URI"
variant="underlined"
type="input"
hint="URI of a WebRTC Signalling Server URI"
class="uri-input"
:rules="[isValidSocketConnectionURI]"
/>
<v-btn icon="mdi-check" class="pa-0 mx-1 mb-5" rounded="lg" flat type="submit" />
<v-template v-if="newConnectionURI.toString() !== mavlink2restServerURI.toString()">
<v-btn icon="mdi-refresh" class="pa-0 mx-1 mb-5" rounded="lg" flat @click="resetConnection" />

<v-btn v-tooltip.bottom="'Set'" icon="mdi-check" class="pa-0 mx-1 mb-5" rounded="lg" flat type="submit" />
<v-template>
<v-btn
v-tooltip.bottom="'Reset to default'"
:disabled="webRTCSignallingURI.val.toString() === webRTCSignallingURI.defaultValue.toString()"
icon="mdi-refresh"
class="pa-0 mx-1 mb-5"
rounded="lg"
flat
@click="resetWebRTCSignallingURI"
/>
</v-template>
</v-form>
<span>Current address: {{ mainVehicleStore.webRTCSignallingURI.val.toString() }} </span><br />
</v-card>
</template>
</BaseConfigurationView>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ref, watch } from 'vue'
import { mavlink2restServerURI } from '@/assets/defaults'
import { defaultGlobalAddress } from '@/assets/defaults'
import * as Connection from '@/libs/connection/connection'
import { ConnectionManager } from '@/libs/connection/connection-manager'
import * as Protocol from '@/libs/vehicle/protocol/protocol'
Expand All @@ -51,46 +145,95 @@ import BaseConfigurationView from './BaseConfigurationView.vue'
const mainVehicleStore = useMainVehicleStore()
const globalAddressForm = ref()
const globalAddressFormValid = ref(false)
const newGlobalAddress = ref(mainVehicleStore.globalAddress)
const webRTCSignallingForm = ref()
const webRTCSignallingFormValid = ref(false)
const webRTCSignallingURI = ref(mainVehicleStore.webRTCSignallingURI)
const connectionForm = ref()
const connectionFormValid = ref(false)
const newConnectionURI = ref(mainVehicleStore.mainConnectionURI)
const connectionURI = ref(mainVehicleStore.mainConnectionURI)
const vehicleConnected = ref<boolean | undefined>(mainVehicleStore.isVehicleOnline)
watch(
() => mainVehicleStore.isVehicleOnline,
() => (vehicleConnected.value = mainVehicleStore.isVehicleOnline)
)
const isValidConnectionURI = computed(() => {
const isValidHostAddress = (value: string): boolean | string => {
if (value.length >= 255) {
return 'Address is too long'
}
// Regexes from https://stackoverflow.com/a/106223/3850957
const ipRegex = new RegExp(
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
)
const hostnameRegex = new RegExp(
'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$'
)
if (ipRegex.test(value) || hostnameRegex.test(value)) {
return true
}
return 'Invalid host address. Should be an IP address or a hostname'
}
const isValidSocketConnectionURI = (value: string): boolean | string => {
try {
const conn = new Connection.URI(newConnectionURI.value)
const conn = new Connection.URI(value)
if (conn.type() !== Connection.Type.WebSocket) {
throw new Error('URI should be of type WebSocket')
}
} catch (error) {
return `Invalid connection URI. ${error}.`
}
return true
})
}
const resetGlobalAddress = (): void => {
newGlobalAddress.value = defaultGlobalAddress
setGlobalAddress()
}
const resetVehicleConnection = async (): Promise<void> => {
connectionURI.value.reset()
const resetConnection = async (): Promise<void> => {
newConnectionURI.value = mavlink2restServerURI
await addNewConnection()
await addNewVehicleConnection()
}
const resetWebRTCSignallingURI = (): void => {
webRTCSignallingURI.value.reset()
}
// Adds a new connection, which right now is the same as changing the main one
const addNewConnection = async (): Promise<void> => {
const addNewVehicleConnection = async (): Promise<void> => {
await connectionForm.value.validate()
vehicleConnected.value = undefined
setTimeout(() => (vehicleConnected.value ??= false), 5000)
try {
ConnectionManager.addConnection(new Connection.URI(newConnectionURI.value), Protocol.Type.MAVLink)
ConnectionManager.addConnection(new Connection.URI(connectionURI.value.val), Protocol.Type.MAVLink)
} catch (error) {
console.error(error)
alert(`Could not update main connection. ${error}.`)
return
}
alert('New connection successfully configured.')
console.debug(`New connection successfully configured to ${connectionURI.value.val}.`)
}
const setGlobalAddress = async (): Promise<void> => {
await globalAddressForm.value.validate()
mainVehicleStore.globalAddress = newGlobalAddress.value
}
const setWebRTCSignallingURI = async (): Promise<void> => {
await webRTCSignallingForm.value.validate()
mainVehicleStore.webRTCSignallingURI.val = webRTCSignallingURI.value.val
}
</script>

Expand Down

0 comments on commit ffb51a3

Please sign in to comment.