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