Skip to content

Commit 4135def

Browse files
frontend: add custom SERVO_FUNCTION widgets
1 parent 644d381 commit 4135def

File tree

6 files changed

+568
-35
lines changed

6 files changed

+568
-35
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<div class="d-flex align-center">
3+
<v-row>
4+
<v-col cols="6">
5+
<span class="text-h6">Increment</span>
6+
</v-col>
7+
<v-col cols="6">
8+
<inline-parameter-editor
9+
:label="actuator_inc_param?.name"
10+
:param="actuator_inc_param"
11+
/>
12+
</v-col>
13+
<v-row>
14+
<v-col cols="12">
15+
<v-alert
16+
v-if="!has_joystick_function_configured"
17+
type="warning"
18+
>
19+
<span>Joystick functions not configured for this actuator.<br />
20+
Please configure a joystick button for this actuator using your GCS of choice.</span>
21+
</v-alert>
22+
</v-col>
23+
</v-row>
24+
<servo-function-range-editor
25+
:param="param"
26+
/>
27+
</v-row>
28+
</div>
29+
</template>
30+
31+
<script lang="ts">
32+
import Vue from 'vue'
33+
34+
import autopilot from '@/store/autopilot'
35+
import Parameter from '@/types/autopilot/parameter'
36+
37+
export default Vue.extend({
38+
name: 'ServoFunctionActuatorEditor',
39+
props: {
40+
param: {
41+
type: Object as () => Parameter,
42+
required: true,
43+
},
44+
},
45+
computed: {
46+
actuator_number(): number | undefined {
47+
const option_name = this.param.options?.[this.param.value] as string
48+
const actuator_number = option_name?.match(/\d+/)?.[0]
49+
if (!actuator_number) return undefined
50+
return parseInt(actuator_number, 10)
51+
},
52+
actuator_inc_param(): Parameter | undefined {
53+
return autopilot.parameter(`ACTUATOR${this.actuator_number}_INC`)
54+
},
55+
btn_params(): Parameter[] {
56+
// returns all JS button parameters
57+
return autopilot.parameterRegex('BTN(\\d+)_(S?)FUNCTION') as Parameter[]
58+
},
59+
options(): Record<string, string> {
60+
return this.btn_params[0]?.options as Record<string, string>
61+
},
62+
this_actuator_functions(): number[] {
63+
// returns the joystick button functions for the current actuator
64+
return Object.entries(this.options)
65+
.filter(([_, value]) => value.startsWith(`actuator_${this.actuator_number}_`))
66+
.map(([key]) => parseInt(key, 10))
67+
},
68+
has_joystick_function_configured(): boolean {
69+
return this.btn_params.some((param) => this.this_actuator_functions.includes(param.value as number))
70+
},
71+
},
72+
})
73+
</script>

core/frontend/src/components/parameter-editor/ServoFunctionEditorDialog.vue

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
:param="param"
2424
/>
2525

26-
<servo-function-range-editor :param="param" />
26+
<component
27+
:is="function_type"
28+
v-if="function_type"
29+
:param="param"
30+
/>
2731
</v-card-text>
2832

2933
<v-card-actions>
@@ -40,18 +44,26 @@
4044
</template>
4145

4246
<script lang="ts">
43-
import Vue from 'vue'
47+
import Vue, { VueConstructor } from 'vue'
4448
4549
import Parameter from '@/types/autopilot/parameter'
4650
4751
import InlineParameterEditor from './InlineParameterEditor.vue'
52+
import ServoFunctionActuatorEditor from './ServoFunctionActuatorEditor.vue'
53+
import ServoFunctionGpioEditor from './ServoFunctionGpioEditor.vue'
54+
import ServoFunctionLightsEditor from './ServoFunctionLightsEditor.vue'
55+
import ServoFunctionMotorEditor from './ServoFunctionMotorEditor.vue'
4856
import ServoFunctionRangeEditor from './ServoFunctionRangeEditor.vue'
4957
5058
export default Vue.extend({
5159
name: 'ServoFunctionEditorDialog',
5260
components: {
5361
InlineParameterEditor,
5462
ServoFunctionRangeEditor,
63+
ServoFunctionMotorEditor,
64+
ServoFunctionGpioEditor,
65+
ServoFunctionActuatorEditor,
66+
ServoFunctionLightsEditor,
5567
},
5668
model: {
5769
prop: 'value',
@@ -67,5 +79,26 @@ export default Vue.extend({
6779
required: true,
6880
},
6981
},
82+
computed: {
83+
function_type(): VueConstructor<Vue> | undefined {
84+
const name = this.param.options?.[this.param.value]
85+
if (name?.toLowerCase().includes('motor')) {
86+
return ServoFunctionMotorEditor
87+
}
88+
if (name?.toLowerCase().includes('gpio')) {
89+
return ServoFunctionGpioEditor
90+
}
91+
if (name?.toLowerCase().includes('actuator')) {
92+
return ServoFunctionActuatorEditor
93+
}
94+
if (name?.toLowerCase().includes('lights')) {
95+
return ServoFunctionLightsEditor
96+
}
97+
if (name?.toLowerCase().includes('disabled')) {
98+
return undefined
99+
}
100+
return ServoFunctionRangeEditor
101+
},
102+
},
70103
})
71104
</script>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<template>
2+
<div class="align-center">
3+
GPIOs can be used for Relays, Leak detection, and other functions not yet supported by this UI.
4+
This servo ({{ servo_number }}) gpio is {{ this_servo_gpio }}.
5+
6+
In use by {{ function_type_using_this_gpio?.name }}
7+
8+
<v-row align="end">
9+
<v-col cols="6">
10+
<span style="font-size: 1.0rem;">Change function for this servo's GPIO</span>
11+
</v-col>
12+
<v-col cols="6">
13+
<v-select
14+
v-model="selected_function_type"
15+
:items="options"
16+
item-text="text"
17+
item-value="value"
18+
return-object
19+
hide-details
20+
@change="onChange"
21+
/>
22+
</v-col>
23+
</v-row>
24+
25+
</div>
26+
</template>
27+
28+
<script lang="ts">
29+
import Vue from 'vue'
30+
31+
import mavlink2rest from '@/libs/MAVLink2Rest'
32+
import autopilot_data from '@/store/autopilot'
33+
import autopilot from '@/store/autopilot_manager'
34+
import Parameter from '@/types/autopilot/parameter'
35+
36+
import LeakSetup from './LeakSetup.vue'
37+
import RelaySetup from './RelaySetup.vue'
38+
39+
export default Vue.extend({
40+
name: 'ServoFunctionGpioEditor',
41+
components: {
42+
LeakSetup,
43+
RelaySetup,
44+
},
45+
props: {
46+
param: {
47+
type: Object as () => Parameter,
48+
required: true,
49+
},
50+
},
51+
data() {
52+
return {
53+
selected_function_type: undefined as {text: string, value: Parameter, disabled?: boolean} | undefined,
54+
}
55+
},
56+
computed: {
57+
pin_parameters(): Parameter[] {
58+
return autopilot_data.parameterRegex('^.*_PIN$') as Parameter[]
59+
},
60+
servo_number(): number | undefined {
61+
const option_name = this.param.name.match(/SERVO(\d+)_FUNCTION/)?.[1]
62+
return option_name ? parseInt(option_name, 10) : undefined
63+
},
64+
board_name(): string | undefined {
65+
return autopilot.current_board?.name
66+
},
67+
this_servo_gpio(): number | undefined {
68+
if (!this.servo_number) return undefined
69+
if (this.board_name?.startsWith('Navigator')) {
70+
return this.servo_number
71+
}
72+
// We are assuming most boards follow the same GPIO numbering scheme as the Pixhawk1
73+
// Aux channels start at 50, and Main channels start at 101
74+
75+
if (this.servo_number <= 8) {
76+
return this.servo_number + 100
77+
}
78+
return this.servo_number - 9 + 50
79+
},
80+
function_pin_parameter_using_this_gpio(): Parameter | undefined {
81+
return this.pin_parameters.find((param) => param.value === this.this_servo_gpio)
82+
},
83+
function_type_using_this_gpio(): Parameter | undefined {
84+
const param_name = this.function_pin_parameter_using_this_gpio?.name.split('_')[0]
85+
let new_param_name: string | undefined
86+
if (param_name?.includes('RELAY')) {
87+
new_param_name = `${param_name}_FUNCTION`
88+
}
89+
if (param_name?.includes('LEAK')) {
90+
new_param_name = `${param_name}_TYPE`
91+
}
92+
return new_param_name
93+
? autopilot_data.parameter(new_param_name as string)
94+
: this.function_pin_parameter_using_this_gpio
95+
},
96+
relay_parameters(): Parameter[] {
97+
return autopilot_data.parameterRegex('^RELAY(\\d+)_FUNCTION$') as Parameter[]
98+
},
99+
leak_parameters(): Parameter[] {
100+
return autopilot_data.parameterRegex('^LEAK(\\d+)_TYPE$') as Parameter[]
101+
},
102+
options(): {text: string, value: Parameter, disabled?: boolean}[] {
103+
const supportedOptions: {text: string, value: Parameter, disabled?: boolean}[] = [
104+
...this.relay_parameters,
105+
...this.leak_parameters,
106+
].map((param) => ({
107+
text: param.name.split('_')[0].toLowerCase().toTitle(),
108+
value: param,
109+
}))
110+
// If the current GPIO is used by an unsupported parameter, add it to the options
111+
const pinParam = this.function_pin_parameter_using_this_gpio
112+
if (pinParam && !this.isSupportedParam(pinParam)) {
113+
supportedOptions.unshift({
114+
text: `${pinParam.name} (not supported)`,
115+
value: pinParam,
116+
disabled: true,
117+
})
118+
}
119+
return supportedOptions
120+
},
121+
},
122+
watch: {
123+
function_type_using_this_gpio: {
124+
handler(new_value: Parameter | undefined) {
125+
if (!new_value) {
126+
this.selected_function_type = undefined
127+
return
128+
}
129+
const isSupported = this.isSupportedParam(new_value)
130+
this.selected_function_type = {
131+
text: isSupported
132+
? new_value.name.split('_')[0].toLowerCase().toTitle()
133+
: `${new_value.name} (not supported)`,
134+
value: new_value,
135+
}
136+
},
137+
immediate: true,
138+
},
139+
},
140+
methods: {
141+
isSupportedParam(param: Parameter): boolean {
142+
return param.name.includes('RELAY') || param.name.includes('LEAK')
143+
},
144+
onChange(value: {text: string, value: Parameter}) {
145+
const is_relay = value.value.name.includes('RELAY')
146+
const is_leak = value.value.name.includes('LEAK')
147+
if (!this.this_servo_gpio) {
148+
console.warn('No GPIO found for servo', this.servo_number)
149+
return
150+
}
151+
if (!is_relay && !is_leak) {
152+
// Unsupported parameter type, don't handle
153+
return
154+
}
155+
if (is_relay) {
156+
const paramName = value.value.name
157+
const paramType = value.value.paramType.type
158+
mavlink2rest.setParam(paramName, 1 /* Relay */, autopilot_data.system_id, paramType)
159+
const pin_param_name = paramName.replace('FUNCTION', 'PIN')
160+
const gpio = this.this_servo_gpio
161+
mavlink2rest.setParam(pin_param_name, gpio, autopilot_data.system_id, 'MAV_PARAM_TYPE_INT8')
162+
const pinParam = this.function_pin_parameter_using_this_gpio
163+
if (pinParam) {
164+
mavlink2rest.setParam(pinParam.name, -1, autopilot_data.system_id, 'MAV_PARAM_TYPE_INT8')
165+
}
166+
}
167+
if (is_leak) {
168+
const paramName = value.value.name
169+
const paramType = value.value.paramType.type
170+
mavlink2rest.setParam(paramName, 1 /* Digital */, autopilot_data.system_id, paramType)
171+
const pin_param_name = paramName.replace('TYPE', 'PIN')
172+
const gpio = this.this_servo_gpio
173+
mavlink2rest.setParam(pin_param_name, gpio, autopilot_data.system_id, 'MAV_PARAM_TYPE_INT8')
174+
const pinParam = this.function_pin_parameter_using_this_gpio
175+
if (pinParam) {
176+
mavlink2rest.setParam(pinParam.name, -1, autopilot_data.system_id, 'MAV_PARAM_TYPE_INT8')
177+
}
178+
}
179+
},
180+
},
181+
})
182+
</script>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<div class="d-flex align-center">
3+
<v-row>
4+
<v-col cols="6">
5+
<span class="text-h6">Joystick Steps</span>
6+
</v-col>
7+
<v-col cols="6">
8+
<inline-parameter-editor
9+
:label="steps_param?.name"
10+
:param="steps_param"
11+
/>
12+
</v-col>
13+
<servo-function-range-editor
14+
:param="param"
15+
/>
16+
</v-row>
17+
</div>
18+
</template>
19+
20+
<script lang="ts">
21+
import Vue from 'vue'
22+
23+
import autopilot from '@/store/autopilot'
24+
import Parameter from '@/types/autopilot/parameter'
25+
26+
import InlineParameterEditor from './InlineParameterEditor.vue'
27+
import ServoFunctionRangeEditor from './ServoFunctionRangeEditor.vue'
28+
29+
export default Vue.extend({
30+
name: 'ServoFunctionLightsEditor',
31+
components: {
32+
InlineParameterEditor,
33+
ServoFunctionRangeEditor,
34+
},
35+
props: {
36+
param: {
37+
type: Object as () => Parameter,
38+
required: true,
39+
},
40+
},
41+
computed: {
42+
steps_param(): Parameter | undefined {
43+
return autopilot.parameter('JS_LIGHTS_STEPS')
44+
},
45+
},
46+
})
47+
</script>

0 commit comments

Comments
 (0)