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