altoslib/altosuilib: Update library version to 7
[fw/altos] / altoslib / AltosLink.java
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 package org.altusmetrum.altoslib_7;
19
20 import java.io.*;
21 import java.util.concurrent.*;
22 import java.util.*;
23
24 public abstract class AltosLink implements Runnable {
25
26         public final static int ERROR = -1;
27         public final static int TIMEOUT = -2;
28
29         public abstract int getchar() throws InterruptedException;
30         public abstract void print(String data) throws InterruptedException;
31         public abstract void putchar(byte c);
32         public abstract void close() throws InterruptedException;
33
34         public static boolean debug = false;
35         public static void set_debug(boolean in_debug) { debug = in_debug; }
36
37         public boolean has_error;
38
39         LinkedList<String> pending_output = new LinkedList<String>();
40
41         public LinkedList<LinkedBlockingQueue<AltosLine>> monitors = new LinkedList<LinkedBlockingQueue<AltosLine>> ();;
42         public LinkedBlockingQueue<AltosLine> reply_queue = new LinkedBlockingQueue<AltosLine>();
43         public LinkedBlockingQueue<byte[]> binary_queue = new LinkedBlockingQueue<byte[]>();
44
45         public synchronized void add_monitor(LinkedBlockingQueue<AltosLine> q) {
46                 set_monitor(true);
47                 monitors.add(q);
48         }
49
50         public synchronized void remove_monitor(LinkedBlockingQueue<AltosLine> q) {
51                 monitors.remove(q);
52                 if (monitors.isEmpty())
53                         set_monitor(false);
54         }
55
56         public void printf(String format, Object ... arguments) {
57                 String  line = String.format(format, arguments);
58                 if (debug) {
59                         synchronized (pending_output) {
60                                 pending_output.add(line);
61                         }
62                 }
63                 try {
64                         print(line);
65                 } catch (InterruptedException ie) {
66
67                 }
68         }
69
70         public String get_reply_no_dialog(int timeout) throws InterruptedException, TimeoutException {
71                 flush_output();
72                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
73                 if (line != null)
74                         return line.line;
75                 return null;
76         }
77
78         public String get_reply() throws InterruptedException {
79                 return get_reply(5000);
80         }
81
82
83         public abstract boolean can_cancel_reply();
84         public abstract boolean show_reply_timeout();
85         public abstract void hide_reply_timeout();
86
87         public boolean  reply_abort;
88         public int      in_reply;
89
90         boolean         reply_timeout_shown = false;
91
92         private boolean check_reply_timeout() {
93                 if (!reply_timeout_shown)
94                         reply_timeout_shown = show_reply_timeout();
95                 return reply_abort;
96         }
97
98         private void cleanup_reply_timeout() {
99                 if (reply_timeout_shown) {
100                         reply_timeout_shown = false;
101                         hide_reply_timeout();
102                 }
103         }
104
105         private int     len_read = 0;
106
107         public void run () {
108                 int c;
109                 byte[] line_bytes = null;
110                 int line_count = 0;
111
112                 try {
113                         for (;;) {
114                                 c = getchar();
115                                 if (Thread.interrupted()) {
116                                         break;
117                                 }
118                                 if (c == ERROR) {
119                                         if (debug)
120                                                 System.out.printf("ERROR\n");
121                                         has_error = true;
122                                         add_telem (new AltosLine());
123                                         add_reply (new AltosLine());
124                                         break;
125                                 }
126                                 if (c == TIMEOUT) {
127                                         if (debug)
128                                                 System.out.printf("TIMEOUT\n");
129                                         continue;
130                                 }
131                                 if (c == '\r' && len_read == 0)
132                                         continue;
133                                 synchronized(this) {
134                                         if (c == '\n' && len_read == 0) {
135                                                 if (line_count != 0) {
136                                                         add_bytes(line_bytes, line_count);
137                                                         line_count = 0;
138                                                 }
139                                         } else {
140                                                 if (line_bytes == null) {
141                                                         line_bytes = new byte[256];
142                                                 } else if (line_count == line_bytes.length) {
143                                                         byte[] new_line_bytes = new byte[line_count * 2];
144                                                         System.arraycopy(line_bytes, 0, new_line_bytes, 0, line_count);
145                                                         line_bytes = new_line_bytes;
146                                                 }
147                                                 line_bytes[line_count] = (byte) c;
148                                                 line_count++;
149                                                 if (len_read !=0 && line_count == len_read) {
150                                                         add_binary(line_bytes, line_count);
151                                                         line_count = 0;
152                                                         len_read = 0;
153                                                 }
154                                         }
155                                 }
156                         }
157                 } catch (InterruptedException e) {
158                 }
159         }
160
161
162         public String get_reply(int timeout) throws InterruptedException {
163                 boolean can_cancel = can_cancel_reply();
164                 String  reply = null;
165
166                 if (!can_cancel && remote)
167                         System.out.printf("Uh-oh, reading remote serial device from swing thread\n");
168
169                 if (remote && can_cancel) {
170                         timeout = 500;
171                         switch (telemetry_rate) {
172                         case AltosLib.ao_telemetry_rate_38400:
173                         default:
174                                 timeout = 500;
175                                 break;
176                         case AltosLib.ao_telemetry_rate_9600:
177                                 timeout = 2000;
178                                 break;
179                         case AltosLib.ao_telemetry_rate_2400:
180                                 timeout = 8000;
181                                 break;
182                         }
183                 }
184                 try {
185                         ++in_reply;
186
187                         flush_output();
188
189                         reply_abort = false;
190                         reply_timeout_shown = false;
191                         for (;;) {
192                                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
193                                 if (line != null) {
194                                         cleanup_reply_timeout();
195                                         reply = line.line;
196                                         break;
197                                 }
198                                 if (!remote || !can_cancel || check_reply_timeout()) {
199                                         reply = null;
200                                         break;
201                                 }
202                         }
203                 } finally {
204                         --in_reply;
205                 }
206                 return reply;
207         }
208
209         public byte[] get_binary_reply(int timeout, int len) throws InterruptedException {
210                 boolean can_cancel = can_cancel_reply();
211                 byte[] bytes = null;
212
213                 synchronized(this) {
214                         len_read = len;
215                 }
216                 try {
217                         ++in_reply;
218
219                         flush_output();
220
221                         reply_abort = false;
222                         reply_timeout_shown = false;
223                         for (;;) {
224                                 bytes = binary_queue.poll(timeout, TimeUnit.MILLISECONDS);
225                                 if (bytes != null) {
226                                         cleanup_reply_timeout();
227                                         break;
228                                 }
229                                 if (!remote || !can_cancel || check_reply_timeout()) {
230                                         bytes = null;
231                                         break;
232                                 }
233                         }
234
235                 } finally {
236                         --in_reply;
237                 }
238                 return bytes;
239         }
240
241         public void add_telem(AltosLine line) throws InterruptedException {
242                 for (int e = 0; e < monitors.size(); e++) {
243                         LinkedBlockingQueue<AltosLine> q = monitors.get(e);
244                         q.put(line);
245                 }
246         }
247
248         public void add_reply(AltosLine line) throws InterruptedException {
249                 reply_queue.put (line);
250         }
251
252         public void abort_reply() {
253                 try {
254                         add_telem (new AltosLine());
255                         add_reply (new AltosLine());
256                 } catch (InterruptedException ie) {
257                 }
258         }
259
260         public void add_string(String line) throws InterruptedException {
261                 if (line.startsWith("TELEM") || line.startsWith("VERSION") || line.startsWith("CRC")) {
262                         add_telem(new AltosLine(line));
263                 } else {
264                         add_reply(new AltosLine(line));
265                 }
266         }
267
268         public void add_bytes(byte[] bytes, int len) throws InterruptedException {
269                 String  line;
270                 line = new String(bytes, 0, len, AltosLib.unicode_set);
271                 if (debug)
272                         System.out.printf("\t\t\t\t\t%s\n", line);
273                 add_string(line);
274         }
275
276         public void add_binary(byte[] bytes, int len) throws InterruptedException {
277                 byte[] dup = new byte[len];
278
279                 if (debug)
280                         System.out.printf ("\t\t\t\t\t%d:", len);
281                 for(int i = 0; i < len; i++) {
282                         dup[i] = bytes[i];
283                         if (debug)
284                                 System.out.printf(" %02x", dup[i]);
285                 }
286                 if (debug)
287                         System.out.printf("\n");
288
289                 binary_queue.put(dup);
290         }
291
292         public synchronized void flush_output() {
293                 if (pending_output == null)
294                         return;
295                 synchronized (pending_output) {
296                         for (String s : pending_output)
297                                 System.out.print(s);
298                         pending_output.clear();
299                 }
300         }
301
302         public void flush_input(int timeout) throws InterruptedException {
303                 flush_output();
304                 boolean got_some;
305
306                 do {
307                         Thread.sleep(timeout);
308                         got_some = !reply_queue.isEmpty();
309                         reply_queue.clear();
310                 } while (got_some);
311         }
312
313
314         public void flush_input() throws InterruptedException {
315                 if (remote)
316                         flush_input(500);
317                 else
318                         flush_input(100);
319         }
320
321
322         /*
323          * Various command-level operations on
324          * the link
325          */
326         public boolean monitor_mode = false;
327         public int telemetry = AltosLib.ao_telemetry_standard;
328         public int telemetry_rate = -1;
329         public double frequency;
330         public String callsign;
331         AltosConfigData config_data;
332
333         private Object config_data_lock = new Object();
334
335         private int telemetry_len() {
336                 return AltosLib.telemetry_len(telemetry);
337         }
338
339         private void set_radio_freq(int frequency) {
340                 if (monitor_mode)
341                         printf("m 0\nc F %d\nm %x\n",
342                                frequency, telemetry_len());
343                 else
344                         printf("c F %d\n", frequency);
345                 flush_output();
346         }
347
348         public void set_radio_frequency(double frequency,
349                                         boolean has_frequency,
350                                         boolean has_setting,
351                                         int cal) {
352                 if (debug)
353                         System.out.printf("set_radio_frequency %7.3f (freq %b) (set %b) %d\n", frequency, has_frequency, has_setting, cal);
354                 if (frequency == 0)
355                         return;
356                 if (has_frequency)
357                         set_radio_freq((int) Math.floor (frequency * 1000 + 0.5));
358                 else if (has_setting)
359                         set_radio_setting(AltosConvert.radio_frequency_to_setting(frequency, cal));
360                 else
361                         set_channel(AltosConvert.radio_frequency_to_channel(frequency));
362         }
363
364         public void set_radio_frequency(double in_frequency) throws InterruptedException, TimeoutException {
365                 frequency = in_frequency;
366                 config_data();
367                 set_radio_frequency(frequency,
368                                     config_data.radio_frequency > 0,
369                                     config_data.radio_setting > 0,
370                                     config_data.radio_calibration);
371         }
372
373         public void set_telemetry(int in_telemetry) {
374                 telemetry = in_telemetry;
375                 if (monitor_mode)
376                         printf("m 0\nm %x\n", telemetry_len());
377                 flush_output();
378         }
379
380         public void set_telemetry_rate(int in_telemetry_rate) {
381                 telemetry_rate = in_telemetry_rate;
382                 if (monitor_mode)
383                         printf("m 0\nc T %d\nm %x\n", telemetry_rate, telemetry_len());
384                 else
385                         printf("c T %d\n", telemetry_rate);
386                 flush_output();
387         }
388
389         public synchronized void set_monitor(boolean monitor) {
390                 monitor_mode = monitor;
391                 if (monitor)
392                         printf("m %x\n", telemetry_len());
393                 else
394                         printf("m 0\n");
395                 flush_output();
396         }
397
398         public synchronized boolean get_monitor() {
399                 return monitor_mode;
400         }
401
402         private void set_channel(int channel) {
403                 if (monitor_mode)
404                         printf("m 0\nc r %d\nm %x\n",
405                                channel, telemetry_len());
406                 else
407                         printf("c r %d\n", channel);
408                 flush_output();
409         }
410
411         private void set_radio_setting(int setting) {
412                 if (monitor_mode)
413                         printf("m 0\nc R %d\nm %x\n",
414                                setting, telemetry_len());
415                 else
416                         printf("c R %d\n", setting);
417                 flush_output();
418         }
419
420         public AltosConfigData config_data() throws InterruptedException, TimeoutException {
421                 synchronized(config_data_lock) {
422                         if (config_data == null) {
423                                 printf("m 0\n");
424                                 config_data = new AltosConfigData(this);
425                                 if (monitor_mode)
426                                         set_monitor(true);
427                         }
428                         return config_data;
429                 }
430         }
431
432         public void set_callsign(String callsign) {
433                 this.callsign = callsign;
434                 if (callsign != null) {
435                         printf ("c c %s\n", callsign);
436                         flush_output();
437                 }
438         }
439
440         public boolean is_loader() throws InterruptedException {
441                 boolean ret = false;
442                 printf("v\n");
443                 for (;;) {
444                         String line = get_reply();
445
446                         if (line == null)
447                                 return false;
448                         if (line.startsWith("software-version"))
449                                 break;
450                         if (line.startsWith("altos-loader"))
451                                 ret = true;
452                 }
453                 return ret;
454         }
455
456         public void to_loader() throws InterruptedException {
457                 printf("X\n");
458                 flush_output();
459                 close();
460                 Thread.sleep(1000);
461         }
462
463         public boolean remote;
464         public int serial;
465         public String name;
466
467         public void start_remote() throws TimeoutException, InterruptedException {
468                 if (frequency == 0.0)
469                         frequency = AltosPreferences.frequency(serial);
470                 if (debug)
471                         System.out.printf("start remote %7.3f\n", frequency);
472                 set_radio_frequency(frequency);
473                 if (telemetry_rate < 0)
474                         telemetry_rate = AltosPreferences.telemetry_rate(serial);
475                 set_telemetry_rate(telemetry_rate);
476                 if (callsign == null || callsign.equals(""))
477                         callsign = AltosPreferences.callsign();
478                 set_callsign(callsign);
479                 printf("p\nE 0\n");
480                 flush_input();
481                 remote = true;
482         }
483
484         public void stop_remote() throws InterruptedException {
485                 if (debug)
486                         System.out.printf("stop remote\n");
487                 try {
488                         flush_input();
489                 } finally {
490                         printf ("~\n");
491                         flush_output();
492                 }
493                 remote = false;
494         }
495
496         public int rssi() throws TimeoutException, InterruptedException {
497                 if (remote)
498                         return 0;
499                 printf("s\n");
500                 String line = get_reply_no_dialog(5000);
501                 if (line == null)
502                         throw new TimeoutException();
503                 String[] items = line.split("\\s+");
504                 if (items.length < 2)
505                         return 0;
506                 if (!items[0].equals("RSSI:"))
507                         return 0;
508                 int rssi = Integer.parseInt(items[1]);
509                 return rssi;
510         }
511
512         public String[] adc() throws TimeoutException, InterruptedException {
513                 printf("a\n");
514                 for (;;) {
515                         String line = get_reply_no_dialog(5000);
516                         if (line == null) {
517                                 throw new TimeoutException();
518                         }
519                         if (!line.startsWith("tick:"))
520                                 continue;
521                         String[] items = line.split("\\s+");
522                         return items;
523                 }
524         }
525
526         public boolean has_monitor_battery() {
527                 return config_data.has_monitor_battery();
528         }
529
530         public double monitor_battery() throws InterruptedException {
531                 int monitor_batt = AltosLib.MISSING;
532
533                 if (config_data.has_monitor_battery()) {
534                         try {
535                         String[] items = adc();
536                         for (int i = 0; i < items.length;) {
537                                 if (items[i].equals("batt")) {
538                                         monitor_batt = Integer.parseInt(items[i+1]);
539                                         i += 2;
540                                         continue;
541                                 }
542                                 i++;
543                         }
544                         } catch (TimeoutException te) {
545                         }
546                 }
547                 if (monitor_batt == AltosLib.MISSING)
548                         return AltosLib.MISSING;
549                 return AltosConvert.cc_battery_to_voltage(monitor_batt);
550         }
551
552         public AltosLink() {
553                 callsign = "";
554                 has_error = false;
555         }
556 }