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