8875ff487b7438040a9f8dd9478f8428e1f2bb61
[fw/altos] / src / product / ao_terraui.c
1 /*
2  * Copyright © 2011 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 <math.h>
20
21 static __xdata struct ao_telemetry_sensor               ao_tel_sensor;
22 static __xdata struct ao_telemetry_location             ao_tel_location;
23 static __xdata struct ao_telemetry_configuration        ao_tel_config;
24 static __xdata int16_t                                  ao_tel_max_speed;
25 static __xdata int16_t                                  ao_tel_max_height;
26 static int8_t ao_tel_rssi;
27
28 static __xdata char ao_lcd_line[17];
29 static __xdata char ao_state_name[] = "SIPBFCDMLI";
30
31 static void
32 ao_terraui_line(uint8_t addr)
33 {
34         ao_lcd_goto(addr);
35         ao_lcd_putstring(ao_lcd_line);
36 }
37
38 #define ao_terraui_state()      (ao_state_name[ao_tel_sensor.state])
39
40 static char
41 ao_terraui_igniter(int16_t sense)
42 {
43         if (sense < AO_IGNITER_OPEN)
44                 return '-';
45         if (sense > AO_IGNITER_CLOSED)
46                 return '+';
47         return '?';
48 }
49
50 static char
51 ao_terraui_battery(void)
52 {
53         if (ao_tel_sensor.v_batt > 25558)
54                 return '+';
55         return '-';
56 }
57
58 static char
59 ao_terraui_gps(void)
60 {
61         if (ao_tel_location.flags & (1 << 4)) {
62                 if ((ao_tel_location.flags & 0xf) >= 4)
63                         return '+';
64         }
65         return '-';
66 }
67
68 static char
69 ao_terraui_local_gps(void)
70 {
71         if (ao_gps_data.flags & (1 << 4)) {
72                 if ((ao_gps_data.flags & 0xf) >= 4)
73                         return '+';
74         }
75         return '-';
76 }
77
78 static char
79 ao_terraui_logging(void)
80 {
81         if (ao_tel_config.flight != 0)
82                 return '+';
83         return '-';
84 }
85
86 static __code char ao_progress[4] = { '\011', '\012', '\014', '\013' };
87
88 static uint8_t  ao_telem_progress;
89 static uint8_t  ao_gps_progress;
90
91 static void
92 ao_terraui_startup(void)
93 {
94         sprintf(ao_lcd_line, "%-16.16s", ao_product);
95         ao_terraui_line(AO_LCD_ADDR(0,0));
96         sprintf(ao_lcd_line, "%-8.8s %5u  ", ao_version, ao_serial_number);
97         ao_terraui_line(AO_LCD_ADDR(1,0));
98 }
99
100 static void
101 ao_terraui_info_firstline(void)
102 {
103         sprintf(ao_lcd_line, "S %4d %7.7s %c",
104                 ao_tel_sensor.serial,
105                 ao_tel_config.callsign,
106                 ao_terraui_state());
107         ao_terraui_line(AO_LCD_ADDR(0,0));
108 }
109         
110 static void
111 ao_terraui_info(void)
112 {
113         ao_terraui_info_firstline();
114         sprintf(ao_lcd_line, "F %4d RSSI%4d%c",
115                 ao_tel_config.flight,
116                 ao_tel_rssi,
117                 ao_progress[ao_telem_progress]);
118         ao_terraui_line(AO_LCD_ADDR(1,0));
119 }
120
121 static void
122 ao_terraui_pad(void)
123 {
124         sprintf(ao_lcd_line, "B%c A%c M%c L%c G%c %c",
125                 ao_terraui_battery(),
126                 ao_terraui_igniter(ao_tel_sensor.sense_d),
127                 ao_terraui_igniter(ao_tel_sensor.sense_m),
128                 ao_terraui_logging(),
129                 ao_terraui_gps(),
130                 ao_terraui_state());
131         ao_terraui_line(AO_LCD_ADDR(0,0));
132         sprintf(ao_lcd_line, "SAT %2d RSSI%4d%c",
133                 ao_tel_location.flags & 0xf,
134                 ao_tel_rssi,
135                 ao_progress[ao_telem_progress]);
136         ao_terraui_line(AO_LCD_ADDR(1,0));
137 }
138
139 static void
140 ao_terraui_ascent(void)
141 {
142         sprintf(ao_lcd_line, "S %5d S\011%5d%c",
143                 ao_tel_sensor.speed >> 4,
144                 ao_tel_max_speed >> 4,
145                 ao_terraui_state());
146         ao_terraui_line(AO_LCD_ADDR(0,0));
147         sprintf(ao_lcd_line, "H %5d H\011%5d%c",
148                 ao_tel_sensor.height >> 4,
149                 ao_tel_max_height >> 4,
150                 ao_progress[ao_telem_progress]);
151         ao_terraui_line(AO_LCD_ADDR(1,0));
152 }
153
154 static int16_t mag(int32_t d)
155 {
156         if (d < 0)
157                 d = -d;
158         if (d > 0x7fff)
159                 d = 0x7fff;
160         return d;
161 }
162
163 static int32_t
164 dist(int32_t d)
165 {
166         __pdata uint32_t m;
167         uint8_t neg = 0;
168
169         if (d < 0) {
170                 d = -d;
171                 neg = 1;
172         }
173
174         m = 10000000;
175         while (d >= (2147483647 / 111198)) {
176                 d /= 10;
177                 m /= 10;
178         }
179         d = (d * 111198) / m;
180         if (neg)
181                 d = -d;
182         return d;
183 }
184
185 static __code uint8_t cos_table[] = {
186    0, /*  0 */
187    0, /*  1 */
188    0, /*  2 */
189  255, /*  3 */
190  254, /*  4 */
191  253, /*  5 */
192  252, /*  6 */
193  251, /*  7 */
194  249, /*  8 */
195  247, /*  9 */
196  245, /* 10 */
197  243, /* 11 */
198  240, /* 12 */
199  238, /* 13 */
200  235, /* 14 */
201  232, /* 15 */
202  228, /* 16 */
203  225, /* 17 */
204  221, /* 18 */
205  217, /* 19 */
206  213, /* 20 */
207  209, /* 21 */
208  205, /* 22 */
209  200, /* 23 */
210  195, /* 24 */
211  190, /* 25 */
212  185, /* 26 */
213  180, /* 27 */
214  175, /* 28 */
215  169, /* 29 */
216  163, /* 30 */
217  158, /* 31 */
218  152, /* 32 */
219  145, /* 33 */
220  139, /* 34 */
221  133, /* 35 */
222  126, /* 36 */
223  120, /* 37 */
224  113, /* 38 */
225  106, /* 39 */
226  100, /* 40 */
227   93, /* 41 */
228   86, /* 42 */
229   79, /* 43 */
230   71, /* 44 */
231   64, /* 45 */
232   57, /* 46 */
233   49, /* 47 */
234   42, /* 48 */
235   35, /* 49 */
236   27, /* 50 */
237   20, /* 51 */
238   12, /* 52 */
239    5, /* 53 */
240    1, /* 54 */
241 };
242
243 static __code uint8_t tan_table[] = {
244     0, /*  0 */
245     4, /*  1 */
246     9, /*  2 */
247    13, /*  3 */
248    18, /*  4 */
249    22, /*  5 */
250    27, /*  6 */
251    31, /*  7 */
252    36, /*  8 */
253    41, /*  9 */
254    45, /* 10 */
255    50, /* 11 */
256    54, /* 12 */
257    59, /* 13 */
258    64, /* 14 */
259    69, /* 15 */
260    73, /* 16 */
261    78, /* 17 */
262    83, /* 18 */
263    88, /* 19 */
264    93, /* 20 */
265    98, /* 21 */
266   103, /* 22 */
267   109, /* 23 */
268   114, /* 24 */
269   119, /* 25 */
270   125, /* 26 */
271   130, /* 27 */
272   136, /* 28 */
273   142, /* 29 */
274   148, /* 30 */
275   154, /* 31 */
276   160, /* 32 */
277   166, /* 33 */
278   173, /* 34 */
279   179, /* 35 */
280   186, /* 36 */
281   193, /* 37 */
282   200, /* 38 */
283   207, /* 39 */
284   215, /* 40 */
285   223, /* 41 */
286   231, /* 42 */
287   239, /* 43 */
288   247, /* 44 */
289 };
290         
291 int16_t ao_atan2(int32_t dy, int32_t dx) __reentrant
292 {
293         int8_t  m = 1;
294         int16_t a = 0;
295         uint8_t r;
296         int8_t  t;
297
298         if (dx == 0) {
299                 if (dy > 0)
300                         return 90;
301                 if (dy < 0)
302                         return -90;
303                 return 0;
304         }
305
306         if (dx < 0) {
307                 a = 180;
308                 m = -m;
309                 dx = -dx;
310         }
311
312         if (dy < 0) {
313                 m = -m;
314                 a = -a;
315                 dy = -dy;
316         }
317
318         if (dy > dx) {
319                 int32_t t;
320
321                 t = dy; dy = dx; dx = t;
322                 a = a + m * 90;
323                 m = -m;
324         }
325
326         dy = dy << 8;
327         dy = (dy + (dx >> 1)) / dx;
328         r = dy;
329         for (t = 0; t < 44; t++)
330                 if (tan_table[t] >= r)
331                         break;
332         return t * m + a;
333 }
334
335 static __pdata int32_t  lon_dist, lat_dist;
336 static __pdata uint32_t ground_dist, range;
337 static __pdata uint8_t dist_in_km;
338 static __pdata int16_t bearing, elevation;
339
340 static void
341 ao_terraui_lat_dist(void)
342 {
343         lat_dist = dist (ao_tel_location.latitude - ao_gps_data.latitude);
344 }
345
346 static void
347 ao_terraui_lon_dist(void) __reentrant
348 {
349         uint8_t c = cos_table[ao_gps_data.latitude >> 24];
350         lon_dist = ao_tel_location.longitude;
351
352         /* check if it's shorter to go the other way around */
353         if ((int16_t) (lon_dist >> 24) < (int16_t) (ao_gps_data.longitude >> 24) - (int16_t) (1800000000 >> 24))
354                 lon_dist += 3600000000ul;
355         lon_dist = dist(lon_dist - ao_gps_data.longitude);
356         if (c) {
357                 if (lon_dist & 0x7f800000)
358                         lon_dist = (lon_dist >> 8) * c;
359                 else
360                         lon_dist = (lon_dist * (int16_t) c) >> 8;
361         }
362 }
363
364 static int32_t sqr(int32_t x) { return x * x; }
365
366 static void
367 ao_terraui_compute(void)
368 {
369         uint16_t        h = ao_tel_sensor.height;
370
371         ao_terraui_lat_dist();
372         ao_terraui_lon_dist();
373         if (lat_dist > 32767 || lon_dist > 32767) {
374                 dist_in_km = 1;
375                 ground_dist = sqr(lat_dist/1000) + sqr(lon_dist/1000);
376                 h = h/1000;
377         } else {
378                 dist_in_km = 0;
379                 ground_dist = sqr(lat_dist) + sqr(lon_dist);
380         }
381         ground_dist = ao_sqrt (ground_dist);
382         range = ao_sqrt(ground_dist * ground_dist + ao_tel_sensor.height * ao_tel_sensor.height);
383         bearing = ao_atan2(lon_dist, lat_dist);
384         if (bearing < 0)
385                 bearing += 360;
386         elevation = ao_atan2(ao_tel_sensor.height, ground_dist);
387 }
388
389 static void
390 ao_terraui_descent(void)
391 {
392         ao_terraui_compute();
393         sprintf(ao_lcd_line, "\007 %4d  \005 %3d  %c",
394                 bearing, elevation,
395                 ao_terraui_state());
396         ao_terraui_line(AO_LCD_ADDR(0,0));
397         sprintf(ao_lcd_line, "H:%5d S:%5d%c",
398                 ao_tel_sensor.height, ao_tel_sensor.speed >> 4,
399                 ao_progress[ao_telem_progress]);
400         ao_terraui_line(AO_LCD_ADDR(1,0));
401 }
402
403 static void
404 ao_terraui_recover(void)
405 {
406         ao_terraui_compute();
407         sprintf(ao_lcd_line, "\007 %4d  \005 %3d  %c",
408                 bearing, elevation,
409                 ao_terraui_state());
410         ao_terraui_line(AO_LCD_ADDR(0,0));
411         sprintf(ao_lcd_line, "R%5ld%cRSSI%4d%c",
412                 ground_dist, dist_in_km ? 'k' : ' ',
413                 ao_tel_rssi,
414                 ao_progress[ao_telem_progress]);
415         ao_terraui_line(AO_LCD_ADDR(1,0));
416 }
417
418 static void
419 ao_terraui_coord(int32_t c, char plus, char minus, char extra) __reentrant
420 {
421         uint16_t        d;
422         uint8_t         m;
423         uint16_t        f;
424
425         if (c < 0) {
426                 plus = minus;
427                 c = -c;
428         }
429         d = c / 10000000;
430         c = c % 10000000;
431         c = c * 60;
432         m = c / 10000000;
433         c = c % 10000000;
434         f = (c + 500) / 1000;
435         sprintf(ao_lcd_line, "%c %3d\362 %2d.%04d\"%c",
436                 plus, d, m, f, extra);
437 }
438
439 static void
440 ao_terraui_remote(void)
441 {
442         ao_terraui_coord(ao_tel_location.latitude, 'N', 'S', ao_terraui_state());
443         ao_terraui_line(AO_LCD_ADDR(0,0));
444         ao_terraui_coord(ao_tel_location.longitude, 'E', 'W', ao_progress[ao_telem_progress]);
445         ao_terraui_line(AO_LCD_ADDR(1,0));
446 }
447
448 static void
449 ao_terraui_local(void) __reentrant
450 {
451         ao_terraui_coord(ao_gps_data.latitude, 'n', 's',
452                          ao_terraui_local_gps());
453         ao_terraui_line(AO_LCD_ADDR(0,0));
454         ao_terraui_coord(ao_gps_data.longitude, 'e', 'w', ao_progress[ao_gps_progress]);
455         ao_terraui_line(AO_LCD_ADDR(1,0));
456 }
457
458 static __pdata uint8_t ao_set_freq;
459 static __pdata uint32_t ao_set_freq_orig;
460
461 static void
462 ao_terraui_freq(void) __reentrant
463 {
464         uint16_t        MHz;
465         uint16_t        frac;
466         
467         MHz = ao_config.frequency / 1000;
468         frac = ao_config.frequency % 1000;
469         ao_terraui_info_firstline();
470         sprintf(ao_lcd_line, "Freq: %3d.%03d MHz", MHz, frac);
471         ao_terraui_line(AO_LCD_ADDR(1,0));
472         ao_lcd_goto(AO_LCD_ADDR(1,11));
473 }
474
475 static void
476 ao_terraui_freq_start(void)
477 {
478         ao_set_freq = 1;
479         ao_set_freq_orig = ao_config.frequency;
480         ao_lcd_cursor_on();
481 }
482
483 static void
484 ao_terraui_freq_button(char b) {
485
486         switch (b) {
487         case 0:
488                 return;
489         case 1:
490                 if (ao_config.frequency > 430000)
491                         ao_config.frequency -= 100;
492                 break;
493         case 2:
494                 ao_set_freq = 0;
495                 ao_lcd_cursor_off();
496                 if (ao_set_freq_orig != ao_config.frequency)
497                         ao_config_put();
498                 return;
499         case 3:
500                 if (ao_config.frequency < 438000)
501                         ao_config.frequency += 100;
502                 break;
503
504         }
505         ao_config_set_radio();
506         ao_radio_recv_abort();
507 }
508
509 static __code void (*__code ao_terraui_page[])(void) = {
510         ao_terraui_startup,
511         ao_terraui_info,
512         ao_terraui_pad,
513         ao_terraui_ascent,
514         ao_terraui_descent,
515         ao_terraui_recover,
516         ao_terraui_remote,
517         ao_terraui_local,
518 };
519
520 #define NUM_PAGE        (sizeof (ao_terraui_page)/sizeof (ao_terraui_page[0]))
521
522 static __pdata uint8_t ao_current_page = 0;
523 static __pdata uint8_t ao_shown_about = 3;
524
525 static void
526 ao_terraui(void)
527 {
528         ao_lcd_start();
529
530         ao_delay(AO_MS_TO_TICKS(100));
531         ao_button_clear();
532
533         for (;;) {
534                 char    b;
535
536                 if (ao_set_freq)
537                         ao_terraui_freq();
538                 else
539                         ao_terraui_page[ao_current_page]();
540
541                 ao_alarm(AO_SEC_TO_TICKS(1));
542                 b = ao_button_get();
543                 ao_clear_alarm();
544
545                 if (b > 0) {
546                         ao_beep_for(AO_BEEP_HIGH, AO_MS_TO_TICKS(10));
547                         ao_shown_about = 0;
548                 }
549                 
550                 if (ao_set_freq) {
551                         ao_terraui_freq_button(b);
552                         continue;
553                 }
554                 switch (b) {
555                 case 0:
556                         if (ao_shown_about) {
557                                 if (--ao_shown_about == 0)
558                                         ao_current_page = 2;
559                         }
560                         break;
561                 case 1:
562                         ao_current_page++;
563                         if (ao_current_page >= NUM_PAGE)
564                                 ao_current_page = 0;
565                         break;
566                 case 2:
567                         ao_terraui_freq_start();
568                         break;
569                 case 3:
570                         if (ao_current_page == 0)
571                                 ao_current_page = NUM_PAGE;
572                         ao_current_page--;
573                         break;
574                 }
575         }
576 }
577
578 __xdata static struct ao_task ao_terraui_task;
579
580 static void
581 ao_terramonitor(void)
582 {
583         uint8_t monitor;
584
585         monitor = ao_monitor_head;
586         ao_monitor_set(sizeof (struct ao_telemetry_generic));
587         for (monitor = ao_monitor_head;;
588              monitor = ao_monitor_ring_next(monitor))
589         {
590                 while (monitor == ao_monitor_head)
591                         ao_sleep(DATA_TO_XDATA(&ao_monitor_head));
592                 if (ao_monitoring != sizeof (union ao_telemetry_all))
593                         continue;
594                 if (!(ao_monitor_ring[monitor].all.status & PKT_APPEND_STATUS_1_CRC_OK))
595                         continue;
596                 ao_tel_rssi = (ao_monitor_ring[monitor].all.rssi >> 1) - 74;
597                 switch (ao_monitor_ring[monitor].all.telemetry.generic.type) {
598                 case AO_TELEMETRY_SENSOR_TELEMETRUM:
599                 case AO_TELEMETRY_SENSOR_TELEMINI:
600                 case AO_TELEMETRY_SENSOR_TELENANO:
601                         ao_xmemcpy(&ao_tel_sensor, &ao_monitor_ring[monitor], sizeof (ao_tel_sensor));
602                         if (ao_tel_sensor.state < ao_flight_boost) {
603                                 ao_tel_max_speed = 0;
604                                 ao_tel_max_height = 0;
605                         } else {
606                                 if (ao_tel_sensor.speed > ao_tel_max_speed)
607                                         ao_tel_max_speed = ao_tel_sensor.speed;
608                                 if (ao_tel_sensor.height > ao_tel_max_height)
609                                         ao_tel_max_height = ao_tel_sensor.height;
610                         }
611                         ao_telem_progress = (ao_telem_progress + 1) & 0x3;
612                         break;
613                 case AO_TELEMETRY_LOCATION:
614                         ao_xmemcpy(&ao_tel_location, &ao_monitor_ring[monitor], sizeof (ao_tel_location));
615                         break;
616                 case AO_TELEMETRY_CONFIGURATION:
617                         ao_xmemcpy(&ao_tel_config, &ao_monitor_ring[monitor], sizeof (ao_tel_config));
618                 }
619         }
620 }
621
622 __xdata static struct ao_task ao_terramonitor_task;
623
624 static void
625 ao_terragps(void)
626 {
627         uint16_t        gps_tick = ao_gps_progress;
628
629         for (;;) {
630                 while (ao_gps_tick == gps_tick)
631                         ao_sleep(&ao_gps_data);
632                 gps_tick = ao_gps_tick;
633                 ao_gps_progress = (ao_gps_progress + 1) & 3;
634         }
635 }
636
637 __xdata static struct ao_task ao_terragps_task;
638
639 void
640 ao_terraui_init(void)
641 {
642         ao_add_task(&ao_terraui_task, ao_terraui, "ui");
643         ao_add_task(&ao_terramonitor_task, ao_terramonitor, "monitor");
644         ao_add_task(&ao_terragps_task, ao_terragps, "gps");
645 }