altos: Add gyro-based orientation tracking
[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_2;
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();
30         public abstract void print(String data);
31         public abstract void close();
32
33         public static boolean debug = false;
34         public static void set_debug(boolean in_debug) { debug = in_debug; }
35
36         public boolean has_error;
37
38         LinkedList<String> pending_output = new LinkedList<String>();
39
40         public LinkedList<LinkedBlockingQueue<AltosLine>> monitors = new LinkedList<LinkedBlockingQueue<AltosLine>> ();;
41         public LinkedBlockingQueue<AltosLine> reply_queue = new LinkedBlockingQueue<AltosLine>();
42
43         public synchronized void add_monitor(LinkedBlockingQueue<AltosLine> q) {
44                 set_monitor(true);
45                 monitors.add(q);
46         }
47
48         public synchronized void remove_monitor(LinkedBlockingQueue<AltosLine> q) {
49                 monitors.remove(q);
50                 if (monitors.isEmpty())
51                         set_monitor(false);
52         }
53
54         public void printf(String format, Object ... arguments) {
55                 String  line = String.format(format, arguments);
56                 if (debug)
57                         pending_output.add(line);
58                 print(line);
59         }
60
61         public String get_reply_no_dialog(int timeout) throws InterruptedException, TimeoutException {
62                 flush_output();
63                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
64                 if (line != null)
65                         return line.line;
66                 return null;
67         }
68
69         public String get_reply() throws InterruptedException {
70                 return get_reply(5000);
71         }
72
73                 
74         public abstract boolean can_cancel_reply();
75         public abstract boolean show_reply_timeout();
76         public abstract void hide_reply_timeout();
77
78         public boolean  reply_abort;
79         public int      in_reply;
80
81         boolean         reply_timeout_shown = false;
82
83         private boolean check_reply_timeout() {
84                 if (!reply_timeout_shown)
85                         reply_timeout_shown = show_reply_timeout();
86                 return reply_abort;
87         }
88
89         private void cleanup_reply_timeout() {
90                 if (reply_timeout_shown) {
91                         reply_timeout_shown = false;
92                         hide_reply_timeout();
93                 }
94         }
95
96
97         public void run () {
98                 int c;
99                 byte[] line_bytes = null;
100                 int line_count = 0;
101
102                 try {
103                         for (;;) {
104                                 c = getchar();
105                                 if (Thread.interrupted()) {
106                                         if (debug)
107                                                 System.out.printf("INTERRUPTED\n");
108                                         break;
109                                 }
110                                 if (c == ERROR) {
111                                         if (debug)
112                                                 System.out.printf("ERROR\n");
113                                         has_error = true;
114                                         add_telem (new AltosLine());
115                                         add_reply (new AltosLine());
116                                         break;
117                                 }
118                                 if (c == TIMEOUT) {
119                                         if (debug)
120                                                 System.out.printf("TIMEOUT\n");
121                                         continue;
122                                 }
123                                 if (c == '\r')
124                                         continue;
125                                 synchronized(this) {
126                                         if (c == '\n') {
127                                                 if (line_count != 0) {
128                                                         add_bytes(line_bytes, line_count);
129                                                         line_count = 0;
130                                                 }
131                                         } else {
132                                                 if (line_bytes == null) {
133                                                         line_bytes = new byte[256];
134                                                 } else if (line_count == line_bytes.length) {
135                                                         byte[] new_line_bytes = new byte[line_count * 2];
136                                                         System.arraycopy(line_bytes, 0, new_line_bytes, 0, line_count);
137                                                         line_bytes = new_line_bytes;
138                                                 }
139                                                 line_bytes[line_count] = (byte) c;
140                                                 line_count++;
141                                         }
142                                 }
143                         }
144                 } catch (InterruptedException e) {
145                 }
146         }
147
148         public String get_reply(int timeout) throws InterruptedException {
149                 boolean can_cancel = can_cancel_reply();
150                 String  reply = null;
151
152                 if (!can_cancel && remote)
153                         System.out.printf("Uh-oh, reading remote serial device from swing thread\n");
154
155                 if (remote && can_cancel)
156                         timeout = 500;
157                 try {
158                         ++in_reply;
159
160                         flush_output();
161
162                         reply_abort = false;
163                         reply_timeout_shown = false;
164                         for (;;) {
165                                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
166                                 if (line != null) {
167                                         cleanup_reply_timeout();
168                                         reply = line.line;
169                                         break;
170                                 }
171                                 if (!remote || !can_cancel || check_reply_timeout()) {
172                                         reply = null;
173                                         break;
174                                 }
175                         }
176                 } finally {
177                         --in_reply;
178                 }
179                 return reply;
180         }
181
182         public void add_telem(AltosLine line) throws InterruptedException {
183                 for (int e = 0; e < monitors.size(); e++) {
184                         LinkedBlockingQueue<AltosLine> q = monitors.get(e);
185                         q.put(line);
186                 }
187         }
188
189         public void add_reply(AltosLine line) throws InterruptedException {
190                 reply_queue.put (line);
191         }
192
193         public void abort_reply() {
194                 try {
195                         add_telem (new AltosLine());
196                         add_reply (new AltosLine());
197                 } catch (InterruptedException e) {
198                 }
199         }
200
201         public void add_string(String line) throws InterruptedException {
202                 if (line.startsWith("TELEM") || line.startsWith("VERSION") || line.startsWith("CRC")) {
203                         add_telem(new AltosLine(line));
204                 } else {
205                         add_reply(new AltosLine(line));
206                 }
207         }
208
209         public void add_bytes(byte[] bytes, int len) throws InterruptedException {
210                 String  line;
211                 try {
212                         line = new String(bytes, 0, len, "UTF-8");
213                 } catch (UnsupportedEncodingException ue) {
214                         line = "";
215                         for (int i = 0; i < len; i++)
216                                 line = line + bytes[i];
217                 }
218                 if (debug)
219                         System.out.printf("\t\t\t\t\t%s\n", line);
220                 add_string(line);
221         }
222
223         public void flush_output() {
224                 for (String s : pending_output)
225                         System.out.print(s);
226                 pending_output.clear();
227         }
228
229         public void flush_input(int timeout) throws InterruptedException {
230                 flush_output();
231                 boolean got_some;
232
233                 do {
234                         Thread.sleep(timeout);
235                         got_some = !reply_queue.isEmpty();
236                         reply_queue.clear();
237                 } while (got_some);
238         }
239
240
241         public void flush_input() throws InterruptedException {
242                 if (remote)
243                         flush_input(500);
244                 else
245                         flush_input(100);
246         }
247
248
249         /*
250          * Various command-level operations on
251          * the link
252          */
253         public boolean monitor_mode = false;
254         public int telemetry = AltosLib.ao_telemetry_standard;
255         public double frequency;
256         public String callsign;
257         AltosConfigData config_data;
258
259         private Object config_data_lock = new Object();
260
261         private int telemetry_len() {
262                 return AltosLib.telemetry_len(telemetry);
263         }
264
265         private void set_radio_freq(int frequency) {
266                 if (monitor_mode)
267                         printf("m 0\nc F %d\nm %x\n",
268                                frequency, telemetry_len());
269                 else
270                         printf("c F %d\n", frequency);
271                 flush_output();
272         }
273
274         public void set_radio_frequency(double frequency,
275                                         boolean has_frequency,
276                                         boolean has_setting,
277                                         int cal) {
278                 if (debug)
279                         System.out.printf("set_radio_frequency %7.3f (freq %b) (set %b) %d\n", frequency, has_frequency, has_setting, cal);
280                 if (frequency == 0)
281                         return;
282                 if (has_frequency)
283                         set_radio_freq((int) Math.floor (frequency * 1000));
284                 else if (has_setting)
285                         set_radio_setting(AltosConvert.radio_frequency_to_setting(frequency, cal));
286                 else
287                         set_channel(AltosConvert.radio_frequency_to_channel(frequency));
288         }
289
290         public void set_radio_frequency(double in_frequency) throws InterruptedException, TimeoutException {
291                 frequency = in_frequency;
292                 config_data();
293                 set_radio_frequency(frequency,
294                                     config_data.radio_frequency > 0,
295                                     config_data.radio_setting > 0,
296                                     config_data.radio_calibration);
297         }
298
299         public void set_telemetry(int in_telemetry) {
300                 telemetry = in_telemetry;
301                 if (monitor_mode)
302                         printf("m 0\nm %x\n", telemetry_len());
303                 flush_output();
304         }
305
306         public void set_monitor(boolean monitor) {
307                 monitor_mode = monitor;
308                 if (monitor)
309                         printf("m %x\n", telemetry_len());
310                 else
311                         printf("m 0\n");
312                 flush_output();
313         }
314
315         private void set_channel(int channel) {
316                 if (monitor_mode)
317                         printf("m 0\nc r %d\nm %x\n",
318                                channel, telemetry_len());
319                 else
320                         printf("c r %d\n", channel);
321                 flush_output();
322         }
323
324         private void set_radio_setting(int setting) {
325                 if (monitor_mode)
326                         printf("m 0\nc R %d\nm %x\n",
327                                setting, telemetry_len());
328                 else
329                         printf("c R %d\n", setting);
330                 flush_output();
331         }
332
333         public AltosConfigData config_data() throws InterruptedException, TimeoutException {
334                 synchronized(config_data_lock) {
335                         if (config_data == null)
336                                 config_data = new AltosConfigData(this);
337                         return config_data;
338                 }
339         }
340
341         public void set_callsign(String callsign) {
342                 this.callsign = callsign;
343                 printf ("c c %s\n", callsign);
344                 flush_output();
345         }
346
347         public boolean remote;
348         public int serial;
349         public String name;
350
351         public void start_remote() throws TimeoutException, InterruptedException {
352                 if (frequency == 0.0)
353                         frequency = AltosPreferences.frequency(serial);
354                 if (debug)
355                         System.out.printf("start remote %7.3f\n", frequency);
356                 set_radio_frequency(frequency);
357                 set_callsign(AltosPreferences.callsign());
358                 printf("p\nE 0\n");
359                 flush_input();
360                 remote = true;
361         }
362
363         public void stop_remote() throws InterruptedException {
364                 if (debug)
365                         System.out.printf("stop remote\n");
366                 try {
367                         flush_input();
368                 } finally {
369                         printf ("~\n");
370                         flush_output();
371                 }
372                 remote = false;
373         }
374
375         public int rssi() throws TimeoutException, InterruptedException {
376                 if (remote)
377                         return 0;
378                 printf("s\n");
379                 String line = get_reply_no_dialog(5000);
380                 if (line == null)
381                         throw new TimeoutException();
382                 String[] items = line.split("\\s+");
383                 if (items.length < 2)
384                         return 0;
385                 if (!items[0].equals("RSSI:"))
386                         return 0;
387                 int rssi = Integer.parseInt(items[1]);
388                 return rssi;
389         }
390
391         public String[] adc() throws TimeoutException, InterruptedException {
392                 printf("a\n");
393                 for (;;) {
394                         String line = get_reply_no_dialog(5000);
395                         if (line == null) {
396                                 throw new TimeoutException();
397                         }
398                         if (!line.startsWith("tick:"))
399                                 continue;
400                         String[] items = line.split("\\s+");
401                         return items;
402                 }
403         }
404
405         public boolean has_monitor_battery() {
406                 return config_data.has_monitor_battery();
407         }
408
409         public double monitor_battery() {
410                 int monitor_batt = AltosLib.MISSING;
411
412                 if (config_data.has_monitor_battery()) {
413                         try {
414                         String[] items = adc();
415                         for (int i = 0; i < items.length;) {
416                                 if (items[i].equals("batt")) {
417                                         monitor_batt = Integer.parseInt(items[i+1]);
418                                         i += 2;
419                                         continue;
420                                 }
421                                 i++;
422                         }
423                         } catch (InterruptedException ie) {
424                         } catch (TimeoutException te) {
425                         }
426                 }
427                 if (monitor_batt == AltosLib.MISSING)
428                         return AltosLib.MISSING;
429                 return AltosConvert.cc_battery_to_voltage(monitor_batt);
430         }
431
432         public AltosLink() {
433                 callsign = "";
434                 has_error = false;
435         }
436 }