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( 140 getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 141 if (exception[0] != null) { 142 log.error("Exception when connecting to {}", portName); 143 return exception[0].getLocalizedMessage(); 144 } 145 switch (responseCode[0]) { 146 case DiscoveryListener.SERVICE_SEARCH_COMPLETED: 147 log.error("Bluetooth connection {} not opened, unknown error", portName); 148 return "Unknown error: failed to connect to " + portName; 149 case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE: 150 log.error("Bluetooth device {} could not be reached", portName); 151 return "Could not find " + portName; 152 case DiscoveryListener.SERVICE_SEARCH_ERROR: 153 log.error("Error when searching for {}", portName); 154 return "Error when searching for " + portName; 155 case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS: 156 log.error("No serial service found on {}", portName); 157 return "Invalid bluetooth device: " + portName; 158 case DiscoveryListener.SERVICE_SEARCH_TERMINATED: 159 log.error("Service search on {} ended prematurely", portName); 160 return "Search for " + portName + " ended unexpectedly"; 161 default: 162 log.warn("Unhandled response code: {}", responseCode[0]); 163 break; 164 } 165 log.error("Unknown error when connecting to {}", portName); 166 return "Unknown error when connecting to " + portName; 167 } 168 169 return null; // normal operation 170 } 171 172 /** 173 * Set up all of the other objects to operate. 174 */ 175 @Override 176 public void configure() { 177 setCommandStationType(getOptionState(option1Name)); 178 setTurnoutHandling(getOptionState(option2Name)); 179 // connect to a packetizing traffic controller 180 LnPacketizer packets = new LnPacketizer(this.getSystemConnectionMemo()); 181 packets.connectPort(this); 182 183 // create memo 184 this.getSystemConnectionMemo().setLnTrafficController(packets); 185 // do the common manager config 186 187 this.getSystemConnectionMemo().configureCommandStation(commandStationType, 188 mTurnoutNoRetry, mTurnoutExtraSpace, mTranspondingAvailable, mInterrogateAtStart, mLoconetProtocolAutoDetect); 189 this.getSystemConnectionMemo().configureManagers(); 190 191 // start operation 192 packets.startThreads(); 193 } 194 195 // base class methods for the LnPortController interface 196 @Override 197 public DataInputStream getInputStream() { 198 if (!opened) { 199 log.error("getInputStream called before load(), stream not available"); 200 return null; 201 } 202 return new DataInputStream(in); 203 } 204 205 @Override 206 public DataOutputStream getOutputStream() { 207 if (!opened) { 208 log.error("getOutputStream called before load(), stream not available"); 209 } 210 return new DataOutputStream(out); 211 } 212 213 @Override 214 public boolean status() { 215 return opened; 216 } 217 218 // private control members 219 private boolean opened = false; 220 private InputStream in = null; 221 private OutputStream out = null; 222 223 /** 224 * {@inheritDoc} 225 */ 226 @Override 227 public String[] validBaudRates() { 228 return new String[]{}; 229 } 230 231 /** 232 * {@inheritDoc} 233 */ 234 @Override 235 public int[] validBaudNumbers() { 236 return new int[]{}; 237 } 238 239 @Nonnull 240 protected static Vector<String> discoverPortNames() { 241 Vector<String> portNameVector = new Vector<>(); 242 try { 243 RemoteDevice[] devices = LocalDevice.getLocalDevice().getDiscoveryAgent().retrieveDevices(DiscoveryAgent.PREKNOWN); 244 if (devices != null) { 245 for (RemoteDevice device : devices) { 246 portNameVector.add(device.getFriendlyName(false)); 247 } 248 } 249 } catch (IOException ex) { 250 log.error("Unable to use bluetooth device", ex); 251 } 252 return portNameVector; 253 } 254 255 private static final Logger log = LoggerFactory.getLogger(LocoNetBluetoothAdapter.class); 256 257}