411399d72faf3ec1650b0d015a8d54cb2aa28eb6
[fw/altos] / src / kernel / ao_config.c
1 /*
2  * Copyright © 2009 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include "ao.h"
19 #include "ao_log.h"
20 #include <ao_config.h>
21 #if HAS_FLIGHT
22 #include <ao_sample.h>
23 #include <ao_data.h>
24 #endif
25 #if HAS_BEEP
26 #include <ao_beep.h>
27 #endif
28
29 __xdata struct ao_config ao_config;
30 __pdata uint8_t ao_config_loaded;
31 __pdata uint8_t ao_config_dirty;
32 __xdata uint8_t ao_config_mutex;
33
34 #ifndef AO_CONFIG_DEFAULT_APRS_INTERVAL
35 #define AO_CONFIG_DEFAULT_APRS_INTERVAL 0
36 #endif
37 #define AO_CONFIG_DEFAULT_MAIN_DEPLOY   250
38 #define AO_CONFIG_DEFAULT_RADIO_CHANNEL 0
39 #define AO_CONFIG_DEFAULT_CALLSIGN      "N0CALL"
40 #define AO_CONFIG_DEFAULT_ACCEL_ZERO_G  16000
41 #define AO_CONFIG_DEFAULT_APOGEE_DELAY  0
42 #define AO_CONFIG_DEFAULT_IGNITE_MODE   AO_IGNITE_MODE_DUAL
43 #define AO_CONFIG_DEFAULT_PAD_ORIENTATION       AO_PAD_ORIENTATION_ANTENNA_UP
44 #if HAS_EEPROM
45 #ifndef USE_INTERNAL_FLASH
46 #error Please define USE_INTERNAL_FLASH
47 #endif
48 #endif
49 #ifndef AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX
50 #if USE_INTERNAL_FLASH
51 #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX        ao_storage_config
52 #else
53 #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX        ((uint32_t) 192 * (uint32_t) 1024)
54 #endif
55 #endif
56 #ifndef AO_CONFIG_DEFAULT_RADIO_POWER
57 #define AO_CONFIG_DEFAULT_RADIO_POWER           0x60
58 #endif
59 #define AO_CONFIG_DEFAULT_RADIO_AMP             0
60
61 #if HAS_EEPROM
62 static void
63 _ao_config_put(void)
64 {
65         ao_config_setup();
66         ao_config_erase();
67         ao_config_write(0, &ao_config, sizeof (ao_config));
68 #if HAS_FLIGHT
69         ao_log_write_erase(0);
70 #endif
71         ao_config_flush();
72 }
73
74 void
75 ao_config_put(void)
76 {
77         ao_mutex_get(&ao_config_mutex);
78         _ao_config_put();
79         ao_mutex_put(&ao_config_mutex);
80 }
81 #endif
82
83 #if HAS_RADIO
84 void
85 ao_config_set_radio(void)
86 {
87         ao_config.radio_setting = ao_freq_to_set(ao_config.frequency, ao_config.radio_cal);
88 }
89 #endif /* HAS_RADIO */
90
91 static void
92 _ao_config_get(void)
93 {
94         uint8_t minor;
95
96         if (ao_config_loaded)
97                 return;
98 #if HAS_EEPROM
99         /* Yes, I know ao_storage_read calls ao_storage_setup,
100          * but ao_storage_setup *also* sets ao_storage_config, which we
101          * need before calling ao_storage_read here
102          */
103         ao_config_setup();
104         ao_config_read(0, &ao_config, sizeof (ao_config));
105 #endif
106         if (ao_config.major != AO_CONFIG_MAJOR) {
107                 ao_config.major = AO_CONFIG_MAJOR;
108                 ao_config.minor = 0;
109
110                 /* Version 0 stuff */
111                 ao_config.main_deploy = AO_CONFIG_DEFAULT_MAIN_DEPLOY;
112                 ao_xmemset(&ao_config.callsign, '\0', sizeof (ao_config.callsign));
113                 ao_xmemcpy(&ao_config.callsign, CODE_TO_XDATA(AO_CONFIG_DEFAULT_CALLSIGN),
114                        sizeof(AO_CONFIG_DEFAULT_CALLSIGN) - 1);
115                 ao_config._legacy_radio_channel = 0;
116         }
117         minor = ao_config.minor;
118         if (minor != AO_CONFIG_MINOR) {
119                 /* Fixups for minor version 1 */
120                 if (minor < 1)
121                         ao_config.apogee_delay = AO_CONFIG_DEFAULT_APOGEE_DELAY;
122                 /* Fixups for minor version 2 */
123                 if (minor < 2) {
124                         ao_config.accel_plus_g = 0;
125                         ao_config.accel_minus_g = 0;
126                 }
127                 /* Fixups for minor version 3 */
128 #if HAS_RADIO
129                 if (minor < 3)
130                         ao_config.radio_cal = ao_radio_cal;
131 #endif
132                 /* Fixups for minor version 4 */
133 #if HAS_FLIGHT
134                 if (minor < 4)
135                         ao_config.flight_log_max = AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX;
136 #endif
137                 /* Fixupes for minor version 5 */
138                 if (minor < 5)
139                         ao_config.ignite_mode = AO_CONFIG_DEFAULT_IGNITE_MODE;
140                 if (minor < 6)
141                         ao_config.pad_orientation = AO_CONFIG_DEFAULT_PAD_ORIENTATION;
142                 if (minor < 8)
143                         ao_config.radio_enable = AO_RADIO_ENABLE_CORE;
144                 if (minor < 9)
145                         ao_xmemset(&ao_config.aes_key, '\0', AO_AES_LEN);
146                 if (minor < 10)
147                         ao_config.frequency = 434550 + ao_config._legacy_radio_channel * 100;
148                 if (minor < 11)
149                         ao_config.apogee_lockout = 0;
150 #if AO_PYRO_NUM
151                 if (minor < 12)
152                         memset(&ao_config.pyro, '\0', sizeof (ao_config.pyro));
153 #endif
154                 if (minor < 13)
155                         ao_config.aprs_interval = AO_CONFIG_DEFAULT_APRS_INTERVAL;
156 #if HAS_RADIO_POWER
157                 if (minor < 14)
158                         ao_config.radio_power = AO_CONFIG_DEFAULT_RADIO_POWER;
159                 #endif
160 #if HAS_RADIO_AMP
161                 if (minor  < 14)
162                         ao_config.radio_amp = AO_CONFIG_DEFAULT_RADIO_AMP;
163 #endif
164 #if HAS_GYRO
165                 if (minor < 15) {
166                         ao_config.accel_zero_along = 0;
167                         ao_config.accel_zero_across = 0;
168                         ao_config.accel_zero_through = 0;
169
170                         /* Reset the main accel offsets to force
171                          * re-calibration
172                          */
173                         ao_config.accel_plus_g = 0;
174                         ao_config.accel_minus_g = 0;
175                 }
176 #endif
177 #if HAS_BEEP_CONFIG
178                 if (minor < 16)
179                         ao_config.mid_beep = AO_BEEP_MID_DEFAULT;
180 #endif
181                 ao_config.minor = AO_CONFIG_MINOR;
182                 ao_config_dirty = 1;
183         }
184 #if HAS_RADIO
185 #if HAS_FORCE_FREQ
186         if (ao_force_freq) {
187                 ao_config.frequency = 434550;
188                 ao_config.radio_cal = ao_radio_cal;
189                 ao_xmemcpy(&ao_config.callsign, CODE_TO_XDATA(AO_CONFIG_DEFAULT_CALLSIGN),
190                        sizeof(AO_CONFIG_DEFAULT_CALLSIGN) - 1);
191         }
192 #endif
193         ao_config_set_radio();
194 #endif
195         ao_config_loaded = 1;
196 }
197
198 void
199 _ao_config_edit_start(void)
200 {
201         ao_mutex_get(&ao_config_mutex);
202         _ao_config_get();
203 }
204
205 void
206 _ao_config_edit_finish(void)
207 {
208         ao_config_dirty = 1;
209         ao_mutex_put(&ao_config_mutex);
210 }
211
212 void
213 ao_config_get(void)
214 {
215         _ao_config_edit_start();
216         ao_mutex_put(&ao_config_mutex);
217 }
218
219 void
220 ao_config_callsign_show(void)
221 {
222         printf ("Callsign: \"%s\"\n", ao_config.callsign);
223 }
224
225 void
226 ao_config_callsign_set(void) __reentrant
227 {
228         uint8_t c;
229         static __xdata char callsign[AO_MAX_CALLSIGN + 1];
230
231         ao_xmemset(callsign, '\0', sizeof callsign);
232         ao_cmd_white();
233         c = 0;
234         while (ao_cmd_lex_c != '\n') {
235                 if (c < AO_MAX_CALLSIGN)
236                         callsign[c++] = ao_cmd_lex_c;
237                 else
238                         ao_cmd_status = ao_cmd_lex_error;
239                 ao_cmd_lex();
240         }
241         if (ao_cmd_status != ao_cmd_success)
242                 return;
243         _ao_config_edit_start();
244         ao_xmemcpy(&ao_config.callsign, &callsign,
245                AO_MAX_CALLSIGN + 1);
246         _ao_config_edit_finish();
247 }
248
249 #if HAS_RADIO
250
251 void
252 ao_config_frequency_show(void) __reentrant
253 {
254         printf("Frequency: %ld\n",
255                ao_config.frequency);
256 }
257
258 void
259 ao_config_frequency_set(void) __reentrant
260 {
261         ao_cmd_decimal();
262         if (ao_cmd_status != ao_cmd_success)
263                 return;
264         _ao_config_edit_start();
265         ao_config.frequency = ao_cmd_lex_u32;
266         ao_config_set_radio();
267         _ao_config_edit_finish();
268 #if HAS_RADIO_RECV
269         ao_radio_recv_abort();
270 #endif
271 }
272 #endif
273
274 #if HAS_FLIGHT
275
276 void
277 ao_config_main_deploy_show(void) __reentrant
278 {
279         printf("Main deploy: %d meters\n",
280                ao_config.main_deploy);
281 }
282
283 void
284 ao_config_main_deploy_set(void) __reentrant
285 {
286         ao_cmd_decimal();
287         if (ao_cmd_status != ao_cmd_success)
288                 return;
289         _ao_config_edit_start();
290         ao_config.main_deploy = ao_cmd_lex_i;
291         _ao_config_edit_finish();
292 }
293
294 #if HAS_ACCEL
295 void
296 ao_config_accel_calibrate_show(void) __reentrant
297 {
298         printf("Accel cal +1g: %d -1g: %d\n",
299                ao_config.accel_plus_g, ao_config.accel_minus_g);
300 #if HAS_GYRO
301         printf ("IMU cal along %d across %d through %d\n",
302                 ao_config.accel_zero_along,
303                 ao_config.accel_zero_across,
304                 ao_config.accel_zero_through);
305 #endif
306 }
307
308 #define ACCEL_CALIBRATE_SAMPLES 1024
309 #define ACCEL_CALIBRATE_SHIFT   10
310
311 #if HAS_GYRO
312 static int16_t accel_cal_along;
313 static int16_t accel_cal_across;
314 static int16_t accel_cal_through;
315 #endif
316
317 static int16_t
318 ao_config_accel_calibrate_auto(char *orientation) __reentrant
319 {
320         uint16_t        i;
321         int32_t         accel_total;
322         uint8_t         cal_data_ring;
323 #if HAS_GYRO
324         int32_t         accel_along_total = 0;
325         int32_t         accel_across_total = 0;
326         int32_t         accel_through_total = 0;
327 #endif
328
329         printf("Orient antenna %s and press a key...", orientation);
330         flush();
331         (void) getchar();
332         puts("\r\n"); flush();
333         puts("Calibrating..."); flush();
334         i = ACCEL_CALIBRATE_SAMPLES;
335         accel_total = 0;
336         cal_data_ring = ao_sample_data;
337         while (i) {
338                 ao_sleep(DATA_TO_XDATA(&ao_sample_data));
339                 while (i && cal_data_ring != ao_sample_data) {
340                         accel_total += (int32_t) ao_data_accel(&ao_data_ring[cal_data_ring]);
341 #if HAS_GYRO
342                         accel_along_total += (int32_t) ao_data_along(&ao_data_ring[cal_data_ring]);
343                         accel_across_total += (int32_t) ao_data_across(&ao_data_ring[cal_data_ring]);
344                         accel_through_total += (int32_t) ao_data_through(&ao_data_ring[cal_data_ring]);
345 #endif
346                         cal_data_ring = ao_data_ring_next(cal_data_ring);
347                         i--;
348                 }
349         }
350 #if HAS_GYRO
351         accel_cal_along = accel_along_total >> ACCEL_CALIBRATE_SHIFT;
352         accel_cal_across = accel_across_total >> ACCEL_CALIBRATE_SHIFT;
353         accel_cal_through = accel_through_total >> ACCEL_CALIBRATE_SHIFT;
354 #endif
355         return accel_total >> ACCEL_CALIBRATE_SHIFT;
356 }
357
358 void
359 ao_config_accel_calibrate_set(void) __reentrant
360 {
361         int16_t up, down;
362 #if HAS_GYRO
363         int16_t accel_along_up = 0, accel_along_down = 0;
364         int16_t accel_across_up = 0, accel_across_down = 0;
365         int16_t accel_through_up = 0, accel_through_down = 0;
366 #endif
367
368         ao_cmd_decimal();
369         if (ao_cmd_status != ao_cmd_success)
370                 return;
371         if (ao_cmd_lex_i == 0) {
372                 up = ao_config_accel_calibrate_auto("up");
373 #if HAS_GYRO
374                 accel_along_up = accel_cal_along;
375                 accel_across_up = accel_cal_across;
376                 accel_through_up = accel_cal_through;
377 #endif
378                 down = ao_config_accel_calibrate_auto("down");
379 #if HAS_GYRO
380                 accel_along_down = accel_cal_along;
381                 accel_across_down = accel_cal_across;
382                 accel_through_down = accel_cal_through;
383 #endif
384         } else {
385                 up = ao_cmd_lex_i;
386                 ao_cmd_decimal();
387                 if (ao_cmd_status != ao_cmd_success)
388                         return;
389                 down = ao_cmd_lex_i;
390         }
391         if (up >= down) {
392                 printf("Invalid accel: up (%d) down (%d)\n",
393                        up, down);
394                 return;
395         }
396         _ao_config_edit_start();
397         ao_config.accel_plus_g = up;
398         ao_config.accel_minus_g = down;
399 #if HAS_GYRO
400         if (ao_cmd_lex_i == 0) {
401                 ao_config.accel_zero_along = (accel_along_up + accel_along_down) / 2;
402                 ao_config.accel_zero_across = (accel_across_up + accel_across_down) / 2;
403                 ao_config.accel_zero_through = (accel_through_up + accel_through_down) / 2;
404         }
405 #endif
406         _ao_config_edit_finish();
407 }
408 #endif /* HAS_ACCEL */
409
410 void
411 ao_config_apogee_delay_show(void) __reentrant
412 {
413         printf("Apogee delay: %d seconds\n",
414                ao_config.apogee_delay);
415 }
416
417 void
418 ao_config_apogee_delay_set(void) __reentrant
419 {
420         ao_cmd_decimal();
421         if (ao_cmd_status != ao_cmd_success)
422                 return;
423         _ao_config_edit_start();
424         ao_config.apogee_delay = ao_cmd_lex_i;
425         _ao_config_edit_finish();
426 }
427
428 void
429 ao_config_apogee_lockout_show(void) __reentrant
430 {
431         printf ("Apogee lockout: %d seconds\n",
432                 ao_config.apogee_lockout);
433 }
434
435 void
436 ao_config_apogee_lockout_set(void) __reentrant
437 {
438         ao_cmd_decimal();
439         if (ao_cmd_status != ao_cmd_success)
440                 return;
441         _ao_config_edit_start();
442         ao_config.apogee_lockout = ao_cmd_lex_i;
443         _ao_config_edit_finish();
444 }
445
446 #endif /* HAS_FLIGHT */
447
448 #if HAS_RADIO
449 void
450 ao_config_radio_cal_show(void) __reentrant
451 {
452         printf("Radio cal: %ld\n", ao_config.radio_cal);
453 }
454
455 void
456 ao_config_radio_cal_set(void) __reentrant
457 {
458         ao_cmd_decimal();
459         if (ao_cmd_status != ao_cmd_success)
460                 return;
461         _ao_config_edit_start();
462         ao_config.radio_cal = ao_cmd_lex_u32;
463         ao_config_set_radio();
464         _ao_config_edit_finish();
465 }
466 #endif
467
468 #if HAS_LOG
469 void
470 ao_config_log_show(void) __reentrant
471 {
472         printf("Max flight log: %d kB\n", (int16_t) (ao_config.flight_log_max >> 10));
473 }
474
475 void
476 ao_config_log_set(void) __reentrant
477 {
478         uint16_t        block = (uint16_t) (ao_storage_block >> 10);
479         uint16_t        log_max = (uint16_t) (ao_storage_log_max >> 10);
480
481         ao_cmd_decimal();
482         if (ao_cmd_status != ao_cmd_success)
483                 return;
484         if (ao_log_present())
485                 printf("Storage must be empty before changing log size\n");
486         else if (block > 1024 && (ao_cmd_lex_i & (block - 1)))
487                 printf("Flight log size must be multiple of %d kB\n", block);
488         else if (ao_cmd_lex_i > log_max)
489                 printf("Flight log max %d kB\n", log_max);
490         else {
491                 _ao_config_edit_start();
492                 ao_config.flight_log_max = (uint32_t) ao_cmd_lex_i << 10;
493                 _ao_config_edit_finish();
494         }
495 }
496 #endif /* HAS_LOG */
497
498 #if HAS_IGNITE
499 void
500 ao_config_ignite_mode_show(void) __reentrant
501 {
502         printf("Ignite mode: %d\n", ao_config.ignite_mode);
503 }
504
505 void
506 ao_config_ignite_mode_set(void) __reentrant
507 {
508         ao_cmd_decimal();
509         if (ao_cmd_status != ao_cmd_success)
510                 return;
511         _ao_config_edit_start();
512         ao_config.ignite_mode = ao_cmd_lex_i;
513         _ao_config_edit_finish();
514 }
515 #endif
516
517 #if HAS_ACCEL
518 void
519 ao_config_pad_orientation_show(void) __reentrant
520 {
521         printf("Pad orientation: %d\n", ao_config.pad_orientation);
522 }
523
524 #ifndef AO_ACCEL_INVERT
525 #define AO_ACCEL_INVERT 0x7fff
526 #endif
527
528 void
529 ao_config_pad_orientation_set(void) __reentrant
530 {
531         ao_cmd_decimal();
532         if (ao_cmd_status != ao_cmd_success)
533                 return;
534         _ao_config_edit_start();
535         ao_cmd_lex_i &= 1;
536         if (ao_config.pad_orientation != ao_cmd_lex_i) {
537                 int16_t t;
538                 t = ao_config.accel_plus_g;
539                 ao_config.accel_plus_g = AO_ACCEL_INVERT - ao_config.accel_minus_g;
540                 ao_config.accel_minus_g = AO_ACCEL_INVERT - t;
541         }
542         ao_config.pad_orientation = ao_cmd_lex_i;
543         _ao_config_edit_finish();
544 }
545 #endif
546
547 #if HAS_RADIO
548 void
549 ao_config_radio_enable_show(void) __reentrant
550 {
551         printf("Radio enable: %d\n", ao_config.radio_enable);
552 }
553
554 void
555 ao_config_radio_enable_set(void) __reentrant
556 {
557         ao_cmd_decimal();
558         if (ao_cmd_status != ao_cmd_success)
559                 return;
560         _ao_config_edit_start();
561         ao_config.radio_enable = ao_cmd_lex_i;
562         _ao_config_edit_finish();
563 }
564 #endif /* HAS_RADIO */
565
566 #if HAS_AES
567
568 __xdata uint8_t ao_config_aes_seq = 1;
569
570 void
571 ao_config_key_show(void) __reentrant
572 {
573         uint8_t i;
574         printf("AES key: ");
575         for (i = 0; i < AO_AES_LEN; i++)
576                 printf ("%02x", ao_config.aes_key[i]);
577         printf("\n");
578 }
579
580 void
581 ao_config_key_set(void) __reentrant
582 {
583         uint8_t i;
584
585         _ao_config_edit_start();
586         for (i = 0; i < AO_AES_LEN; i++) {
587                 ao_cmd_hexbyte();
588                 if (ao_cmd_status != ao_cmd_success)
589                         break;
590                 ao_config.aes_key[i] = ao_cmd_lex_i;
591         }
592         ++ao_config_aes_seq;
593         _ao_config_edit_finish();
594 }
595 #endif
596
597 #if HAS_APRS
598
599 void
600 ao_config_aprs_show(void)
601 {
602         printf ("APRS interval: %d\n", ao_config.aprs_interval);
603 }
604
605 void
606 ao_config_aprs_set(void)
607 {
608         ao_cmd_decimal();
609         if (ao_cmd_status != ao_cmd_success)
610                 return;
611         _ao_config_edit_start();
612         ao_config.aprs_interval = ao_cmd_lex_i;
613         _ao_config_edit_finish();
614 }
615
616 #endif /* HAS_APRS */
617
618 #if HAS_RADIO_AMP
619
620 void
621 ao_config_radio_amp_show(void)
622 {
623         printf ("Radio amp setting: %d\n", ao_config.radio_amp);
624 }
625
626 void
627 ao_config_radio_amp_set(void)
628 {
629         ao_cmd_decimal();
630         if (ao_cmd_status != ao_cmd_success)
631                 return;
632         _ao_config_edit_start();
633         ao_config.radio_amp = ao_cmd_lex_i;
634         _ao_config_edit_finish();
635 }
636
637 #endif
638
639 #if HAS_RADIO_POWER
640
641 void
642 ao_config_radio_power_show(void)
643 {
644         printf ("Radio power setting: %d\n", ao_config.radio_power);
645 }
646
647 void
648 ao_config_radio_power_set(void)
649 {
650         ao_cmd_decimal();
651         if (ao_cmd_status != ao_cmd_success)
652                 return;
653         _ao_config_edit_start();
654         ao_config.radio_power = ao_cmd_lex_i;
655         _ao_config_edit_finish();
656 }
657
658 #endif
659
660 #if HAS_BEEP_CONFIG
661 void
662 ao_config_beep_show(void)
663 {
664         printf ("Beeper setting: %d\n", ao_config.mid_beep);
665 }
666
667 void
668 ao_config_beep_set(void)
669 {
670         ao_cmd_decimal();
671         if (ao_cmd_status != ao_cmd_success)
672                 return;
673         _ao_config_edit_start();
674         ao_config.mid_beep = ao_cmd_lex_i;
675         _ao_config_edit_finish();
676 }
677 #endif
678
679
680 struct ao_config_var {
681         __code char     *str;
682         void            (*set)(void) __reentrant;
683         void            (*show)(void) __reentrant;
684 };
685
686 static void
687 ao_config_help(void) __reentrant;
688
689 static void
690 ao_config_show(void) __reentrant;
691
692 #if HAS_EEPROM
693 static void
694 ao_config_save(void) __reentrant;
695 #endif
696
697 __code struct ao_config_var ao_config_vars[] = {
698 #if HAS_FLIGHT
699         { "m <meters>\0Main deploy (m)",
700           ao_config_main_deploy_set,    ao_config_main_deploy_show, },
701         { "d <delay>\0Apogee delay (s)",
702           ao_config_apogee_delay_set,   ao_config_apogee_delay_show },
703         { "L <seconds>\0Apogee detect lockout (s)",
704           ao_config_apogee_lockout_set, ao_config_apogee_lockout_show, },
705 #endif /* HAS_FLIGHT */
706 #if HAS_RADIO
707         { "F <freq>\0Frequency (kHz)",
708           ao_config_frequency_set, ao_config_frequency_show },
709         { "c <call>\0Callsign (8 char max)",
710           ao_config_callsign_set,       ao_config_callsign_show },
711         { "e <0 disable, 1 enable>\0Enable telemetry and RDF",
712           ao_config_radio_enable_set, ao_config_radio_enable_show },
713         { "f <cal>\0Radio calib (cal = rf/(xtal/2^16))",
714           ao_config_radio_cal_set,      ao_config_radio_cal_show },
715 #if HAS_RADIO_POWER
716         { "p <setting>\0Radio power setting (0-255)",
717           ao_config_radio_power_set,    ao_config_radio_power_show },
718 #endif
719 #if HAS_RADIO_AMP
720         { "d <setting>\0Radio amplifier setting (0-3)",
721           ao_config_radio_amp_set,      ao_config_radio_amp_show },
722 #endif
723 #endif /* HAS_RADIO */
724 #if HAS_ACCEL
725         { "a <+g> <-g>\0Accel calib (0 for auto)",
726           ao_config_accel_calibrate_set,ao_config_accel_calibrate_show },
727         { "o <0 antenna up, 1 antenna down>\0Set pad orientation",
728           ao_config_pad_orientation_set,ao_config_pad_orientation_show },
729 #endif /* HAS_ACCEL */
730 #if HAS_LOG
731         { "l <size>\0Flight log size (kB)",
732           ao_config_log_set,            ao_config_log_show },
733 #endif
734 #if HAS_IGNITE
735         { "i <0 dual, 1 apogee, 2 main>\0Set igniter mode",
736           ao_config_ignite_mode_set,    ao_config_ignite_mode_show },
737 #endif
738 #if HAS_AES
739         { "k <32 hex digits>\0Set AES encryption key",
740           ao_config_key_set, ao_config_key_show },
741 #endif
742 #if AO_PYRO_NUM
743         { "P <n,?>\0Configure pyro channels",
744           ao_pyro_set, ao_pyro_show },
745 #endif
746 #if HAS_APRS
747         { "A <secs>\0APRS packet interval (0 disable)",
748           ao_config_aprs_set, ao_config_aprs_show },
749 #endif
750 #if HAS_BEEP_CONFIG
751         { "b <val>\0Configure beeper tone (freq = 1/2 (24e6/32) / beep",
752           ao_config_beep_set, ao_config_beep_show },
753 #endif
754         { "s\0Show",
755           ao_config_show,               0 },
756 #if HAS_EEPROM
757         { "w\0Write to eeprom",
758           ao_config_save,               0 },
759 #endif
760         { "?\0Help",
761           ao_config_help,               0 },
762         { 0, 0, 0 }
763 };
764
765 void
766 ao_config_set(void)
767 {
768         char    c;
769         uint8_t cmd;
770
771         ao_cmd_white();
772         c = ao_cmd_lex_c;
773         ao_cmd_lex();
774         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
775                 if (ao_config_vars[cmd].str[0] == c) {
776                         (*ao_config_vars[cmd].set)();
777                         return;
778                 }
779         ao_cmd_status = ao_cmd_syntax_error;
780 }
781
782 static void
783 ao_config_help(void) __reentrant
784 {
785         uint8_t cmd;
786         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
787                 printf("%-20s %s\n",
788                        ao_config_vars[cmd].str,
789                        ao_config_vars[cmd].str+1+
790                        strlen(ao_config_vars[cmd].str));
791 }
792
793 static void
794 ao_config_show(void) __reentrant
795 {
796         uint8_t cmd;
797         ao_config_get();
798         printf("Config version: %d.%d\n",
799                ao_config.major, ao_config.minor);
800         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
801                 if (ao_config_vars[cmd].show)
802                         (*ao_config_vars[cmd].show)();
803 #if HAS_MS5607
804         ao_ms5607_info();
805 #endif
806 }
807
808 #if HAS_EEPROM
809 static void
810 ao_config_save(void) __reentrant
811 {
812         uint8_t saved = 0;
813         ao_mutex_get(&ao_config_mutex);
814         if (ao_config_dirty) {
815                 _ao_config_put();
816                 ao_config_dirty = 0;
817                 saved = 1;
818         }
819         ao_mutex_put(&ao_config_mutex);
820         if (saved)
821                 puts("Saved");
822         else
823                 puts("Nothing to save");
824 }
825 #endif
826
827 __code struct ao_cmds ao_config_cmds[] = {
828         { ao_config_set,        "c <var> <value>\0Set config (? for help, s to show)" },
829         { 0, NULL },
830 };
831
832 void
833 ao_config_init(void)
834 {
835         ao_cmd_register(&ao_config_cmds[0]);
836 }