001package jmri.jmrix.loconet.bluetooth; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.io.OutputStream; 008import java.util.Vector; 009 010import javax.annotation.Nonnull; 011import javax.bluetooth.BluetoothStateException; 012import javax.bluetooth.DeviceClass; 013import javax.bluetooth.DiscoveryAgent; 014import javax.bluetooth.DiscoveryListener; 015import javax.bluetooth.LocalDevice; 016import javax.bluetooth.RemoteDevice; 017import javax.bluetooth.ServiceRecord; 018import javax.bluetooth.UUID; 019import javax.microedition.io.Connection; 020import javax.microedition.io.Connector; 021import javax.microedition.io.StreamConnection; 022import jmri.jmrix.ConnectionStatus; 023import jmri.jmrix.loconet.LnPacketizer; 024import jmri.jmrix.loconet.LnPortController; 025import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * Provide access to LocoNet via a LocoNet Bluetooth adapter. 031 */ 032public class LocoNetBluetoothAdapter extends LnPortController { 033 034 public LocoNetBluetoothAdapter() { 035 this(new LocoNetSystemConnectionMemo()); 036 } 037 038 public LocoNetBluetoothAdapter(LocoNetSystemConnectionMemo adapterMemo) { 039 super(adapterMemo); 040 option1Name = "CommandStation"; // NOI18N 041 option2Name = "TurnoutHandle"; // NOI18N 042 options.put(option1Name, new Option(Bundle.getMessage("CommandStationTypeLabel"), commandStationNames, false)); 043 options.put(option2Name, new Option(Bundle.getMessage("TurnoutHandling"), 044 new String[]{Bundle.getMessage("HandleNormal"), Bundle.getMessage("HandleSpread"), Bundle.getMessage("HandleOneOnly"), Bundle.getMessage("HandleBoth")})); // I18N 045 } 046 047 @Override 048 public Vector<String> getPortNames() { 049 return LocoNetBluetoothAdapter.discoverPortNames(); 050 } 051 052 @Override 053 public String openPort(String portName, String appName) { 054 int[] responseCode = new int[]{-1}; 055 Exception[] exception = new Exception[]{null}; 056 try { 057 // Find the RemoteDevice with this name. 058 RemoteDevice[] devices = LocalDevice.getLocalDevice().getDiscoveryAgent().retrieveDevices(DiscoveryAgent.PREKNOWN); 059 if (devices != null) { 060 for (RemoteDevice device : devices) { 061 if (device.getFriendlyName(false).equals(portName)) { 062 Object[] waitObj = new Object[0]; 063 // Start a search for a serialport service (UUID 0x1101) 064 LocalDevice.getLocalDevice().getDiscoveryAgent().searchServices(new int[]{0x0100}, new UUID[]{new UUID(0x1101)}, device, new DiscoveryListener() { 065 @Override 066 public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { 067 synchronized (waitObj) { 068 for (ServiceRecord service : servRecord) { 069 // Service found, get url for connection. 070 String url = service.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); 071 if (url == null) { 072 continue; 073 } 074 try { 075 // Open connection. 076 Connection conn = Connector.open(url, Connector.READ_WRITE); 077 if (conn instanceof StreamConnection) { // The connection should be a StreamConnection, otherwise it's a one way communication. 078 StreamConnection stream = (StreamConnection) conn; 079 in = stream.openInputStream(); 080 out = stream.openOutputStream(); 081 opened = true; 082 // Port is open, let openPort continue. 083 //waitObj.notify(); 084 } else { 085 throw new IOException("Could not establish a two-way communication"); 086 } 087 } catch (IOException IOe) { 088 exception[0] = IOe; 089 } 090 } 091 if (!opened) { 092 exception[0] = new IOException("No service found to connect to"); 093 } 094 } 095 } 096 097 @Override 098 public void serviceSearchCompleted(int transID, int respCode) { 099 synchronized (waitObj) { 100 // Search for services complete, if the port was not opened, save the response code for error analysis. 101 responseCode[0] = respCode; 102 // Search completer, let openPort continue. 103 waitObj.notify(); 104 } 105 } 106 107 @Override 108 public void inquiryCompleted(int discType) { 109 } 110 111 @Override 112 public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { 113 } 114 }); 115 synchronized (waitObj) { 116 // Wait until either the port is open on the search has returned a response code. 117 while (!opened && responseCode[0] == -1) { 118 try { 119 // Wait for search to complete. 120 waitObj.wait(); 121 } catch (InterruptedException ex) { 122 log.error("Thread unexpectedly interrupted", ex); 123 } 124 } 125 } 126 break; 127 } 128 } 129 } 130 } catch (BluetoothStateException BSe) { 131 log.error("Exception when using bluetooth"); 132 return BSe.getLocalizedMessage(); 133 } catch (IOException IOe) { 134 log.error("Unknown IOException when establishing connection to {}", portName); 135 return IOe.getLocalizedMessage(); 136 } 137 138 if (!opened) { 139 ConnectionStatus.instance().setConnectionState(null, portName, ConnectionStatus.CONNECTION_DOWN); 140 if (exception[0] != null) { 141 log.error("Exception when connecting to {}", portName); 142 return exception[0].getLocalizedMessage(); 143 } 144 switch (responseCode[0]) { 145 case DiscoveryListener.SERVICE_SEARCH_COMPLETED: 146 log.error("Bluetooth connection {} not opened, unknown error", portName); 147 return "Unknown error: failed to connect to " + portName; 148 case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE: 149 log.error("Bluetooth device {} could not be reached", portName); 150 return "Could not find " + portName; 151 case DiscoveryListener.SERVICE_SEARCH_ERROR: 152 log.error("Error when searching for {}", portName); 153 return "Error when searching for " + portName; 154 case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS: 155 log.error("No serial service found on {}", portName); 156 return "Invalid bluetooth device: " + portName; 157 case DiscoveryListener.SERVICE_SEARCH_TERMINATED: 158 log.error("Service search on {} ended prematurely", portName); 159 return "Search for " + portName + " ended unexpectedly"; 160 default: 161 log.warn("Unhandled response code: {}", responseCode[0]); 162 break; 163 } 164 log.error("Unknown error when connecting to {}", portName); 165 return "Unknown error when connecting to " + portName; 166 } 167 168 return null; // normal operation 169 } 170 171 /** 172 * Set up all of the other objects to operate. 173 */ 174 @Override 175 public void configure() { 176 setCommandStationType(getOptionState(option1Name)); 177 setTurnoutHandling(getOptionState(option2Name)); 178 // connect to a packetizing traffic controller 179 LnPacketizer packets = new LnPacketizer(this.getSystemConnectionMemo()); 180 packets.connectPort(this); 181 182 // create memo 183 this.getSystemConnectionMemo().setLnTrafficController(packets); 184 // do the common manager config 185 186 this.getSystemConnectionMemo().configureCommandStation(commandStationType, 187 mTurnoutNoRetry, mTurnoutExtraSpace, mTranspondingAvailable, mInterrogateAtStart, mLoconetProtocolAutoDetect); 188 this.getSystemConnectionMemo().configureManagers(); 189 190 // start operation 191 packets.startThreads(); 192 } 193 194 // base class methods for the LnPortController interface 195 @Override 196 public DataInputStream getInputStream() { 197 if (!opened) { 198 log.error("getInputStream called before load(), stream not available"); 199 return null; 200 } 201 return new DataInputStream(in); 202 } 203 204 @Override 205 public DataOutputStream getOutputStream() { 206 if (!opened) { 207 log.error("getOutputStream called before load(), stream not available"); 208 } 209 return new DataOutputStream(out); 210 } 211 212 @Override 213 public boolean status() { 214 return opened; 215 } 216 217 // private control members 218 private boolean opened = false; 219 private InputStream in = null; 220 private OutputStream out = null; 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public String[] validBaudRates() { 227 return new String[]{}; 228 } 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override 234 public int[] validBaudNumbers() { 235 return new int[]{}; 236 } 237 238 @Nonnull 239 protected static Vector<String> discoverPortNames() { 240 Vector<String> portNameVector = new Vector<>(); 241 try { 242 RemoteDevice[] devices = LocalDevice.getLocalDevice().getDiscoveryAgent().retrieveDevices(DiscoveryAgent.PREKNOWN); 243 if (devices != null) { 244 for (RemoteDevice device : devices) { 245 portNameVector.add(device.getFriendlyName(false)); 246 } 247 } 248 } catch (IOException ex) { 249 log.error("Unable to use bluetooth device", ex); 250 } 251 return portNameVector; 252 } 253 254 private final static Logger log = LoggerFactory.getLogger(LocoNetBluetoothAdapter.class); 255 256}