I've been facing this issue while connecting OBDII devices. This issue's occurrence is very random varied across cheap Chinese OBD devices to expensive branded ones.
For Bluetooth communication in flutter, I'm using the Flutter Bluetooth Serial package https://pub.dev/packages/flutter_bluetooth_serial
What I'm doing is initially creating a socket via createRfcommSocketToServiceRecord method. When IOException occurs, then the socket is created via createRfcommSocket reflection method. If still, the IOException occurs, then the Input and Output streams and socket are closed manually and then the connection is tried again. I try connection a total of 3 times.
Here is the connect function which I changed in the package.
public void connect() throws IOException {
if (isConnected()) {
throw new IOException("already connected");
}
BluetoothSocket fallbackSocket = null;
boolean isConnectionEstablished = false;
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(mRemoteAddress);
if (device == null) {
throw new IOException("device not found");
}
mBluetoothSocket = device.createRfcommSocketToServiceRecord(DEFAULT_UUID); // #TODO . introduce ConnectionMethod
if (mBluetoothSocket == null) {
throw new IOException("socket connection not established");
}
// Cancel discovery, even though we didn't start it
bluetoothAdapter.cancelDiscovery();
if (mFirstConnection == 0)
mFirstConnection = 1;
try {
mBluetoothSocket.connect();
isConnectionEstablished = true;
} catch (IOException ioException) {
Log.e(TAG, "IO exception: " + ioException.getMessage());
try {
Class<?> clazz = mBluetoothSocket.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[] {Integer.valueOf(1)};
mFallBackSocket = (BluetoothSocket)m.invoke(mBluetoothSocket.getRemoteDevice(), params);
mBluetoothSocket = mFallBackSocket;
if (mBluetoothSocket != null) {
mBluetoothSocket.connect();
isConnectionEstablished = true;
}else{
Log.d(TAG, "fallback_socket received null....: " + mBluetoothSocket);
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | IOException e) {
Log.e(TAG, "exception_in_code....: " + e);
e.printStackTrace();
}
}
if (isConnectionEstablished) {
connectionThread = new ConnectionThread(mBluetoothSocket);
connectionThread.start();
} else {
if (mFirstConnection == 1) {
mFirstConnection = 2;
resetFallBackSocket();
reconnectSocket();
} else if (mFirstConnection == 2) {
mFirstConnection = 3;
resetFallBackSocket();
reconnectSocket();
} else {
mFirstConnection = 0;
resetFallBackSocket();
throw new IOException("socket connection not established...");
}
}
}
public void resetFallBackSocket() {
mFallBackSocket = null;
}
reconnectSocket is the function in which the Input and Output streams and Socket is closed manually and connection is tried again.
public void reconnectSocket() throws IOException {
Log.e(TAG, "debug13. Reconnection Bluetooth socket..");
dataLogsFinal = dataLogsFinal + "debug13. Reconnection Bluetooth socket..$";
Log.d(TAG, "Reconnection Bluetooth socket...");
if (mBluetoothSocket == null)
throw new IOException("Bluetooth Socket is NULL!" + dataLogsFinal);
StringBuilder errorBuilder = new StringBuilder();
try {
mBluetoothSocket.getInputStream().close();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
Log.e(TAG, "Error closing input stream: " + e.getMessage());
errorBuilder.append("Error closing input stream: ").append(e.getMessage()).append(" | ");
}
try {
mBluetoothSocket.getOutputStream().close();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
Log.e(TAG, "Error closing output stream: " + e.getMessage());
errorBuilder.append("Error closing output stream: ").append(e.getMessage()).append(" | ");
}
try {
mBluetoothSocket.close();
Thread.sleep(1000);
} catch (IOException | InterruptedException | NullPointerException e) {
e.printStackTrace();
Log.e(TAG, "Error closing bluetooth socket: " + e.getMessage());
errorBuilder.append("Error closing bluetooth socket: ").append(e.getMessage()).append(" | ");
}
try {
connect();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Error starting service: " + e.getMessage());
errorBuilder.append("Error starting service: ").append(e.getMessage());
throw new IOException(errorBuilder.toString());
}
}
I've tried changing the channel value for the port ranging from 1-5 for the reflection method.
Port-1:- Most of the time the connection gets created.
Port-2:- As soon as the device gets connected, it gets disconnected.
Port-3:- Device gets connected but unexpected responses are received.
Port-4:- java.io.IOException: bt socket closed, read return: -1
Port-5:- java.io.IOException: bt socket closed, read return: -1
How do we figure out that port number has to be used for creating the connection via the reflection method?
Previously we had faced a similar kind of issue with the native android app and the issue was resolved with the help of the exact same methods which I've mentioned above.
With all the possible tries, now ran out of ideas. Any help will be very much appreciated.
Thanks
Related
I am new to using the Xirsys API for Android. I have followed the tutorial ("Learning WebRTC Getting Started"), but one thing I am noticing is that the 'users' message I get from SYS keeps growing, with duplicates of the same name I am using for my tests (presumably because I am connecting multiple times as I am trying out the APIs). Here is an example message:
{"m":{"f":"StayingInTouch/__sys__","o":"peers","t":null},"p":{"users":["tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad#gmail.com","tad"]},"t":"u"}
Is there something I am supposed to do other than close the various resources in order to have Xirsys remove that user from its internal list?
I start by issuing a PUT via Volley to get the token, then follow that up with a GET via Volley to get the signalling host Url, then open the socket:
okhttp3.Request request = new okhttp3.Request.Builder().url(hostValue + "/v2/" + mToken).build();
ws = client.newWebSocket(request, socketListener);
All that works just fine, and I am able to signal back and forth.
Here is my onDestroy():
protected void onDestroy() {
shutDownResources();
try {
client.dispatcher().executorService().shutdown();
} catch (Exception e) {
myLog("Error shutting down socket client: " + e.getMessage());
}
mQueue.cancelAll(null);
mQueue.stop();
super.onDestroy();
}
And here is the shutDownResources() method:
private void shutDownResources() {
if ( localDataChannel != null ) {
try {
localDataChannel.unregisterObserver();
localObserver = null;
localDataChannel.close();
localDataChannel.dispose();
localDataChannel = null;
} catch (Exception e) {
myLog( "Error closing local data channel:" + e.getMessage());
}
}
if ( localPeer != null ) {
try {
localPeer.close();
localPeer.dispose();
localPeer = null;
} catch (Exception e) {
myLog( "Error closing peer:" + e.getMessage());
}
}
if ( ws != null ) {
try {
ws.cancel();
ws = null;
} catch (Exception e) {
myLog( "Error shutting down socket client: " + e.getMessage());
}
}
iceServers = null;
iceServerMap.clear();
}
I have installed a python server on my robot and also create a socket to robot successfully. Everything is looking fine but when I want to create a channel I have an error that says:
sending naocom/start.sh
W/System.err: bash: naocom/start.sh: Permission denied
here is where I tried to start connection and start python server with
public Map<String, Integer> sendSSHCommands(String[] aCommands, String... aInput){
// Connect to ssh server
Session vSession = connectSSH();
Map<String, Integer> vExitStatus = new HashMap<String, Integer>();
if( vSession != null){
// execute commands
for( int i=0; i < aCommands.length; i++ ){
String cmd = aCommands[i];
try{
// open channel
Channel vChannel = vSession.openChannel("naocom/start.sh");
ChannelExec vChannelExec = (ChannelExec) vChannel;
OutputStream vOutStream = vChannel.getOutputStream();
vChannelExec.setCommand(cmd);
vChannelExec.setOutputStream(System.out);
vChannelExec.setErrStream(System.err);
// connect
Log.i(TAG, "sending " + cmd);
vChannel.connect();
// get user information
SharedPreferences vPreferences = MainActivity.getPreferences();
// wait for command to complete
while( !vChannel.isClosed() ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
};
// add exit status
vExitStatus.put( cmd, vChannel.getExitStatus() );
vOutStream.close();
vChannel.disconnect();
} catch(JSchException e){
Log.e(TAG, e.getMessage());
} catch (IOException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
}
// close ssh connection
closeSSH(vSession);
}
return vExitStatus;
}
when I debuged my code I found it happend when start(); in Channel.java called
public void connect() throws JSchException{
connect(0);
}
public void connect(int connectTimeout) throws JSchException{
this.connectTimeout=connectTimeout;
try{
sendChannelOpen();
start(); //here i have an error
}
catch(Exception e){
connected=false;
disconnect();
if(e instanceof JSchException)
throw (JSchException)e;
throw new JSchException(e.toString(), e);
}
}
We are about to release the new version of our software, and for the version afterward, our goal is to make the connection process for our Bluetooth SPP connections more reliable. We use the RN42 module in our products, and currently, at times it may take more than one try to connect to our boards.
Here is my current code:
class ConnectThread extends Thread {
BluetoothDevice mDevice;
public ConnectThread(BluetoothDevice device) throws SecurityException, NoSuchMethodException {
mDevice = device;
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
try {
btSocket = mDevice.createInsecureRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
Log.e("Error", "Could not create socket!");
}
}
public void cancel() {
interrupt();
try {
Log.i("Treadmill", "in connect thread cancellation");
btSocket.close();
} catch (IOException localIOException) {
Log.e("Treadmill", "exception + " + localIOException.getMessage());
}
}
public void run() {
btAdapter.cancelDiscovery();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e("whatever", "InterruptedException: " + e.getMessage(), e);
}
try {
btSocket.connect();
Log.i("Treadmill", "After Connect");
} catch (IOException ioe) {
Log.i("Treadmill", "Trying Fallback");
try {
Method m;
try {
btSocket.close();
m = mDevice.getClass().getMethod("createInsecureRfcommSocket", new Class[]{int.class});
btSocket = (BluetoothSocket) m.invoke(mDevice, 1);
Thread.sleep(500);
btSocket.connect();
} catch (IllegalArgumentException e) {
Log.e("whatever", "IllegalArgumentException: " + e.getMessage(), e);
} catch (IllegalAccessException e) {
Log.e("whatever", "IllegalAccessException: " + e.getMessage(), e);
} catch (InvocationTargetException e) {
Log.e("whatever", "InvocationTargetException: " + e.getMessage(), e);
} catch (NoSuchMethodException e) {
Log.e("whatever", "NoSuchMethodException: " + e.getMessage(), e);
} catch (InterruptedException e) {
Log.e("whatever", "InterruptedException: " + e.getMessage(), e);
}
} catch (IOException ioe2) {
Log.e("Treadmill", "Failed to connect to Bluetooth device: " + ioe2.getMessage());
eventHandler.obtainMessage(MESSAGE_ERRORCONNECT, 0, 0, getResources().getString(R.string.connerr) + ": " + ioe2.getMessage()).sendToTarget();
try {
btSocket.close();
} catch (IOException localIOException2) {
Log.e("Error", "IO Exception!");
}
return;
}
}
eventHandler.obtainMessage(MESSAGE_CONNECT, 0, 0, "").sendToTarget();
synchronized (this) {
connectThread = null;
}
manageConnectedSocket(btSocket);
}
}
Even with the fallback to reflection the connection intermittently fails on some devices. I get the following error:
find_rfc_slot_by_id unable to find RFCOMM slot id: XX (XX being a number that increments on each attempted connection).
followed by this:
Failed to connect to Bluetooth device: read failed, socket might closed or timeout, read ret: -1
Does anyone know how to avoid these errors.
Interestingly, for comparison. I am testing on two tablets. One tablet, the Samsung Galaxy Tab 4 seems to work extremely well, while another, the Astro Tab A10, seems to be a bit more intermittent unless you wait several seconds between connecting and disconnecting.
For more reliable connection means even app was closed, Bluetooth should be keep connected in the background.
Below is the working solution I followed in my app to keep Bluetooth connection background.
First create a class which extends service, because service runs in the background even app closed until you call stopService or stopSelf methods
while starting BluetoothService class pass Bluetooth Mac address to connect and run in the background.
Sample code:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null){
String deviceg = intent.getStringExtra("bluetooth_device");
if (deviceg != null){
connectToDevice(deviceg);
}
}
return START_STICKY;
}
Below is the connect to device method which identifies mac Address into Bluetooth Device.
public synchronized void connectToDevice(String macAddress){
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddress);
if (mConnectedThread != null){
mConnectedThread.cancel();
mConnectedThread = null;
}
mConnectThread = new ConnectBtThread(device);
toast("connecting");
mConnectThread.start();
}
This is my Thread class inside BluetoothService which runs in a separate thread
Code:
private class ConnectBtThread extends Thread{
private final BluetoothSocket mSocket;
private final BluetoothDevice mDevice;
public ConnectBtThread(BluetoothDevice device){
mDevice = device;
BluetoothSocket socket = null;
try {
socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(B_UUID));
} catch (IOException e) {
e.printStackTrace();
}
mSocket = socket;
}
#Override
public void run() {
if (mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
try {
mSocket.connect();
Log.d("service","Bluetooth one running (connected)");
} catch (IOException e) {
try {
mSocket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
connected(mSocket);
}
public void cancel(){
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
It works perfectly fine for our app.
If you want to access service methods bind this service to your activity
So I have set up a serial connection between a paired raspberry pi device running a pybluez rfcomm-server. I have paired my Android device to the Pi, and then put together a Bluetooth service that leverages a Thread to handle the two-way asynchronous comms. Most of the code is straight from the Android Developers Guides on Bluetooth Socket communication here and reading/writing to the socket as discussed here
The issue arises when I manually disconnect from the Pi by closing down my socket and closing down the thread handling the communication. I can reconnect, spin up another thread, and successfully start communicating with the Pi again but this is where it gets weird, IOExcpetions start getting thrown and Logcat is pissing and moaning that and "Error occured sending data". The exception is caught obviously and like I said earlier, the Pi is still happily receiving the messages. I can repeat this by disconnecting and reconnecting again and again.
Normally I would just shrug and move on because it works right? But I don't feel like any IOExceptions should be being thrown.
Here is some code:
(BtBus is an RxJava based message bus I made, all messages are send on background threads and received on the main thread)
BluetoothService - connect() and disconnect() methods
public void connect(){
if(btAdapter == null) {
Log.e(TAG, "No bluetooth adapter available");
}
Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
// There are paired devices. Get the name and address of each paired device.
for (BluetoothDevice device : pairedDevices) {
if(device.getName().contains("raspberry")){
pi = device;
Log.d(TAG, "using paired device: " + pi.getName());
break;
}
}
}else {
Log.d(TAG, "No BT devices paired");
}
if(pi != null) {
if(btAdapter.isDiscovering())
btAdapter.cancelDiscovery();
Observable.fromCallable(this::connectSocket)
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.subscribe(socket -> {
if(socket != null){
try {
socket.connect();
Log.d(TAG, "Socked with device created, creating thread");
connectedThread = new ConnectedThread(socket);
Log.d(TAG, "Thread created, starting...");
connectedThread.start();
Log.d(TAG, "Thread Started");
isConnected = true;
BtBus.publish(new BtAction("CONNECTION_ESTABLISHED"));
}catch (IOException e) {
Log.d(TAG, "Unable to connect socket");
e.printStackTrace();
}
}
});
}
//disconnect
public void disconnect() {
if(socket != null) {
connectedThread.cancel();
}
}
private BluetoothSocket connectSocket(){
try {
socket = pi.createInsecureRfcommSocketToServiceRecord(DEFAULT_UUID);
return socket;
}catch (IOException e){
Log.d(TAG, "Failed to get service with Pi UUID");
return null;
}
}
ConnectedThread
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private byte[] mmBuffer; // mmBuffer store for the stream
private ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams; using temp objects because
// member streams are final.
try {
tmpIn = socket.getInputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating input stream", e);
}
try {
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating output stream", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
BtBus.subscribe(action -> {
if(action instanceof BtAction){
write(((BtAction) action).getAction().getBytes());
}
});
}
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
// Send the obtained bytes to the UI activity.
BtAction action = BtCommFilter.getActionType(mmBuffer);
BtBus.publish(action);
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
// Call this from the main activity to send data to the remote device.
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
// Share the sent message with the UI activity.
} catch (IOException e) {
Log.e(TAG, "Error occurred when sending data", e);
// Send a failure message back to the activity.
}
}
// Call this method from the main activity to shut down the connection.
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the connect socket", e);
}
}
}
Here is the stack relevant stack trace from the exception
E/BluetoothService: Error occurred when sending data
java.io.IOException: socket closed
at android.net.LocalSocketImpl$SocketOutputStream.write(LocalSocketImpl.java:130)
at android.bluetooth.BluetoothSocket.write(BluetoothSocket.java:659)
at android.bluetooth.BluetoothOutputStream.write(BluetoothOutputStream.java:85)
at java.io.OutputStream.write(OutputStream.java:82)
at com.medspark.tenscontroller.service.BluetoothService$ConnectedThread.write(BluetoothService.java:183)
at com.medspark.tenscontroller.service.BluetoothService$ConnectedThread.lambda$new$0$BluetoothService$ConnectedThread(BluetoothService.java:157)
at com.medspark.tenscontroller.service.BluetoothService$ConnectedThread$$Lambda$0.accept(Unknown Source)
at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:60)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:200)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6872)
As you can see, when I close the connection the socket is properly closed and the thread is disposed of. When I reconnect a new socket is created and a new thread spun up, so I don't understand where the IOException is being thrown. Thanks in advance.
I can bind socket to specific network in android 5.x.
How to bind DatagramSocket? Is it possible?
Looking in sources if it can be done with reflection with no luck.
https://developer.android.com/reference/android/net/Network.html#bindSocket(java.net.Socket)
UPDATE ParcelFileDescriptor.fromDatagramSocket(socket).getFd() can replace helper function getFileDescriptor . There is also new function that bind direct to FileDescriptor in API 23 https://developer.android.com/reference/android/net/Network.html#bindSocket%28java.io.FileDescriptor%29
UPDATE: Just saw some gray additional new function in API level 22.
https://developer.android.com/reference/android/net/Network.html#bindSocket(java.net.DatagramSocket)
UPDATE: Can be done also with reflection in API level 21
private static int getFileDescriptor(DatagramSocket socket) throws SocketException {
try {
Field implField = DatagramSocket.class.getDeclaredField("impl");
implField.setAccessible(true);
DatagramSocketImpl implValue = (DatagramSocketImpl)implField.get(socket);
Field fdField = DatagramSocketImpl.class.getDeclaredField("fd");
fdField.setAccessible(true);
FileDescriptor fdValue = (FileDescriptor)fdField.get(implValue);
Field descField = FileDescriptor.class.getDeclaredField("descriptor");
descField.setAccessible(true);
return (Integer)descField.get(fdValue);
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new AssertionError(e);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new AssertionError(e);
}
}
private DatagramChannel createDatagramChannel(Network activeWifiNetwork) throws IOException{
DatagramChannel channel = DatagramChannel.open();
try{
if (activeWifiNetwork == null) {
Log.d(TAG, "No active network to bind to ");
return channel;
}
Class<?> networkUtils = Class.forName("android.net.NetworkUtils");
for (Method m : networkUtils.getDeclaredMethods()) {
if (m.getName().equalsIgnoreCase("bindSocketToNetwork")){
DatagramSocket socket = channel.socket();
socket.getReuseAddress();
int socketFileDescriptor = getFileDescriptor(socket);
int result = (Integer) m.invoke(null, socketFileDescriptor , Integer.valueOf(activeWifiNetwork.toString()));
Log.d(TAG, "Result bind datagram to network is " + result);
if (result != 0){
throw new Exception("Error binding socket to network");
}
break;
}
}
} catch(Exception ex){
ex.printStackTrace();
}
return channel;
}