da75ffddaf5ffdbd0a8c82049a16cba7f650e751
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / AltosBluetooth.java
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  * Copyright © 2012 Mike Beattie <mike@ethernal.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.AltosDroid;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.UUID;
25
26 import android.bluetooth.BluetoothAdapter;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothSocket;
29 //import android.os.Bundle;
30 import android.os.Handler;
31 //import android.os.Message;
32 import android.util.Log;
33
34 import org.altusmetrum.altoslib_6.*;
35
36 public class AltosBluetooth extends AltosLink {
37
38         // Debugging
39         private static final String TAG = "AltosBluetooth";
40         private static final boolean D = true;
41
42         private ConnectThread    connect_thread = null;
43         private Thread           input_thread   = null;
44
45         private Handler          handler;
46
47         private BluetoothAdapter adapter;
48         private BluetoothSocket  socket;
49         private InputStream      input;
50         private OutputStream     output;
51
52         // Constructor
53         public AltosBluetooth(BluetoothDevice device, Handler handler) {
54 //              set_debug(D);
55                 adapter = BluetoothAdapter.getDefaultAdapter();
56                 this.handler = handler;
57
58                 create_socket(device);
59                 connect_thread = new ConnectThread();
60                 connect_thread.start();
61         }
62
63         private Object closed_lock = new Object();
64         private boolean closed = false;
65
66         private boolean closed() {
67                 synchronized(closed_lock) {
68                         return closed;
69                 }
70         }
71
72         private void connected() {
73                 if (closed()) {
74                         if (D) Log.d(TAG, "connected after closed");
75                         return;
76                 }
77
78                 try {
79                         synchronized(this) {
80                                 if (socket != null) {
81                                         input = socket.getInputStream();
82                                         output = socket.getOutputStream();
83
84                                         input_thread = new Thread(this);
85                                         input_thread.start();
86
87                                         // Configure the newly connected device for telemetry
88                                         print("~\nE 0\n");
89                                         set_monitor(false);
90                                         if (D) Log.d(TAG, "ConnectThread: connected");
91
92                                         /* Let TelemetryService know we're connected
93                                          */
94                                         handler.obtainMessage(TelemetryService.MSG_CONNECTED, this).sendToTarget();
95
96                                         /* Notify other waiting threads that we're connected now
97                                          */
98                                         notifyAll();
99                                 }
100                         }
101                 } catch (IOException io) {
102                         connect_failed();
103                 }
104         }
105
106         private void connect_failed() {
107                 if (closed()) {
108                         if (D) Log.d(TAG, "connect_failed after closed");
109                         return;
110                 }
111
112                 close_socket();
113                 input = null;
114                 output = null;
115                 handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget();
116                 if (D) Log.e(TAG, "ConnectThread: Failed to establish connection");
117         }
118
119         private void disconnected() {
120                 if (closed()) {
121                         if (D) Log.d(TAG, "disconnected after closed");
122                         return;
123                 }
124
125                 if (D) Log.d(TAG, "Sending disconnected message");
126                 handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget();
127         }
128
129         private void close_socket() {
130                 BluetoothSocket tmp_socket;
131
132                 synchronized(this) {
133                         tmp_socket = socket;
134                         socket = null;
135                 }
136
137                 if (tmp_socket != null) {
138                         try {
139                                 tmp_socket.close();
140                         } catch (IOException e) {
141                                 if (D) Log.e(TAG, "close_socket failed");
142                         }
143                 }
144         }
145
146         private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
147
148         private void create_socket(BluetoothDevice  device) {
149
150                 BluetoothSocket tmp_socket = null;
151
152                 try {
153                         tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
154                 } catch (IOException e) {
155                         e.printStackTrace();
156                 }
157                 if (socket != null) {
158                         if (D) Log.d(TAG, String.format("Socket already allocated %s", socket.toString()));
159                         close_socket();
160                 }
161                 synchronized (this) {
162                         socket = tmp_socket;
163                 }
164         }
165
166         private class ConnectThread extends Thread {
167
168                 public void run() {
169                         if (D) Log.d(TAG, "ConnectThread: BEGIN");
170                         setName("ConnectThread");
171
172                         // Always cancel discovery because it will slow down a connection
173                         try {
174                                 adapter.cancelDiscovery();
175                         } catch (Exception e) {
176                                 if (D) Log.d(TAG, String.format("cancelDiscovery exception %s", e.toString()));
177                         }
178
179                         BluetoothSocket local_socket = null;
180
181                         synchronized (AltosBluetooth.this) {
182                                 if (!closed())
183                                         local_socket = socket;
184                         }
185
186                         if (local_socket != null) {
187                                 try {
188                                         // Make a connection to the BluetoothSocket
189                                         // This is a blocking call and will only return on a
190                                         // successful connection or an exception
191                                         local_socket.connect();
192                                 } catch (IOException e) {
193                                         if (D) Log.d(TAG, String.format("Connect exception %s", e.toString()));
194                                         local_socket = null;
195                                 }
196                         }
197
198                         if (local_socket != null) {
199                                 connected();
200                         } else {
201                                 connect_failed();
202                         }
203
204                         if (D) Log.d(TAG, "ConnectThread: completed");
205                 }
206         }
207
208         public double frequency() {
209                 return frequency;
210         }
211
212         public int telemetry_rate() {
213                 return telemetry_rate;
214         }
215
216         public void save_frequency() {
217                 AltosPreferences.set_frequency(0, frequency);
218         }
219
220         public void save_telemetry_rate() {
221                 AltosPreferences.set_telemetry_rate(0, telemetry_rate);
222         }
223
224         private synchronized void wait_connected() throws InterruptedException, IOException {
225                 if (input == null && socket != null) {
226                         if (D) Log.d(TAG, "wait_connected...");
227                         wait();
228                         if (D) Log.d(TAG, "wait_connected done");
229                 }
230                 if (socket == null)
231                         throw new IOException();
232         }
233
234         public void print(String data) {
235                 byte[] bytes = data.getBytes();
236                 if (D) Log.d(TAG, "print(): begin");
237                 try {
238                         wait_connected();
239                         output.write(bytes);
240                         if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
241                 } catch (IOException e) {
242                         disconnected();
243                 } catch (InterruptedException e) {
244                         disconnected();
245                 }
246         }
247
248         public void putchar(byte c) {
249                 byte[] bytes = { c };
250                 if (D) Log.d(TAG, "print(): begin");
251                 try {
252                         wait_connected();
253                         output.write(bytes);
254                         if (D) Log.d(TAG, "print(): Wrote byte: '" + c + "'");
255                 } catch (IOException e) {
256                         disconnected();
257                 } catch (InterruptedException e) {
258                         disconnected();
259                 }
260         }
261
262         private static final int buffer_size = 1024;
263
264         private byte[] buffer = new byte[buffer_size];
265         private int buffer_len = 0;
266         private int buffer_off = 0;
267
268         private byte[] debug_chars = new byte[buffer_size];
269         private int debug_off;
270
271         private void debug_input(byte b) {
272                 if (b == '\n') {
273                         Log.d(TAG, "            " + new String(debug_chars, 0, debug_off));
274                         debug_off = 0;
275                 } else {
276                         if (debug_off < buffer_size)
277                                 debug_chars[debug_off++] = b;
278                 }
279         }
280
281         public int getchar() {
282                 while (buffer_off == buffer_len) {
283                         try {
284                                 wait_connected();
285                                 buffer_len = input.read(buffer);
286                                 buffer_off = 0;
287                         } catch (IOException e) {
288                                 if (D) Log.d(TAG, "getchar IOException");
289                                 disconnected();
290                                 return AltosLink.ERROR;
291                         } catch (java.lang.InterruptedException e) {
292                                 if (D) Log.d(TAG, "getchar Interrupted");
293                                 disconnected();
294                                 return AltosLink.ERROR;
295                         }
296                 }
297                 if (D)
298                         debug_input(buffer[buffer_off]);
299                 return buffer[buffer_off++];
300         }
301
302         public void closing() {
303                 synchronized(closed_lock) {
304                         if (D) Log.d(TAG, "Marked closed true");
305                         closed = true;
306                 }
307         }
308
309
310         public void close() {
311                 if (D) Log.d(TAG, "close(): begin");
312
313                 closing();
314
315                 close_socket();
316
317                 synchronized(this) {
318
319                         if (input_thread != null) {
320                                 if (D) Log.d(TAG, "close(): stopping input_thread");
321                                 try {
322                                         if (D) Log.d(TAG, "close(): input_thread.interrupt().....");
323                                         input_thread.interrupt();
324                                         if (D) Log.d(TAG, "close(): input_thread.join().....");
325                                         input_thread.join();
326                                 } catch (Exception e) {}
327                                 input_thread = null;
328                         }
329                         input = null;
330                         output = null;
331                         notifyAll();
332                 }
333         }
334
335         //public void flush_output() { super.flush_output(); }
336
337         // Stubs of required methods when extending AltosLink
338         public boolean can_cancel_reply()   { return false; }
339         public boolean show_reply_timeout() { return true; }
340         public void hide_reply_timeout()    { }
341
342 }