protoflight 0.1.2

Protoflight flight controller.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
#[cfg(feature = "serde")]
use {
    sequential_storage::map::PostcardValue,
    serde::{Deserialize, Serialize},
};

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PidConfig {
    pub kp: u8, // proportional gain
    pub ki: u8, // integral gain
    pub kd: u8, // derivative gain
    pub ks: u8, // setpoint gain
    pub kk: u8, // setpoint derivative gain ('kick')
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for PidConfig {}

impl PidConfig {
    pub const fn new() -> Self {
        Self { kp: 0, ki: 0, kd: 0, ks: 0, kk: 0 }
    }
    pub const fn new5(kp: u8, ki: u8, kd: u8, ks: u8, kk: u8) -> Self {
        Self { kp, ki, kd, ks, kk }
    }
}

impl Default for PidConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[allow(unused)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PidConfigs {
    pub roll_rate: PidConfig,
    pub pitch_rate: PidConfig,
    pub yaw_rate: PidConfig,
    pub roll_angle: PidConfig,
    pub pitch_angle: PidConfig,
}

impl PidConfigs {
    pub const fn new() -> Self {
        Self {
            // Betaflight compatible defaults.
            roll_rate: PidConfig::new5(45, 80, 30, 120, 0),
            pitch_rate: PidConfig::new5(47, 84, 34, 125, 0),
            yaw_rate: PidConfig::new5(45, 80, 0, 120, 0),
            roll_angle: PidConfig::new5(50, 75, 75, 50, 0),
            pitch_angle: PidConfig::new5(50, 75, 75, 50, 0),
        }
    }
}

impl Default for PidConfigs {
    fn default() -> Self {
        Self::new()
    }
}

/// Configuration data for the flight controller filters.
/// These the dterm filters, the output filters, and the RC smoothing filters.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FlightControllerFiltersConfig {
    //enum { PT1 : 0, BIQUAD, PT2, PT3 }
    pub dterm_lpf1_hz: u16,
    pub dterm_lpf2_hz: u16,
    //#if defined(USE_DTERM_FILTERS_EXTENDED)
    pub dterm_notch_hz: u16,
    pub dterm_notch_cutoff: u16,
    pub dterm_dynamic_lpf1_min_hz: u16,
    pub dterm_dynamic_lpf1_max_hz: u16,
    pub dterm_lpf1_type: u8,
    pub dterm_lpf2_type: u8,
    //#endif
    pub yaw_lpf_hz: u16,
    pub output_lpf_hz: u16,
    pub rc_smoothing_feedforward_cutoff: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for FlightControllerFiltersConfig {}

impl FlightControllerFiltersConfig {
    pub const PT1: u8 = 0;
    pub const _BIQUAD: u8 = 1;
    pub const _PT2: u8 = 2;
    pub const _PT3: u8 = 3;

    pub const fn new() -> Self {
        Self {
            dterm_lpf1_hz: 75,
            dterm_lpf2_hz: 150,
            dterm_notch_hz: 0,
            dterm_notch_cutoff: 160,
            dterm_dynamic_lpf1_min_hz: 75,
            dterm_dynamic_lpf1_max_hz: 150,
            dterm_lpf1_type: Self::PT1,
            dterm_lpf2_type: Self::PT1,
            yaw_lpf_hz: 100,
            output_lpf_hz: 500,
            rc_smoothing_feedforward_cutoff: 0,
        }
    }
}

impl Default for FlightControllerFiltersConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FlightModeConfig {
    pub level_race_mode: u8, // aka "NFE(not fast enough) race mode": angle mode on roll, acro mode on pitch
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for FlightModeConfig {}

impl FlightModeConfig {
    pub const fn new() -> Self {
        Self { level_race_mode: 0 }
    }
}

impl Default for FlightModeConfig {
    fn default() -> Self {
        Self::new()
    }
}

/// Configuration data for Throttle PID Attenuation (TPA),
/// Allows dynamic adjustment of the PID gains according to the throttle value.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TpaConfig {
    pub mode: u8,
    pub rate: u8,
    pub breakpoint: u16,
    pub low_rate: i8,
    pub low_always: u8,
    pub low_breakpoint: u16,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for TpaConfig {}

impl TpaConfig {
    pub const _TPA_MODE_P: u8 = 0;
    pub const TPA_MODE_D: u8 = 1;
    pub const _TPA_MODE_PDS: u8 = 2;

    pub const fn new() -> Self {
        Self { mode: Self::TPA_MODE_D, rate: 65, breakpoint: 1350, low_rate: 20, low_always: 0, low_breakpoint: 1050 }
    }
}

impl Default for TpaConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AntiGravityConfig {
    pub cutoff_hz: u8,
    pub p_gain: u8,
    pub i_gain: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for AntiGravityConfig {}

impl AntiGravityConfig {
    pub const fn new() -> Self {
        Self { cutoff_hz: 5, p_gain: 100, i_gain: 80 }
    }
}

impl Default for AntiGravityConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CrashFlipConfig {
    pub motor_percent: u8,
    pub rate: u8,
    pub auto_rearm: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for CrashFlipConfig {}

impl CrashFlipConfig {
    pub const fn new() -> Self {
        Self { motor_percent: 0, rate: 0, auto_rearm: 0 }
    }
}

impl Default for CrashFlipConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct YawSpinRecoveryConfig {
    pub yaw_spin_threshold: i16,
    pub yaw_spin_recovery: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for YawSpinRecoveryConfig {}

impl YawSpinRecoveryConfig {
    pub const RECOVERY_OFF: u8 = 0;
    pub const _RECOVERY_ON: u8 = 1;
    pub const _RECOVERY_AUTO: u8 = 2;

    pub const fn new() -> Self {
        Self { yaw_spin_threshold: 0, yaw_spin_recovery: Self::RECOVERY_OFF }
    }
}

impl Default for YawSpinRecoveryConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CrashRecoveryConfig {
    pub d_threshold: u16,        // dterm crash value
    pub g_threshold: u16,        // gyro crash value
    pub setpoint_threshold: u16, // setpoint must be below this value to detect crash, so flips and rolls are not interpreted as crashes
    pub time: u16,               // ms
    pub delay: u16,              // ms
    pub limit_yaw: u16,          // limits yaw error rate, so crashes don't cause huge throttle increase
    pub recovery_angle: u8,      // degrees
    pub recovery_rate: u8,       // degrees per second
    pub recovery: u8,            // off, on, on and beeps when it is in crash recovery mode
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for CrashRecoveryConfig {}

impl CrashRecoveryConfig {
    pub const fn new() -> Self {
        Self {
            d_threshold: 50,         // dterm crash value
            g_threshold: 400,        // gyro crash value
            setpoint_threshold: 350, // setpoint must be below this value to detect crash, so flips and rolls are not interpreted as crashes
            time: 500,               // ms
            delay: 0,                // ms
            limit_yaw: 200,          // limits yaw error rate, so crashes don't cause huge throttle increase
            recovery_angle: 10,      // degrees
            recovery_rate: 100,      // degrees per second
            recovery: 0,             // off, on, on and beeps when it is in crash recovery mode
        }
    }
}

impl Default for CrashRecoveryConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ItermRelaxConfig {
    pub relax_type: u8,                   // not used
    pub relax: u8,                        // Enable iterm suppression during stick input
    pub relax_setpoint_threshold_dps: u8, // Full iterm suppression once setpoint has exceeded this value (degrees per second)
    pub relax_cutoff: u8, // Cutoff frequency used by low pass filter which predicts average response of the quad to setpoint
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for ItermRelaxConfig {}

impl ItermRelaxConfig {
    #[allow(unused)]
    pub const RELAX_OFF: u8 = 0;
    pub const RELAX_ON: u8 = 1;
    pub const fn new() -> Self {
        Self { relax_type: 0, relax: Self::RELAX_ON, relax_setpoint_threshold_dps: 40, relax_cutoff: 15 }
    }
}

impl Default for ItermRelaxConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DMaxConfig {
    pub d_max: [u8; 2], // Maximum D value on each axis
    pub gain: u8,       // gain factor for amount of gyro / setpoint activity required to boost D
    pub advance: u8,    // percentage multiplier for setpoint
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for DMaxConfig {}

impl DMaxConfig {
    pub const fn new() -> Self {
        Self { d_max: [0u8; 2], gain: 0, advance: 0 }
    }
}

impl Default for DMaxConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GyroConfig {
    pub gyro_movement_calibration_threshold: u8, // people keep forgetting that moving model while init results in wrong gyro offsets. and then they never reset gyro. so this is now on by default.
    pub gyro_hardware_lpf: u8,                   // gyro DLPF setting
    pub gyro_high_fsr: u8,

    pub gyro_lpf1_static_hz: u16,
    pub gyro_lpf2_static_hz: u16,

    pub gyro_soft_notch_hz_1: u16,
    pub gyro_soft_notch_cutoff_1: u16,
    pub gyro_soft_notch_hz_2: u16,
    pub gyro_soft_notch_cutoff_2: u16,
    pub gyro_offset_yaw: i16,
    pub check_overflow: u8,

    // Lowpass primary/secondary
    pub gyro_lpf1_type: u8,
    pub gyro_lpf2_type: u8,

    pub yaw_spin_recovery: u8,
    pub yaw_spin_threshold: i16,

    pub gyro_calibration_duration: u16, // Gyro calibration duration in 1/100 second

    pub gyro_lpf1_dyn_min_hz: u16,
    pub gyro_lpf1_dyn_max_hz: u16,

    pub gyro_filter_debug_axis: u8,

    pub gyros_detected: u8,     // What gyros should be shown as part of the cli status command.
    pub gyro_lpf1_dyn_expo: u8, // set the curve for dynamic gyro lowpass filter
    pub simplified_gyro_filter: u8,
    pub simplified_gyro_filter_multiplier: u8,

    pub gyro_enabled_bitmask: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for GyroConfig {}

impl GyroConfig {
    pub const fn new() -> Self {
        Self {
            gyro_movement_calibration_threshold: 0, // people keep forgetting that moving model while init results in wrong gyro offsets. and then they never reset gyro. so this is now on by default.
            gyro_hardware_lpf: 0,                   // gyro DLPF setting
            gyro_high_fsr: 0,

            gyro_lpf1_static_hz: 0,
            gyro_lpf2_static_hz: 0,

            gyro_soft_notch_hz_1: 0,
            gyro_soft_notch_cutoff_1: 0,
            gyro_soft_notch_hz_2: 0,
            gyro_soft_notch_cutoff_2: 0,
            gyro_offset_yaw: 0,
            check_overflow: 0,

            // Lowpass primary/secondary
            gyro_lpf1_type: 0,
            gyro_lpf2_type: 0,

            yaw_spin_recovery: 0,
            yaw_spin_threshold: 0,

            gyro_calibration_duration: 0, // Gyro calibration duration in 1/100 second

            gyro_lpf1_dyn_min_hz: 0,
            gyro_lpf1_dyn_max_hz: 0,

            gyro_filter_debug_axis: 0,

            gyros_detected: 0,     // What gyros should be shown as part of the cli status command.
            gyro_lpf1_dyn_expo: 0, // set the curve for dynamic gyro lowpass filter
            simplified_gyro_filter: 0,
            simplified_gyro_filter_multiplier: 0,

            gyro_enabled_bitmask: 0,
        }
    }
}

impl Default for GyroConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn _is_normal<T: Sized + Send + Sync + Unpin>() {}
    fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
    #[cfg(feature = "serde")]
    fn is_config<T: Serialize + for<'a> Deserialize<'a> + for<'a> PostcardValue<'a>>() {}

    #[test]
    fn normal_types() {
        is_full::<PidConfig>();
        is_full::<FlightControllerFiltersConfig>();
        is_full::<FlightModeConfig>();
        is_full::<TpaConfig>();
        is_full::<CrashFlipConfig>();
        is_full::<AntiGravityConfig>();
        is_full::<CrashFlipConfig>();
        is_full::<YawSpinRecoveryConfig>();
        is_full::<CrashRecoveryConfig>();
        is_full::<ItermRelaxConfig>();
        is_full::<DMaxConfig>();

        #[cfg(feature = "serde")]
        is_config::<PidConfig>();
        #[cfg(feature = "serde")]
        is_config::<FlightControllerFiltersConfig>();
        #[cfg(feature = "serde")]
        is_config::<FlightModeConfig>();
        #[cfg(feature = "serde")]
        is_config::<TpaConfig>();
        #[cfg(feature = "serde")]
        is_config::<CrashFlipConfig>();
        #[cfg(feature = "serde")]
        is_config::<AntiGravityConfig>();
        #[cfg(feature = "serde")]
        is_config::<CrashFlipConfig>();
        #[cfg(feature = "serde")]
        is_config::<YawSpinRecoveryConfig>();
        #[cfg(feature = "serde")]
        is_config::<CrashRecoveryConfig>();
        #[cfg(feature = "serde")]
        is_config::<ItermRelaxConfig>();
        #[cfg(feature = "serde")]
        is_config::<DMaxConfig>();
    }
    #[test]
    fn test_new() {
        let config = FlightControllerFiltersConfig::new();
        assert_eq!(75, config.dterm_lpf1_hz);
    }
}