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