altosui: Complete split out of separate java library
[fw/altos] / altosui / AltosSerial.java
1 /*
2  * Copyright © 2010 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 /*
19  * Deal with TeleDongle on a serial port
20  */
21
22 package altosui;
23
24 import java.lang.*;
25 import java.io.*;
26 import java.util.concurrent.*;
27 import java.util.*;
28 import java.text.*;
29 import java.awt.*;
30 import java.awt.event.*;
31 import javax.swing.*;
32 import javax.swing.filechooser.FileNameExtensionFilter;
33 import javax.swing.table.*;
34 import org.altusmetrum.AltosLib.*;
35
36 import libaltosJNI.*;
37
38 /*
39  * This class reads from the serial port and places each received
40  * line in a queue. Dealing with that queue is left up to other
41  * threads.
42  */
43
44 public class AltosSerial implements Runnable {
45
46         static java.util.List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>());
47
48         AltosDevice device;
49         SWIGTYPE_p_altos_file altos;
50         LinkedList<LinkedBlockingQueue<AltosLine>> monitors;
51         LinkedBlockingQueue<AltosLine> reply_queue;
52         Thread input_thread;
53         String line;
54         byte[] line_bytes;
55         int line_count;
56         boolean monitor_mode;
57         int telemetry;
58         double frequency;
59         public static boolean debug;
60         boolean remote;
61         LinkedList<String> pending_output = new LinkedList<String>();
62         Frame frame;
63         AltosConfigData config_data;
64
65         static void set_debug(boolean new_debug) {
66                 debug = new_debug;
67         }
68
69         public void run () {
70                 int c;
71
72                 try {
73                         for (;;) {
74                                 c = libaltos.altos_getchar(altos, 0);
75                                 if (Thread.interrupted())
76                                         break;
77                                 if (c == libaltosConstants.LIBALTOS_ERROR) {
78                                         for (int e = 0; e < monitors.size(); e++) {
79                                                 LinkedBlockingQueue<AltosLine> q = monitors.get(e);
80                                                 q.put(new AltosLine());
81                                         }
82                                         reply_queue.put (new AltosLine());
83                                         break;
84                                 }
85                                 if (c == libaltosConstants.LIBALTOS_TIMEOUT)
86                                         continue;
87                                 if (c == '\r')
88                                         continue;
89                                 synchronized(this) {
90                                         if (c == '\n') {
91                                                 if (line_count != 0) {
92                                                         try {
93                                                                 line = new String(line_bytes, 0, line_count, "UTF-8");
94                                                         } catch (UnsupportedEncodingException ue) {
95                                                                 line = "";
96                                                                 for (int i = 0; i < line_count; i++)
97                                                                         line = line + line_bytes[i];
98                                                         }
99                                                         if (debug)
100                                                                 System.out.printf("\t\t\t\t\t%s\n", line);
101                                                         if (line.startsWith("TELEM") || line.startsWith("VERSION") || line.startsWith("CRC")) {
102                                                                 for (int e = 0; e < monitors.size(); e++) {
103                                                                         LinkedBlockingQueue<AltosLine> q = monitors.get(e);
104                                                                         q.put(new AltosLine (line));
105                                                                 }
106                                                         } else {
107                                                                 reply_queue.put(new AltosLine (line));
108                                                         }
109                                                         line_count = 0;
110                                                         line = "";
111                                                 }
112                                         } else {
113                                                 if (line_bytes == null) {
114                                                         line_bytes = new byte[256];
115                                                 } else if (line_count == line_bytes.length) {
116                                                         byte[] new_line_bytes = new byte[line_count * 2];
117                                                         System.arraycopy(line_bytes, 0, new_line_bytes, 0, line_count);
118                                                         line_bytes = new_line_bytes;
119                                                 }
120                                                 line_bytes[line_count] = (byte) c;
121                                                 line_count++;
122                                         }
123                                 }
124                         }
125                 } catch (InterruptedException e) {
126                 }
127         }
128
129         public void flush_output() {
130                 if (altos != null) {
131                         for (String s : pending_output)
132                                 System.out.print(s);
133                         pending_output.clear();
134                         libaltos.altos_flush(altos);
135                 }
136         }
137
138         boolean         abort;
139         JDialog         timeout_dialog;
140         boolean timeout_started = false;
141
142         private void stop_timeout_dialog() {
143                 if (timeout_started) {
144                         timeout_started = false;
145                         Runnable r = new Runnable() {
146                                         public void run() {
147                                                 timeout_dialog.setVisible(false);
148                                         }
149                                 };
150                         SwingUtilities.invokeLater(r);
151                 }
152         }
153
154         private void start_timeout_dialog_internal() {
155
156                 Object[] options = { "Cancel" };
157
158                 JOptionPane     pane = new JOptionPane();
159                 pane.setMessage(String.format("Connecting to %s, %7.3f MHz", device.toShortString(), frequency));
160                 pane.setOptions(options);
161                 pane.setInitialValue(null);
162
163                 timeout_dialog = pane.createDialog(frame, "Connecting...");
164
165                 timeout_dialog.setVisible(true);
166
167                 Object o = pane.getValue();
168                 if (o == null)
169                         return;
170                 if (options[0].equals(o))
171                         abort = true;
172                 timeout_dialog.dispose();
173                 timeout_dialog = null;
174         }
175
176         private boolean check_timeout() {
177                 if (!timeout_started && frame != null) {
178                         if (!SwingUtilities.isEventDispatchThread()) {
179                                 timeout_started = true;
180                                 Runnable r = new Runnable() {
181                                                 public void run() {
182                                                         start_timeout_dialog_internal();
183                                                 }
184                                         };
185                                 SwingUtilities.invokeLater(r);
186                         }
187                 }
188                 return abort;
189         }
190
191         public void flush_input() throws InterruptedException {
192                 flush_output();
193                 boolean got_some;
194
195                 int timeout = 100;
196                 if (remote)
197                         timeout = 500;
198                 do {
199                         Thread.sleep(timeout);
200                         got_some = !reply_queue.isEmpty();
201                         synchronized(this) {
202                                 if (!"VERSION".startsWith(line) &&
203                                     !line.startsWith("VERSION"))
204                                         line = "";
205                                 reply_queue.clear();
206                         }
207                 } while (got_some);
208         }
209
210         int     in_reply;
211
212         public String get_reply(int timeout) throws InterruptedException {
213                 boolean can_cancel = true;
214                 String  reply = null;
215
216                 try {
217                         ++in_reply;
218
219                         if (SwingUtilities.isEventDispatchThread()) {
220                                 can_cancel = false;
221                                 if (remote)
222                                         System.out.printf("Uh-oh, reading remote serial device from swing thread\n");
223                         }
224                         flush_output();
225                         if (remote && can_cancel) {
226                                 timeout = 500;
227                         }
228                         abort = false;
229                         timeout_started = false;
230                         for (;;) {
231                                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
232                                 if (line != null) {
233                                         stop_timeout_dialog();
234                                         reply = line.line;
235                                         break;
236                                 }
237                                 if (!remote || !can_cancel || check_timeout()) {
238                                         reply = null;
239                                         break;
240                                 }
241                         }
242                 } finally {
243                         --in_reply;
244                 }
245                 return reply;
246         }
247
248         public String get_reply() throws InterruptedException {
249                 return get_reply(5000);
250         }
251
252         public String get_reply_no_dialog(int timeout) throws InterruptedException, TimeoutException {
253                 flush_output();
254                 AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
255                 if (line != null)
256                         return line.line;
257                 return null;
258         }
259
260         public void add_monitor(LinkedBlockingQueue<AltosLine> q) {
261                 set_monitor(true);
262                 monitors.add(q);
263         }
264
265         public void remove_monitor(LinkedBlockingQueue<AltosLine> q) {
266                 monitors.remove(q);
267                 if (monitors.isEmpty())
268                         set_monitor(false);
269         }
270
271         public void close() {
272                 if (remote) {
273                         try {
274                                 stop_remote();
275                         } catch (InterruptedException ie) {
276                         }
277                 }
278                 if (in_reply != 0)
279                         System.out.printf("Uh-oh. Closing active serial device\n");
280
281                 if (altos != null) {
282                         libaltos.altos_close(altos);
283                 }
284                 if (input_thread != null) {
285                         try {
286                                 input_thread.interrupt();
287                                 input_thread.join();
288                         } catch (InterruptedException e) {
289                         }
290                         input_thread = null;
291                 }
292                 if (altos != null) {
293                         libaltos.altos_free(altos);
294                         altos = null;
295                 }
296                 synchronized (devices_opened) {
297                         devices_opened.remove(device.getPath());
298                 }
299                 if (debug)
300                         System.out.printf("Closing %s\n", device.getPath());
301         }
302
303         private void putc(char c) {
304                 if (altos != null)
305                         libaltos.altos_putchar(altos, c);
306         }
307
308         public void print(String data) {
309                 if (debug)
310                         pending_output.add(data);
311                 for (int i = 0; i < data.length(); i++)
312                         putc(data.charAt(i));
313         }
314
315         public void printf(String format, Object ... arguments) {
316                 print(String.format(format, arguments));
317         }
318
319         private void open() throws FileNotFoundException, AltosSerialInUseException {
320                 synchronized (devices_opened) {
321                         if (devices_opened.contains(device.getPath()))
322                                 throw new AltosSerialInUseException(device);
323                         devices_opened.add(device.getPath());
324                 }
325                 altos = device.open();
326                 if (altos == null) {
327                         final String    message = device.getErrorString();
328                         close();
329                         throw new FileNotFoundException(String.format("%s (%s)",
330                                                                       device.toShortString(), message));
331                 }
332                 if (debug)
333                         System.out.printf("Open %s\n", device.getPath());
334                 input_thread = new Thread(this);
335                 input_thread.start();
336                 print("~\nE 0\n");
337                 set_monitor(false);
338                 flush_output();
339         }
340
341         private int telemetry_len() {
342                 return Altos.telemetry_len(telemetry);
343         }
344
345         private void set_channel(int channel) {
346                 if (altos != null) {
347                         if (monitor_mode)
348                                 printf("m 0\nc r %d\nm %x\n",
349                                        channel, telemetry_len());
350                         else
351                                 printf("c r %d\n", channel);
352                         flush_output();
353                 }
354         }
355
356         private void set_radio_setting(int setting) {
357                 if (altos != null) {
358                         if (monitor_mode)
359                                 printf("m 0\nc R %d\nm %x\n",
360                                        setting, telemetry_len());
361                         else
362                                 printf("c R %d\n", setting);
363                         flush_output();
364                 }
365         }
366
367         private void set_radio_freq(int frequency) {
368                 if (altos != null) {
369                         if (monitor_mode)
370                                 printf("m 0\nc F %d\nm %x\n",
371                                        frequency, telemetry_len());
372                         else
373                                 printf("c F %d\n", frequency);
374                         flush_output();
375                 }
376         }
377
378         public void set_radio_frequency(double frequency,
379                                         boolean has_frequency,
380                                         boolean has_setting,
381                                         int cal) {
382                 if (debug)
383                         System.out.printf("set_radio_frequency %7.3f (freq %b) (set %b) %d\n", frequency, has_frequency, has_setting, cal);
384                 if (frequency == 0)
385                         return;
386                 if (has_frequency)
387                         set_radio_freq((int) Math.floor (frequency * 1000));
388                 else if (has_setting)
389                         set_radio_setting(AltosConvert.radio_frequency_to_setting(frequency, cal));
390                 else
391                         set_channel(AltosConvert.radio_frequency_to_channel(frequency));
392         }
393
394         public void set_radio_frequency(double in_frequency) throws InterruptedException, TimeoutException {
395                 frequency = in_frequency;
396                 if (frequency == 0.0)
397                         frequency = AltosPreferences.frequency(device.getSerial());
398                 config_data();
399                 set_radio_frequency(frequency,
400                                     config_data.radio_frequency != 0,
401                                     config_data.radio_setting != 0,
402                                     config_data.radio_calibration);
403         }
404
405         public void set_telemetry(int in_telemetry) {
406                 telemetry = in_telemetry;
407                 if (altos != null) {
408                         if (monitor_mode)
409                                 printf("m 0\nm %x\n", telemetry_len());
410                         flush_output();
411                 }
412         }
413
414         void set_monitor(boolean monitor) {
415                 monitor_mode = monitor;
416                 if (altos != null) {
417                         if (monitor)
418                                 printf("m %x\n", telemetry_len());
419                         else
420                                 printf("m 0\n");
421                         flush_output();
422                 }
423         }
424
425         public void set_callsign(String callsign) {
426                 if (altos != null) {
427                         printf ("c c %s\n", callsign);
428                         flush_output();
429                 }
430         }
431
432         public AltosConfigData config_data() throws InterruptedException, TimeoutException {
433                 if (config_data == null)
434                         config_data = new AltosConfigData(this);
435                 return config_data;
436         }
437
438         public void start_remote() throws TimeoutException, InterruptedException {
439                 if (debug)
440                         System.out.printf("start remote %7.3f\n", frequency);
441                 if (frequency == 0.0)
442                         frequency = AltosUIPreferences.frequency(device.getSerial());
443                 set_radio_frequency(frequency);
444                 set_callsign(AltosUIPreferences.callsign());
445                 printf("p\nE 0\n");
446                 flush_input();
447                 remote = true;
448         }
449
450         public void stop_remote() throws InterruptedException {
451                 if (debug)
452                         System.out.printf("stop remote\n");
453                 try {
454                         flush_input();
455                 } finally {
456                         printf ("~\n");
457                         flush_output();
458                 }
459                 remote = false;
460         }
461
462         public void set_frame(Frame in_frame) {
463                 frame = in_frame;
464         }
465
466         public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
467                 device = in_device;
468                 line = "";
469                 monitor_mode = false;
470                 frame = null;
471                 telemetry = Altos.ao_telemetry_standard;
472                 monitors = new LinkedList<LinkedBlockingQueue<AltosLine>> ();
473                 reply_queue = new LinkedBlockingQueue<AltosLine> ();
474                 open();
475         }
476 }