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.
23 CustomFunctionsContext modelFunctionsContext
= { 0 };
25 CustomFunctionsContext globalFunctionsContext
= { 0 };
29 * This is a test function for debugging purpose, you may insert there your code and compile with the option DEBUG=YES
34 printf("testFunc\n"); fflush(stdout
);
37 // for testing the WD reset uncomment the following line
42 PLAY_FUNCTION(playValue
, source_t idx
)
44 if (IS_FAI_FORBIDDEN(idx
))
47 if (idx
== MIXSRC_NONE
)
50 getvalue_t val
= getValue(idx
);
52 if (idx
>= MIXSRC_FIRST_TELEM
) {
53 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[(idx
-MIXSRC_FIRST_TELEM
) / 3];
55 if (telemetrySensor
.prec
> 0) {
56 if (telemetrySensor
.prec
== 2) {
58 val
= div_and_round(val
, 100);
61 val
= div_and_round(val
, 10);
67 val
= div_and_round(val
, 10);
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
);
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
;
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
;
140 for (uint8_t i
=0; i
<NUM_TRIMS
; i
++) {
145 for (uint8_t i
=0; i
<MAX_SPECIAL_FUNCTIONS
; i
++) {
146 const CustomFunctionData
* cfn
= &functions
[i
];
147 swsrc_t swtch
= CFN_SWITCH(cfn
);
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
);
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
);
168 uint8_t param
= CFN_CH_INDEX(cfn
);
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
);
178 case FUNC_INSTANT_TRIM
:
179 newActiveFunctions
|= (1u << FUNCTION_INSTANT_TRIM
);
180 if (!isFunctionActive(FUNCTION_INSTANT_TRIM
)) {
181 if (IS_INSTANT_TRIM_ALLOWED()) {
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
));
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!
199 case FUNC_RESET_TELEMETRY
:
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();
212 timerSet(CFN_TIMER_INDEX(cfn
), CFN_PARAM(cfn
));
215 case FUNC_SET_FAILSAFE
:
216 setCustomFailsafe(CFN_PARAM(cfn
));
219 #if defined(DANGEROUS_MODULE_FUNCTIONS)
220 case FUNC_RANGECHECK
:
223 unsigned int moduleIndex
= CFN_PARAM(cfn
);
224 if (moduleIndex
< NUM_MODULES
) {
225 moduleState
[moduleIndex
].mode
= 1 + CFN_FUNC(cfn
) - FUNC_RANGECHECK
;
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
);
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
);
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;
265 case FUNC_PLAY_SOUND
:
266 case FUNC_PLAY_TRACK
:
267 case FUNC_PLAY_VALUE
:
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
);
283 else if (CFN_FUNC(cfn
) == FUNC_HAPTIC
) {
284 haptic
.event(AU_SPECIAL_SOUND_LAST
+CFN_PARAM(cfn
));
288 playCustomFunctionFile(cfn
, PLAY_INDEX
);
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
);
304 case FUNC_BACKGND_MUSIC_PAUSE
:
305 newActiveFunctions
|= (1 << FUNCTION_BACKGND_MUSIC_PAUSE
);
309 case FUNC_PLAY_SOUND
:
310 case FUNC_PLAY_TRACK
:
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
);
327 if (CFN_FUNC(cfn
) == FUNC_PLAY_TRACK
&& param
> 250)
328 param
= GVAR_VALUE(param
-251, getGVarFlightMode(mixerCurrentFlightMode
, param
-251));
330 PUSH_CUSTOM_PROMPT(active
? param
: param
+1, PLAY_INDEX
);
334 // PLAY_BOTH would change activeFnSwitches otherwise
343 newActiveFunctions
|= (1 << FUNCTION_VARIO
);
350 if (CFN_PARAM(cfn
)) {
351 newActiveFunctions
|= (1 << FUNCTION_LOGS
);
352 logDelay
= CFN_PARAM(cfn
);
358 newActiveFunctions
|= (1 << FUNCTION_BACKLIGHT
);
361 case FUNC_SCREENSHOT
:
362 if (!(functionsContext
.activeSwitches
& switch_mask
)) {
363 mainRequestFlags
|= (1 << REQUEST_SCREENSHOT
);
374 newActiveSwitches
|= switch_mask
;
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
:
384 unsigned int moduleIndex
= CFN_PARAM(cfn
);
385 if (moduleIndex
< NUM_MODULES
) {
386 moduleState
[moduleIndex
].mode
= 0;
397 functionsContext
.activeSwitches
= newActiveSwitches
;
398 functionsContext
.activeFunctions
= newActiveFunctions
;