View Javadoc
1   package fr.ifremer.tutti.ichtyometer;
2   
3   /*
4    * #%L
5    * Tutti :: Ichtyometer API
6    * $Id$
7    * $HeadURL$
8    * %%
9    * Copyright (C) 2012 - 2014 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the 
14   * License, or (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public 
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  import com.google.common.base.Preconditions;
28  import com.google.common.collect.Maps;
29  import com.google.common.collect.Sets;
30  import com.intel.bluetooth.BlueCoveImpl;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import javax.bluetooth.BluetoothStateException;
35  import javax.bluetooth.LocalDevice;
36  import javax.bluetooth.RemoteDevice;
37  import javax.bluetooth.ServiceRecord;
38  import javax.microedition.io.Connector;
39  import javax.microedition.io.StreamConnection;
40  import java.io.Closeable;
41  import java.io.IOException;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Set;
45  
46  /**
47   * Ichtyometer bluetooth client.
48   *
49   * Created on 10/11/13.
50   *
51   * @author Tony Chemit - chemit@codelutin.com
52   * @since 3.1
53   */
54  public class IchtyometerClient implements Closeable {
55  
56      /** Logger. */
57      private static final Log log = LogFactory.getLog(IchtyometerClient.class);
58  
59      /**
60       * To keep a cache of discovered remote device. Keys are the name of device, values are the remote device.
61       */
62      protected static Map<String, RemoteDevice> REMOTE_DEVICE_CACHE;
63  
64      /**
65       * To keep a cache of discovered connection url on remote devices. Keys are the name of device, values are
66       * the connection of the remote device.
67       */
68      protected static Map<String, String> REMOTE_CONNECTION_URL_CACHE;
69  
70      private final int maximumNumberOfTryToConnect;
71  
72      public IchtyometerClient(int maximumNumberOfTryToConnect) {
73          this.maximumNumberOfTryToConnect = maximumNumberOfTryToConnect;
74      }
75  
76      public static void clearRemoteDeviceCaches() {
77          REMOTE_DEVICE_CACHE = null;
78          REMOTE_CONNECTION_URL_CACHE = null;
79      }
80  
81      /**
82       * Local bluetooth device.
83       */
84      protected LocalDevice localDevice;
85  
86      /**
87       * Set of opened connections.
88       */
89      protected final Set<StreamConnection> connections = Sets.newHashSet();
90  
91      /**
92       * Remote BigFin bluetooth device.
93       */
94      protected RemoteDevice remoteDevice;
95  
96      /**
97       * Url connection to the remote device.
98       */
99      protected String connectionUrl;
100 
101     /**
102      * Flag set to {@code true} if {@link #open(RemoteDeviceChooser, boolean)} method was invoked with success.
103      */
104     protected boolean open;
105 
106     /**
107      * Friendly name of the device.
108      */
109     protected String name;
110 
111     public void open(RemoteDeviceChooser remoteDeviceChooser, boolean forceCompleteScan) throws IOException {
112 
113         Preconditions.checkState(!open, "Client is already opened");
114 
115         try {
116             localDevice = LocalDevice.getLocalDevice();
117         } catch (BluetoothStateException e) {
118             throw new LocalDeviceNotFoundException();
119         }
120 
121         if (forceCompleteScan || REMOTE_DEVICE_CACHE == null) {
122 
123             // build map of remote devices
124 
125             REMOTE_DEVICE_CACHE = discoverRemoteDevices();
126 
127         }
128 
129         if (REMOTE_DEVICE_CACHE.isEmpty()) {
130             throw new RemoteDeviceNotFoundException("No remote device found");
131         }
132 
133         // ask user to choose the device
134         name = remoteDeviceChooser.chooseRemoteDevice(REMOTE_DEVICE_CACHE.keySet());
135 
136         if (name == null) {
137             throw new RemoteDeviceNotFoundException("User did not choose a remote device");
138         }
139 
140         remoteDevice = REMOTE_DEVICE_CACHE.get(name);
141 
142         if (remoteDevice == null) {
143             throw new RemoteDeviceNotFoundException(
144                     "Could not find remote device with name '" + name + "'");
145         }
146 
147         if (forceCompleteScan || REMOTE_CONNECTION_URL_CACHE == null) {
148             REMOTE_CONNECTION_URL_CACHE = Maps.newTreeMap();
149         }
150 
151         if (!REMOTE_CONNECTION_URL_CACHE.containsKey(name)) {
152 
153             int serviceIndex = 3;
154 
155             List<ServiceRecord> serviceRecords = discoverServiceRecords(serviceIndex);
156 
157             if (log.isInfoEnabled()) {
158                 log.info("Found some services for index: " + serviceIndex);
159             }
160             
161             // take first service record
162             ServiceRecord serviceRecord = serviceRecords.get(0);
163 
164             // get connection url
165             String url = serviceRecord.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, true);
166 
167             if (log.isInfoEnabled()) {
168                 log.info("Found service(" + serviceIndex + "): " + url + ", name: " + name);
169             }
170             REMOTE_CONNECTION_URL_CACHE.put(name, url);
171 
172         }
173 
174         // get connection url
175         connectionUrl = REMOTE_CONNECTION_URL_CACHE.get(name);
176 
177         open = true;
178     }
179 
180     @Override
181     public void close() throws IOException {
182 
183         if (!open) {
184             return;
185         }
186         try {
187             for (StreamConnection connection : Sets.newHashSet(connections)) {
188                 closeConnection(connection);
189             }
190         } finally {
191 
192             BlueCoveImpl.shutdown();
193         }
194     }
195 
196     public StreamConnection openConnection() throws IOException {
197 
198         checkIsOpened();
199 
200         StreamConnection connection = (StreamConnection) Connector.open(connectionUrl, 2);
201         connections.add(connection);
202         return connection;
203     }
204 
205     public void closeConnection(StreamConnection connection) throws IOException {
206         checkIsOpened();
207         boolean remove = connections.remove(connection);
208         if (!remove) {
209             throw new IllegalArgumentException("Connection is not coming from this client, won't close it!");
210         }
211         connection.close();
212     }
213 
214     public boolean isOpen() {
215         return open;
216     }
217 
218     public String getName() {
219         checkIsOpened();
220         return name;
221     }
222 
223     protected void checkIsOpened() {
224         if (!open) {
225             throw new IllegalStateException("Client is not opened!");
226         }
227     }
228 
229     protected Map<String, RemoteDevice> discoverRemoteDevices() throws RemoteDeviceNotFoundException {
230 
231         RemoteDevicesFinder remoteDevicesFinder = new RemoteDevicesFinder(maximumNumberOfTryToConnect, this);
232 
233         Map<String, RemoteDevice> devices;
234         try {
235             devices = remoteDevicesFinder.findDevices(localDevice);
236 
237         } catch (Exception e) {
238             throw new RemoteDeviceNotFoundException("Could not detected devices", e);
239         }
240 
241         return devices;
242 
243     }
244 
245     protected List<ServiceRecord> discoverServiceRecords(int serviceIndex) throws RemoteDeviceNotFoundException, RemoteDeviceServiceNotFoundException {
246 
247         ServiceRecordsFinder serviceRecordsFinder = new ServiceRecordsFinder(maximumNumberOfTryToConnect, this);
248         List<ServiceRecord> serviceRecords;
249         try {
250             serviceRecords = serviceRecordsFinder.findServices(localDevice, remoteDevice, serviceIndex);
251         } catch (Exception e) {
252             throw new RemoteDeviceNotFoundException("Could not read remote device services", e);
253         }
254 
255         if (serviceRecords.isEmpty()) {
256             throw new RemoteDeviceServiceNotFoundException("No services detected.");
257         }
258 
259         return serviceRecords;
260 
261     }
262 
263 
264 }