Improve multi (#7136)
[opentx.git] / radio / src / functions.cpp
blob0d1ebbf381335fb78327869391ebea8335ae26b1
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "opentx.h"
23 CustomFunctionsContext modelFunctionsContext = { 0 };
25 CustomFunctionsContext globalFunctionsContext = { 0 };
27 #if defined(DEBUG)
29 * This is a test function for debugging purpose, you may insert there your code and compile with the option DEBUG=YES
31 void testFunc()
33 #ifdef SIMU
34 printf("testFunc\n"); fflush(stdout);
35 #endif
37 // for testing the WD reset uncomment the following line
38 // while (1);
40 #endif
42 PLAY_FUNCTION(playValue, source_t idx)
44 if (IS_FAI_FORBIDDEN(idx))
45 return;
47 if (idx == MIXSRC_NONE)
48 return;
50 getvalue_t val = getValue(idx);
52 if (idx >= MIXSRC_FIRST_TELEM) {
53 TelemetrySensor & telemetrySensor = g_model.telemetrySensors[(idx-MIXSRC_FIRST_TELEM) / 3];
54 uint8_t attr = 0;
55 if (telemetrySensor.prec > 0) {
56 if (telemetrySensor.prec == 2) {
57 if (val >= 5000) {
58 val = div_and_round(val, 100);
60 else {
61 val = div_and_round(val, 10);
62 attr = PREC1;
65 else {
66 if (val >= 500) {
67 val = div_and_round(val, 10);
69 else {
70 attr = PREC1;
74 PLAY_NUMBER(val, telemetrySensor.unit == UNIT_CELLS ? UNIT_VOLTS : telemetrySensor.unit, attr);
76 else if (idx >= MIXSRC_FIRST_TIMER && idx <= MIXSRC_LAST_TIMER) {
77 PLAY_DURATION(val, 0);
79 else if (idx == MIXSRC_TX_TIME) {
80 PLAY_DURATION(val*60, PLAY_TIME);
82 else if (idx == MIXSRC_TX_VOLTAGE) {
83 PLAY_NUMBER(val, UNIT_VOLTS, PREC1);
85 else {
86 if (idx <= MIXSRC_LAST_CH) {
87 val = calcRESXto100(val);
89 PLAY_NUMBER(val, 0, 0);
93 void playCustomFunctionFile(const CustomFunctionData * sd, uint8_t id)
95 if (sd->play.name[0] != '\0') {
96 char filename[sizeof(SOUNDS_PATH) + LEN_FUNCTION_NAME + sizeof(SOUNDS_EXT)] = SOUNDS_PATH "/";
97 strncpy(filename + SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2);
98 strncpy(filename + sizeof(SOUNDS_PATH), sd->play.name, LEN_FUNCTION_NAME);
99 filename[sizeof(SOUNDS_PATH) + LEN_FUNCTION_NAME] = '\0';
100 strcat(filename + sizeof(SOUNDS_PATH), SOUNDS_EXT);
101 PLAY_FILE(filename, sd->func == FUNC_BACKGND_MUSIC ? PLAY_BACKGROUND : 0, id);
105 bool isRepeatDelayElapsed(const CustomFunctionData * functions, CustomFunctionsContext & functionsContext, uint8_t index)
107 const CustomFunctionData * cfn = &functions[index];
108 tmr10ms_t tmr10ms = get_tmr10ms();
109 uint8_t repeatParam = CFN_PLAY_REPEAT(cfn);
110 if (!IS_SILENCE_PERIOD_ELAPSED() && repeatParam == CFN_PLAY_REPEAT_NOSTART) {
111 functionsContext.lastFunctionTime[index] = tmr10ms;
113 if (!functionsContext.lastFunctionTime[index] || (repeatParam && repeatParam!=CFN_PLAY_REPEAT_NOSTART && (signed)(tmr10ms-functionsContext.lastFunctionTime[index])>=100*repeatParam)) {
114 functionsContext.lastFunctionTime[index] = tmr10ms;
115 return true;
117 else {
118 return false;
122 #define VOLUME_HYSTERESIS 10 // how much must a input value change to actually be considered for new volume setting
123 getvalue_t requiredSpeakerVolumeRawLast = 1024 + 1; //initial value must be outside normal range
125 void evalFunctions(const CustomFunctionData * functions, CustomFunctionsContext & functionsContext)
127 MASK_FUNC_TYPE newActiveFunctions = 0;
128 MASK_CFN_TYPE newActiveSwitches = 0;
130 uint8_t playFirstIndex = (functions == g_model.customFn ? 1 : 1+MAX_SPECIAL_FUNCTIONS);
131 #define PLAY_INDEX (i+playFirstIndex)
133 #if defined(OVERRIDE_CHANNEL_FUNCTION)
134 for (uint8_t i=0; i<MAX_OUTPUT_CHANNELS; i++) {
135 safetyCh[i] = OVERRIDE_CHANNEL_UNDEFINED;
137 #endif
139 #if defined(GVARS)
140 for (uint8_t i=0; i<NUM_TRIMS; i++) {
141 trimGvar[i] = -1;
143 #endif
145 for (uint8_t i=0; i<MAX_SPECIAL_FUNCTIONS; i++) {
146 const CustomFunctionData * cfn = &functions[i];
147 swsrc_t swtch = CFN_SWITCH(cfn);
148 if (swtch) {
149 MASK_CFN_TYPE switch_mask = ((MASK_CFN_TYPE)1 << i);
151 bool active = getSwitch(swtch, IS_PLAY_FUNC(CFN_FUNC(cfn)) ? GETSWITCH_MIDPOS_DELAY : 0);
153 if (HAS_ENABLE_PARAM(CFN_FUNC(cfn))) {
154 active &= (bool)CFN_ACTIVE(cfn);
157 if (active) {
158 switch (CFN_FUNC(cfn)) {
160 #if defined(OVERRIDE_CHANNEL_FUNCTION)
161 case FUNC_OVERRIDE_CHANNEL:
162 safetyCh[CFN_CH_INDEX(cfn)] = CFN_PARAM(cfn);
163 break;
164 #endif
166 case FUNC_TRAINER:
168 uint8_t param = CFN_CH_INDEX(cfn);
169 if (param == 0)
170 newActiveFunctions |= 0x0F;
171 else if (param <= NUM_STICKS)
172 newActiveFunctions |= (1 << (param - 1));
173 else if (param == NUM_STICKS + 1)
174 newActiveFunctions |= (1u << FUNCTION_TRAINER_CHANNELS);
175 break;
178 case FUNC_INSTANT_TRIM:
179 newActiveFunctions |= (1u << FUNCTION_INSTANT_TRIM);
180 if (!isFunctionActive(FUNCTION_INSTANT_TRIM)) {
181 if (IS_INSTANT_TRIM_ALLOWED()) {
182 instantTrim();
185 break;
187 case FUNC_RESET:
188 switch (CFN_PARAM(cfn)) {
189 case FUNC_RESET_TIMER1:
190 case FUNC_RESET_TIMER2:
191 case FUNC_RESET_TIMER3:
192 timerReset(CFN_PARAM(cfn));
193 break;
194 case FUNC_RESET_FLIGHT:
195 if (!(functionsContext.activeSwitches & switch_mask)) {
196 mainRequestFlags |= (1 << REQUEST_FLIGHT_RESET); // on systems with threads flightReset() must not be called from the mixers thread!
198 break;
199 case FUNC_RESET_TELEMETRY:
200 telemetryReset();
201 break;
203 if (CFN_PARAM(cfn)>=FUNC_RESET_PARAM_FIRST_TELEM) {
204 uint8_t item = CFN_PARAM(cfn)-FUNC_RESET_PARAM_FIRST_TELEM;
205 if (item < MAX_TELEMETRY_SENSORS) {
206 telemetryItems[item].clear();
209 break;
211 case FUNC_SET_TIMER:
212 timerSet(CFN_TIMER_INDEX(cfn), CFN_PARAM(cfn));
213 break;
215 case FUNC_SET_FAILSAFE:
216 setCustomFailsafe(CFN_PARAM(cfn));
217 break;
219 #if defined(DANGEROUS_MODULE_FUNCTIONS)
220 case FUNC_RANGECHECK:
221 case FUNC_BIND:
223 unsigned int moduleIndex = CFN_PARAM(cfn);
224 if (moduleIndex < NUM_MODULES) {
225 moduleState[moduleIndex].mode = 1 + CFN_FUNC(cfn) - FUNC_RANGECHECK;
227 break;
229 #endif
231 #if defined(GVARS)
232 case FUNC_ADJUST_GVAR:
233 if (CFN_GVAR_MODE(cfn) == FUNC_ADJUST_GVAR_CONSTANT) {
234 SET_GVAR(CFN_GVAR_INDEX(cfn), CFN_PARAM(cfn), mixerCurrentFlightMode);
236 else if (CFN_GVAR_MODE(cfn) == FUNC_ADJUST_GVAR_GVAR) {
237 SET_GVAR(CFN_GVAR_INDEX(cfn), GVAR_VALUE(CFN_PARAM(cfn), getGVarFlightMode(mixerCurrentFlightMode, CFN_PARAM(cfn))), mixerCurrentFlightMode);
239 else if (CFN_GVAR_MODE(cfn) == FUNC_ADJUST_GVAR_INCDEC) {
240 if (!(functionsContext.activeSwitches & switch_mask)) {
241 SET_GVAR(CFN_GVAR_INDEX(cfn), limit<int16_t>(MODEL_GVAR_MIN(CFN_GVAR_INDEX(cfn)), GVAR_VALUE(CFN_GVAR_INDEX(cfn), getGVarFlightMode(mixerCurrentFlightMode, CFN_GVAR_INDEX(cfn))) + CFN_PARAM(cfn), MODEL_GVAR_MAX(CFN_GVAR_INDEX(cfn))), mixerCurrentFlightMode);
244 else if (CFN_PARAM(cfn) >= MIXSRC_FIRST_TRIM && CFN_PARAM(cfn) <= MIXSRC_LAST_TRIM) {
245 trimGvar[CFN_PARAM(cfn)-MIXSRC_FIRST_TRIM] = CFN_GVAR_INDEX(cfn);
247 else {
248 SET_GVAR(CFN_GVAR_INDEX(cfn), limit<int16_t>(MODEL_GVAR_MIN(CFN_GVAR_INDEX(cfn)), calcRESXto100(getValue(CFN_PARAM(cfn))), MODEL_GVAR_MAX(CFN_GVAR_INDEX(cfn))), mixerCurrentFlightMode);
250 break;
251 #endif
253 case FUNC_VOLUME:
255 getvalue_t raw = getValue(CFN_PARAM(cfn));
256 // only set volume if input changed more than hysteresis
257 if (abs(requiredSpeakerVolumeRawLast - raw) > VOLUME_HYSTERESIS) {
258 requiredSpeakerVolumeRawLast = raw;
260 requiredSpeakerVolume = ((1024 + requiredSpeakerVolumeRawLast) * VOLUME_LEVEL_MAX) / 2048;
261 break;
264 #if defined(SDCARD)
265 case FUNC_PLAY_SOUND:
266 case FUNC_PLAY_TRACK:
267 case FUNC_PLAY_VALUE:
268 #if defined(HAPTIC)
269 case FUNC_HAPTIC:
270 #endif
272 if (isRepeatDelayElapsed(functions, functionsContext, i)) {
273 if (!IS_PLAYING(PLAY_INDEX)) {
274 if (CFN_FUNC(cfn) == FUNC_PLAY_SOUND) {
275 if (audioQueue.isEmpty()) {
276 AUDIO_PLAY(AU_SPECIAL_SOUND_FIRST + CFN_PARAM(cfn));
279 else if (CFN_FUNC(cfn) == FUNC_PLAY_VALUE) {
280 PLAY_VALUE(CFN_PARAM(cfn), PLAY_INDEX);
282 #if defined(HAPTIC)
283 else if (CFN_FUNC(cfn) == FUNC_HAPTIC) {
284 haptic.event(AU_SPECIAL_SOUND_LAST+CFN_PARAM(cfn));
286 #endif
287 else {
288 playCustomFunctionFile(cfn, PLAY_INDEX);
292 break;
295 case FUNC_BACKGND_MUSIC:
296 if (!(newActiveFunctions & (1 << FUNCTION_BACKGND_MUSIC))) {
297 newActiveFunctions |= (1 << FUNCTION_BACKGND_MUSIC);
298 if (!IS_PLAYING(PLAY_INDEX)) {
299 playCustomFunctionFile(cfn, PLAY_INDEX);
302 break;
304 case FUNC_BACKGND_MUSIC_PAUSE:
305 newActiveFunctions |= (1 << FUNCTION_BACKGND_MUSIC_PAUSE);
306 break;
308 #else
309 case FUNC_PLAY_SOUND:
310 case FUNC_PLAY_TRACK:
311 case FUNC_PLAY_BOTH:
312 case FUNC_PLAY_VALUE:
314 tmr10ms_t tmr10ms = get_tmr10ms();
315 uint8_t repeatParam = CFN_PLAY_REPEAT(cfn);
316 if (!functionsContext.lastFunctionTime[i] || (CFN_FUNC(cfn)==FUNC_PLAY_BOTH && active!=(bool)(functionsContext.activeSwitches&switch_mask)) || (repeatParam && (signed)(tmr10ms-functionsContext.lastFunctionTime[i])>=1000*repeatParam)) {
317 functionsContext.lastFunctionTime[i] = tmr10ms;
318 uint8_t param = CFN_PARAM(cfn);
319 if (CFN_FUNC(cfn) == FUNC_PLAY_SOUND) {
320 AUDIO_PLAY(AU_SPECIAL_SOUND_FIRST+param);
322 else if (CFN_FUNC(cfn) == FUNC_PLAY_VALUE) {
323 PLAY_VALUE(param, PLAY_INDEX);
325 else {
326 #if defined(GVARS)
327 if (CFN_FUNC(cfn) == FUNC_PLAY_TRACK && param > 250)
328 param = GVAR_VALUE(param-251, getGVarFlightMode(mixerCurrentFlightMode, param-251));
329 #endif
330 PUSH_CUSTOM_PROMPT(active ? param : param+1, PLAY_INDEX);
333 if (!active) {
334 // PLAY_BOTH would change activeFnSwitches otherwise
335 switch_mask = 0;
337 break;
339 #endif
341 #if defined(VARIO)
342 case FUNC_VARIO:
343 newActiveFunctions |= (1 << FUNCTION_VARIO);
344 break;
345 #endif
348 #if defined(SDCARD)
349 case FUNC_LOGS:
350 if (CFN_PARAM(cfn)) {
351 newActiveFunctions |= (1 << FUNCTION_LOGS);
352 logDelay = CFN_PARAM(cfn);
354 break;
355 #endif
357 case FUNC_BACKLIGHT:
358 newActiveFunctions |= (1 << FUNCTION_BACKLIGHT);
359 break;
361 case FUNC_SCREENSHOT:
362 if (!(functionsContext.activeSwitches & switch_mask)) {
363 mainRequestFlags |= (1 << REQUEST_SCREENSHOT);
365 break;
367 #if defined(DEBUG)
368 case FUNC_TEST:
369 testFunc();
370 break;
371 #endif
374 newActiveSwitches |= switch_mask;
376 else {
377 functionsContext.lastFunctionTime[i] = 0;
378 #if defined(DANGEROUS_MODULE_FUNCTIONS)
379 if (functionsContext.activeSwitches & switch_mask) {
380 switch (CFN_FUNC(cfn)) {
381 case FUNC_RANGECHECK:
382 case FUNC_BIND:
384 unsigned int moduleIndex = CFN_PARAM(cfn);
385 if (moduleIndex < NUM_MODULES) {
386 moduleState[moduleIndex].mode = 0;
388 break;
392 #endif
397 functionsContext.activeSwitches = newActiveSwitches;
398 functionsContext.activeFunctions = newActiveFunctions;