Hello I have a code written in Java and I need to create an TCP Connection with GPS device in android studio, where you can type in IP/PORT addresses, if someone can help me thanks in advance.
public class TCPConnection implements Runnable {
/**
* <h1>TCP Connection construct</h1>
* <p>The tcp connection requires two parameters socket and view model. </p>
* #param socket to establish connection.
* */
TCPConnection(Socket socket) {
super();
this.socket = socket;
converter = new Converter();
crc16 = new Crc16();
}
/**
* <h1>Run function to start listener</h1>
* <p>Simply runs the runnable thread to listen everything from client</p>
* */
public void run() {
try {
inputStream = new DataInputStream(socket.getInputStream());
outputStream = new DataOutputStream(socket.getOutputStream());
Listen();
} catch (IOException e) {
e.printStackTrace();
}
}
Probably I need to create an button to start Listen incoming connections, also use Log class .....
/**
* <h1>Listen</h1>
* <p>Function for listening connected client</p>
* #throws IOException throws exception if input stream is interrupted
* */
private void Listen() throws IOException {
while (flag) {
System.out.println("listening...");
while (!socket.isClosed() && inputStream.available() == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
Communicate();
}
inputStream.close();
outputStream.close();
socket.close();
}
/**
* <h1>Get Number Of Records</h1>
* <p>Reads the number of records to send back to the sender</p>
* #param data the parameter is a received hex data
* #return String format number of records
* */
private String GetNumberOfRecords(String data) {
return data.substring(18, 20);
}
Everything is written in a comments line, why stackoverflow says add more details :D...
/**
* <h1>Communicate</h1>
* <p>A reader and sender with client, first it reads imei, then sends back 01.
* It receives data, as soon it receives it sends back number of records.
* The while loop initializes and runs until it get interrupted or client disconnects.</p>
* */
private void Communicate() {
imei = Objects.requireNonNull(ReadInput()).substring(4);
imei = converter.ReadImei(imei);
String path = System.getProperty("user.home") + "/Desktop";
logger = new Logger(path+"/Logs/TCPLogs/"+imei);
logger.PrintToLOG(GetTime()+" IMEI: " +imei);
if(imei.length() < 15){
SendOutput("00");
}
else{
SendOutput("01");
logger.PrintToLOG("\tResponse: [0" + 1 + "]");
String input = ReadInput();
Log(Objects.requireNonNull(input));
while(flag){
String recordsCount = GetNumberOfRecords(input);
SendOutput("000000" + recordsCount);
logger.PrintToLOG("\tCrc: " + Integer.toHexString(CRC(input)));
logger.PrintToLOG("\tResponse: [000000" + recordsCount + "]\n");
input = ReadInput();
Log(Objects.requireNonNull(input));
}
}
/**
* <h1>Send Output</h1>
* <p>Sends output to the client</p>
* #param message the parameter is a received hex data
* */
private void SendOutput(String message) {
try {
outputStream.write(converter.StringToByteArray(message));
outputStream.flush();
} catch (IOException e) {
System.out.println("Output stream was interrupted");
}
}
/**
* <h1>CRC</h1>
* <p>Calculates CRC of received data</p>
* #param str the parameter is a received hex data
* #return int of crc16
* */
private int CRC(String str) {
str = str.substring(16, str.length() - 8);
byte[] bytes = converter.StringToByteArray(str);
return crc16.getCRC(bytes);
}
/**
* <h1>Read Input</h1>
* <p>Reads the input from client. Currently maximum message byte is set up to 8192,
* if message is bigger then message will not be properly readable and displayed.</p>
* #return String of received data
* */
private String ReadInput() {
byte[] messageByte = new byte[8192];
int dataSize;
try {
dataSize = inputStream.read(messageByte);
String finalInput = converter.BytesArrayToHex(messageByte, dataSize);
SendToConsole(finalInput);
return finalInput;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* <h1>Send To Console</h1>
* <p>Simply prints out the results to the text area for user</p>
* #param input the parameter is String format to print in text area
* */
private void SendToConsole(String input) {
if(imei!=null)
{
String message = viewModel.getClientMessage() + "\r\nFrom imei - "+imei+" : " + input + "\n" + repeatChar();
Platform.runLater(() -> viewModel.setClientMessage(message));
}
else {
String message = viewModel.getClientMessage() + "\r\nReceived imei - : " + input + "\n" + repeatChar();
Platform.runLater(() -> viewModel.setClientMessage(message));
}
}
/**
* <h1>Log</h1>
* <p>Given String is being written to log file.</p>
* #param data the parameter is a received data
* */
private void Log(String data) {
logger.PrintToLOG("\tcodec : " + data.substring(16, 18));
logger.PrintToLOG("\tNumber of Records : " + GetNumberOfRecords(data));
logger.PrintToLOG("\tAVL data : " + data + "\n");
}
/**
* <h1>Set Running</h1>
* <p>Sets flag to run or stop while loop in order to interrupt the thread.</p>
* */
void setRunning() {
this.flag = false;
}
/**
* <h1>Repeat Char</h1>
* <p>Repeats the '=' character multiple times.</p>
* #return String is being returned.
* */
private String repeatChar() {
char[] data = new char[50];
Arrays.fill(data, '=');
return new String(data);
}
/**
* <h1>Get Time</h1>
* <p>Gets time when method is being called</p>
* #return Time in String format
* */
private String GetTime()
{
LocalDateTime localDateTime = LocalDateTime.now();
LocalTime localTime = localDateTime.toLocalTime();
return localTime.toString();
}
}
public class TCPServer implements Runnable {
private int port;
private Socket socket;
private ServerSocket ss;
private boolean running = true;
private ArrayList<TCPConnection> tcpConnections;
/**
* <h1>TCP server construct</h1>
* <p>The tcp server takes port parameter </p>
* #param port is required for server to listen all incoming connections
* */
public TCPServer(int port) {
this.port = port;
}
/**
* <h1>Run</h1>
* <p>Runs the runnable thread to listen connections, it accepts a connection, if accept was successful,
* the connection is added to tcpConnections list and runs the TCPConnection for further listening.
* The server is running in while loop and stops when Running is set to false,
* then break is called and shutdowns every connected client.</p>
* */
public void run() {
tcpConnections = new ArrayList<>();
try {
ss = new ServerSocket(port);
System.out.println("Listening on port : " + ss.getLocalPort());
ExecutorService executorService;
while (true) {
executorService = Executors.newSingleThreadExecutor();
socket = ss.accept();
TCPConnection connection = new TCPConnection(socket);
executorService.submit(connection);
tcpConnections.add(connection);
if (!running) {
StopConnections();
break;
}
}
executorService.shutdownNow();
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (IOException e) {
System.out.println("socket is closed");
}
}
/**
* <h1>Set Flag</h1>
* <p>Function is being called when we want to interrupt server thread and stop it.</p>
* #param flag the parameter sets whenever to true(run server) or false(stop server)
* */
public void setFlag(boolean flag) {
running = flag;
if (!running) {
try {
ss.close();
if (socket != null)
socket.close();
} catch (IOException e) {
System.out.println("Socket is " + socket.isClosed());
}
}
}
/**
* <h1>Stop Connections</h1>
* <p>Function is being called when we are stopping server,
* this function iterates through every connection and stops it.</p>
* */
private void StopConnections() {
if (!tcpConnections.isEmpty()) {
for (TCPConnection connections : tcpConnections) {
connections.setRunning();
}
tcpConnections.clear();
}
}
}
Android supports Java code as long as your android API level supports the Java version you are using. There shouldn't be any reason why you cannot use this in Android.
Just note that Android will throw an exception if you run any network tasks on the UI thread. E.g. Creating a socket should be run as an IntentService or AsyncTask, there are other options as well.
Related
I have a main thread server which basically listen to whoever want to connect to the port
/**
* The main server thread receives request from and sends
* response to clients.
*/
public class Server {
/*
Port number for the client connection
*/
private static final int PORT = 3000;
/*
The number of client can be connected
*/
private static final int SIZE = 10;
/*
The executor
*/
private static ExecutorService executorService = Executors.newFixedThreadPool(SIZE);
/**
* Starts the main server.
*/
public static void main(String[] args) {
/*
All the information are stored into the queue
*/
BlockingQueue<Message> messageQueue = new LinkedBlockingQueue<>();
/*
All the clients are stored into the map
*/
ConcurrentMap<byte[], Boolean> clientManagement = new ConcurrentHashMap<>();
runMainServer(messageQueue, clientManagement);
}
private static void runMainServer(BlockingQueue<Message> messageQueue, ConcurrentMap<byte[], Boolean> clientManagement) {
try (
ServerSocket serverSocket = new ServerSocket(PORT);
) {
System.out.println("Starting server");
while (true) {
System.out.println("Waiting for request");
Socket socket = serverSocket.accept();
System.out.println("Processing request");
ClientThread newClient = new ClientThread(socket, messageQueue, clientManagement);
executorService.submit(newClient);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
And I have many multi-threading sub-server to handle each identical client. The client is first going to be accepted by the server, and checked if first message that the server received is a connect_message. If it is, then they are officially connected. There are many more message other than connect_message. But I am just not going to be too specific on them.
/**
* The client thread.
*/
public class ClientThread implements Runnable {
private Socket socket;
private BlockingQueue<Message> messageQueue;
private ConcurrentMap<byte[], Boolean> clientManagement;
private byte[] username;
/**
*
*
* #param socket
* #param messageQueue
* #param clientManagement
*/
public ClientThread(Socket socket, BlockingQueue<Message> messageQueue, ConcurrentMap<byte[], Boolean> clientManagement) {
this.socket = socket;
this.messageQueue = messageQueue;
this.clientManagement = clientManagement;
this.username = new byte[1];
}
/**
*
*/
#Override
public void run() {
try (
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
) {
Message m = (Message) in.readObject();
if (m.getIdentifier() == MessageIdentifier.CONNECT_MESSAGE) {
ConnectMessage cm = (ConnectMessage) m;
this.username = cm.getUsername();
clientManagement.put(cm.getUsername(), true);
byte[] cntMsg = "Successfully Connected!".getBytes();
ConnectResponse cr = new ConnectResponse(true, cntMsg.length, cntMsg);
out.writeObject(cr);
} else {
// Handle failing request
handleFailedMsg(out, "Client should connect first");
socket.close();
throw new IllegalArgumentException("Connect unsuccessfully");
}
handleClient(in, out);
socket.close();
} catch (IOException | ClassNotFoundException | InterruptedException e) {
e.printStackTrace();
}
}
/**
*
* #param in
* #param out
* #throws InterruptedException
* #throws IOException
* #throws ClassNotFoundException
*/
private void handleClient(ObjectInputStream in, ObjectOutputStream out)
throws InterruptedException, IOException, ClassNotFoundException {
while (true) {
// Handle message taken from the queue
Message msgFromQueue = messageQueue.take();
handleQueueRequest(msgFromQueue, out);
// Handle request obtained by user
Message request = (Message) in.readObject();
// Handle disconnect
if (request.getIdentifier() == MessageIdentifier.DISCONNECT_MESSAGE) {
DisconnectMessage dm = (DisconnectMessage) request;
// If the message is not for this thread, then put it back and ignore it.
if (!Arrays.equals(username, dm.getUsername())) {
messageQueue.add(request);
continue;
}
// Check if the username is inside the client map
if (!clientManagement.containsKey(dm.getUsername())) {
handleFailedMsg(out, "The client doesn't exist");
}
// Disconnect
clientManagement.remove(dm.getUsername());
// Create disconnect response
byte[] message = "See you again".getBytes();
DisconnectResponse dr = new DisconnectResponse(true, message.length, message);
// Write to the client
out.writeObject(dr);
break;
}
// Handle other
if (!handleRequest(request, out)) {
handleFailedMsg(out, "The request failed due to incorrect username.");
}
}
}
/**
*
* #param request
* #param out
* #return
* #throws IOException
*/
private boolean handleRequest(Message request, ObjectOutputStream out) throws IOException {
switch (request.getIdentifier()) {
// If broadcast, then every one should know
case BROADCAST_MESSAGE:
BroadcastMessage bm = (BroadcastMessage) request;
if (!Arrays.equals(username, bm.getUsername())) {
return false;
}
messageQueue.add(request);
break;
// If user want the list of connected users
case QUERY_CONNECTED_USERS:
QueryUsersMessage qu = (QueryUsersMessage) request;
if (!Arrays.equals(username, qu.getUsername())) {
return false;
}
List<Pair<Integer, byte[]>> userList = new ArrayList<>();
for (byte[] username : clientManagement.keySet()) {
Pair<Integer, byte[]> user = new Pair<>(username.length, username);
userList.add(user);
}
// Create a new query response containing all the users
QueryResponse qr = new QueryResponse(clientManagement.keySet().size(), userList);
out.writeObject(qr);
break;
// If user wants to send a direct message to the other user
case DIRECT_MESSAGE:
DirectMessage dm = (DirectMessage) request;
if (!Arrays.equals(username, dm.getUsername())) {
return false;
}
messageQueue.add(request);
break;
// If user wants to send an insult to the other user and broadcast to the chat room
case SEND_INSULT:
SendInsultMessage si = (SendInsultMessage) request;
if (!Arrays.equals(username, si.getUsername())) {
return false;
}
messageQueue.add(request);
break;
}
return true;
}
/**
*
* #param out
* #param description
* #throws IOException
*/
public void handleFailedMsg(ObjectOutputStream out, String description) throws IOException {
byte[] failedMsg = description.getBytes();
FailedMessage fm = new FailedMessage(failedMsg.length, failedMsg);
out.writeObject(fm);
}
/**
*
* #param request
* #param out
* #throws IOException
*/
public void handleQueueRequest(Message request, ObjectOutputStream out) throws IOException {
switch (request.getIdentifier()) {
case SEND_INSULT:
// Gets the message from queue
SendInsultMessage si = (SendInsultMessage) request;
// Check if the user already gotten the message
if (!si.getOtherUsers().contains(username)) {
out.writeObject(si);
si.addUsers(username);
}
// Check if all the users already gotten the message
if (si.getOtherUsers().size() < clientManagement.keySet().size()) {
messageQueue.add(si);
}
break;
case DIRECT_MESSAGE:
DirectMessage dm = (DirectMessage) request;
// Check if the message is for this user
if (Arrays.equals(username, dm.getRecipientUsername())) {
out.writeObject(dm);
} else { // If not for this user then put it back
messageQueue.add(dm);
}
break;
case BROADCAST_MESSAGE:
// Gets the message from queue
BroadcastMessage bm = (BroadcastMessage) request;
// Check if the user already gotten the message
if (!bm.getOtherUsers().contains(username)) {
out.writeObject(bm);
bm.addUsers(username);
}
// Check if all the users already gotten the message
if (bm.getOtherUsers().size() < clientManagement.keySet().size()) {
messageQueue.add(bm);
}
break;
}
}
I want to do JUnit test for my server. What is the best way to test a multi-threading server like this?
Here are the JUnit test code that I am trying. I first start a thread which is accepted by the server. Then I am going to start a client and pretend that the client is sending something to the server. I first want to try a connect_message to see how connection work. But so far, the test doesn't seem to responding on JUnit test. It just keeps running, nothing happen
public class ClientThreadTest {
private Thread foo;
private List<ClientThread> clientList;
private BlockingQueue<Message> messageQueue;
private ConcurrentMap<byte[], Boolean> clientManagement;
private static final int PORT = 3000;
#Before
public void setUp() throws Exception {
messageQueue = new LinkedBlockingQueue<>();
clientManagement = new ConcurrentHashMap<>();
}
#Test
public void run() throws IOException, ClassNotFoundException {
ServerSocket socket = new ServerSocket(PORT);
foo = new Thread(new ClientThread(socket.accept(), messageQueue, clientManagement));
foo.start();
Socket fooClient = new Socket("localhost", PORT);
ObjectOutputStream out = new ObjectOutputStream(fooClient.getOutputStream());
ObjectInputStream in = new ObjectInputStream(fooClient.getInputStream());
// First test Connection message
byte[] username = "foo".getBytes();
ConnectMessage cm = new ConnectMessage(username.length, username);
// The message need to get
byte[] cntMsg = "Successfully Connected!".getBytes();
ConnectResponse cr = new ConnectResponse(true, cntMsg.length, cntMsg);
out.writeObject(cm);
ConnectResponse m = (ConnectResponse) in.readObject();
System.out.println(Arrays.toString(m.getMessage()));
}
I have solved my own problem!
For anyone who is doing JUnit testing on a multi-threading server. Here is my suggestion:
You have to start you main server at the beginning, before anything else. Keep you main server listening to some port that you give it.
Then you have to start your client and you have to give it the same port number which you gave to the main server
Last but not least, you can start your thread to deal with a specific client. Somehow if I instantiated my thread as ClientThread foo, and I called foo.run(), it won't work. I have to instantiate Thread foo, and make my ClientThread() as an input to Thread(), and call foo.start() instead!
Now it is working!
I have been using a tcp client extension by Jean-Rodolphe Letertre for app inventor 2 and it works flawlessly until you call the disconnect method and than it crashes the app. After looking at the code for the extension i found that disconnect only shuts down output, input and than closes the socket which shouldn't cause any crashes so my suspicion fell on the connect method which runs a thread because it keeps reading data in a loop from a tcp socket and when we call disconnect we don't finish the thread which causes application crash because input is closed and an exception goes unhandled.
NOTE: The code is not mine and i don't ask to fix it for me i only want to know if iv'e found the problem which causes crashes and if so i will fix it myself. Thanks in advance for any help!
The code:
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.gmail.at.moicjarod;
import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.util.RuntimeErrorAlert;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.YailList;
import com.google.appinventor.components.runtime.util.SdkLevel;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.os.StrictMode;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.SocketException;
/**
* Simple Client Socket
* #author moicjarod#gmail.com (Jean-Rodolphe Letertre)
* with the help of the work of lizlooney # google.com (Liz Looney) and josmasflores # gmail.com (Jose Dominguez)
* the help of Alexey Brylevskiy for debugging
* and the help of Hossein Amerkashi from AppyBuilder for compatibility with AppyBuilder
*/
#DesignerComponent(version = 4,
description = "Non-visible component that provides client socket connectivity.",
category = ComponentCategory.EXTENSION,
nonVisible = true,
iconName = "http://jr.letertre.free.fr/Projets/AIClientSocket/clientsocket.png")
#SimpleObject(external = true)
#UsesPermissions(permissionNames = "android.permission.INTERNET")
public class ClientSocketAI2Ext extends AndroidNonvisibleComponent implements Component
{
private static final String LOG_TAG = "ClientSocketAI2Ext";
private final Activity activity;
// the socket object
private Socket clientSocket = null;
// the address to connect to
private String serverAddress = "";
// the port to connect to
private String serverPort = "";
// boolean that indicates the state of the connection, true = connected, false = not connected
private boolean connectionState = false;
// boolean that indicates the mode used, false = string sent as is, true = String is considered as hexadecimal data and will be converted before sending
// same behavior is used when receiving data
private boolean hexaStringMode = false;
InputStream inputStream = null;
/**
* Creates a new Client Socket component.
*
* #param container the Form that this component is contained in.
*/
public ClientSocketAI2Ext(ComponentContainer container)
{
super(container.$form());
activity = container.$context();
// compatibility with AppyBuilder (thx Hossein Amerkashi <kkashi01 [at] gmail [dot] com>)
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
/**
* Method that returns the server's address.
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The address of the server the client will connect to.")
public String ServerAddress()
{
return serverAddress;
}
/**
* Method to specify the server's address
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
#SimpleProperty
public void ServerAddress(String address)
{
serverAddress = address;
}
/**
* Method that returns the server's port.
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The port of the server the client will connect to.")
public String ServerPort()
{
return serverPort;
}
/**
* Method to specify the server's port
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
#SimpleProperty
public void ServerPort(String port)
{
serverPort = port;
}
/**
* Method that returns the connection state
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The state of the connection - true = connected, false = disconnected")
public boolean ConnectionState()
{
return connectionState;
}
/**
* Method that returns the mode (string or hexastring)
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The mode of sending and receiving data.")
public boolean HexaStringMode()
{
return hexaStringMode;
}
/**
* Method to specify the mode (string or hexastring)
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN)
#SimpleProperty
public void HexaStringMode(boolean mode)
{
hexaStringMode = mode;
}
/**
* Creates the socket, connect to the server and launches the thread to receive data from server
*/
#SimpleFunction(description = "Tries to connect to the server and launches the thread for receiving data (blocking until connected or failed)")
public void Connect()
{
if (connectionState == true)
{
throw new YailRuntimeError("Connect error, socket connected yet, please disconnect before reconnect !", "Error");
}
try
{
// connecting the socket
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(serverAddress, Integer.parseInt(serverPort)), 5000);
connectionState = true;
// begin the receive loop in a new thread
AsynchUtil.runAsynchronously(new Runnable()
{
#Override
public void run()
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int bytesRead;
try
{
// get the input stream and save the data
inputStream = clientSocket.getInputStream();
while (true)
{
// test if there is a server problem then close socket properly (thx Axeley :-))
try
{
bytesRead = inputStream.read(buffer);
if(bytesRead == -1)
break;
}
catch(SocketException e)
{
if(e.getMessage().indexOf("ETIMEDOUT") >= 0)
break;
throw e;
}
byteArrayOutputStream.write(buffer, 0, bytesRead);
final String dataReceived;
// hexaStringMode is false, so we don't transform the string received
if (hexaStringMode == false)
{
dataReceived = byteArrayOutputStream.toString("UTF-8");
}
// hexaStringMode is true, so we make a string with each character as an hexa symbol representing the received message
else
{
int i;
char hexaSymbol1, hexaSymbol2;
String tempData = "";
byte[] byteArray = byteArrayOutputStream.toByteArray();
for (i = 0; i < byteArrayOutputStream.size(); i++)
{
if (((byteArray[i] & 0xF0) >> 4) < 0xA)
// 0 to 9 symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x30);
else
// A to F symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x37);
if ((byteArray[i] & 0x0F) < 0xA)
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x30);
else
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x37);
tempData = tempData + hexaSymbol1 + hexaSymbol2;
}
dataReceived = tempData;
}
// reset of the byteArrayOutputStream to flush the content
byteArrayOutputStream.reset();
// then we send the data to the user using an event
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
DataReceived(dataReceived);
}
} );
}
// When we go there, either we have
// - server shutdown
// - disconnection asked (inputstream closed => -1 returned)
// - connection problem
// so, if it is not disconnected yet, we disconnect the socket and inform the user of it.
if (connectionState == true)
{
Disconnect();
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
RemoteConnectionClosed();
}
} );
}
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)", "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
}
}
} );
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Connect error" + e.getMessage(), "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Connect error (Socket Creation)" + e.getMessage(), "Error");
}
}
/**
* Send data through the socket to the server
*/
#SimpleFunction(description = "Send data to the server")
public void SendData(final String data)
{
final byte [] dataToSend;
byte [] dataCopy = data.getBytes();
if (connectionState == false)
{
throw new YailRuntimeError("Send error, socket not connected.", "Error");
}
if (hexaStringMode == false)
{
//dataSend = new byte [data.length()];
// if hexaStringMode is false, we send data as is
dataToSend = data.getBytes();
}
else
{
// if hexaStringMode is true, we begin to verify we can transcode the symbols
// verify if the data we want to send contains only hexa symbols
int i;
for (i = 0; i < data.length(); i++)
{
if (((dataCopy[i] < 0x30) || (dataCopy[i] > 0x39)) && ((dataCopy[i] < 0x41) || (dataCopy[i] > 0x46)) && ((dataCopy[i] < 0x61) || (dataCopy[i] > 0x66)))
throw new YailRuntimeError("Send data : hexaStringMode is selected and non hexa symbol found in send String.", "Error");
}
// verify that the number of symbols is even
if ((data.length() %2) == 1)
{
throw new YailRuntimeError("Send data : hexaStringMode is selected and send String length is odd. Even number of characters needed.", "Error");
}
// if all tests pass, we transcode the data :
dataToSend=new byte[data.length()/2+1];
for (i = 0; i < data.length(); i=i+2)
{
byte [] temp1 = new byte [2];
temp1 [0] = dataCopy[i];
temp1 [1] = dataCopy[i+1];
String temp2 = new String (temp1);
dataToSend[i/2]=(byte)Integer.parseInt(temp2, 16);
}
// end of c-type string character
dataToSend[i/2] = (byte)0x00;
}
// we then send asynchonously the data
AsynchUtil.runAsynchronously(new Runnable()
{
#Override
public void run()
{
try
{
OutputStream out;
out = clientSocket.getOutputStream();
out.write(dataToSend);
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_SEND", e);
throw new YailRuntimeError("Send data" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_UNABLE_TO_SEND_DATA", e);
throw new YailRuntimeError("Send Data", "Error");
}
}
} );
}
/**
* Close the socket
*/
#SimpleFunction(description = "Disconnect to the server")
public void Disconnect()
{
if (connectionState == true)
{
connectionState = false;
try
{
// shutdown the input socket,
clientSocket.shutdownInput();
clientSocket.shutdownOutput();
clientSocket.close();
}
catch (SocketException e)
{
// modifications by axeley too :-)
if(e.getMessage().indexOf("ENOTCONN") == -1)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
// if not connected, then just ignore the exception
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
finally
{
clientSocket=null;
}
}
else
throw new YailRuntimeError("Socket not connected, can't disconnect.", "Error");
}
/**
* Event indicating that a message has been received
*
* #param data the data sent by the server
*/
#SimpleEvent
public void DataReceived(String data)
{
// invoke the application's "DataReceived" event handler.
EventDispatcher.dispatchEvent(this, "DataReceived", data);
}
/**
* Event indicating that the remote socket closed the connection
*
*/
#SimpleEvent
public void RemoteConnectionClosed()
{
// invoke the application's "RemoteConnectionClosed" event handler.
EventDispatcher.dispatchEvent(this, "RemoteConnectionClosed");
}
}
By commenting the following lines, it could be achieved:
throw new YailRuntimeError("...
The problem on this approach is that we wouldn't be able do know the disconnection reason, however we must agree that these messages are rather informative than functional in the sense of the application workflow, therefore an alternative option is to add the ability to disable these calls in runtime.
I trying to create a TCP server using NIO(Selector based) which can check whether any client is idle for more than 5 minutes.
I had been using time out on read operations using blocking IO, but there is no such option is provided in NIO.
Is there any efficient way to achieve this?
I achieved this by periodically checking idle clients.
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
/**
* {#link NIOServer} class is used to create a Non-Blocking TCP server listening
* on the port specified in the constructor parameter and listens for clients
* sending data.
*
* #author AchuthaRanga.Chenna
*
*/
public class NIOServer implements Runnable {
private Logger logger = Logger.getLogger(this.getClass().getName());
private InetAddress hostAddress;
private int port;
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private boolean runServer = true;
private ConcurrentHashMap<SocketChannel, Long> clients = new ConcurrentHashMap<SocketChannel, Long>();
/**
* Constructor to pass the host and port of server.
*
* #param hostAddress
* address on which server has to be run.
* #param port
* port of the server.
**/
public NIOServer(InetAddress hostAddress, int port) throws IOException {
this.hostAddress = hostAddress;
this.port = port;
this.selector = initSelector();
IdleSocketChecker isc = new IdleSocketChecker();
new Thread(isc).start();
}
/**
* Method to create a ServerSocket and register to selector with option
* OP_ACCEPT to accept connections from clients.
*
* #return Selector registered with a serverSocket listening on a port to
* accept connections.
* #throws IOException
* on fail to create a selector or bind the server to the
* address.
*/
private Selector initSelector() throws IOException {
Selector socketSelector = SelectorProvider.provider().openSelector();
this.serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(this.hostAddress, this.port);
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(isa);
serverSocketChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
/**
* Method with a infinite loop to iterate over the selected keys of the
* selector to know the option interested of the client sockets connected.
* Loop breaks when on IOException occurs on the selector which is listening
* for IOOperations of the client.
**/
#Override
public void run() {
while (runServer) {
try {
/*
* ".select" is a blocking call which invokes when any channel
* registered with the selector has an I/O operation to be done.
*/
this.selector.select();
/*
* Get a Iterator of the channels having I/O event to be
* handled.
*/
Iterator<SelectionKey> selectedKeys = this.selector.selectedKeys().iterator();
/* Iterate over the selected keys having I/O event to be done. */
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
/* Remove the key to avoid infinite loop. */
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
} else if (key.isReadable()) {
this.read(key);
}
} catch (CancelledKeyException e) {// key has been canceled
}
}
/* Exception is generated when the Selector fails. */
/*
* Close the server and return from the while loop when Selector
* fails.
*/
} catch (IOException e) {
logger.error("[run] Exception on generation of client event", e);
try {
serverSocketChannel.close();
selector.close();
} catch (IOException e1) {
logger.error("[run] Exception on shutting down NIOSERVER due to selector is closed", e1);
}
break;
}
}
logger.info("[NIOSERVER thread closed normally]");
/* Clean up the resources */
this.releaseResources();
}
/**
* Method to release the resources used to create NIOSERVER SOCKET.
*/
public void releaseResources() {
try {
this.serverSocketChannel.close();
} catch (IOException e) {
logger.warn("[run]", e);
}
try {
this.selector.close();
} catch (IOException e) {
logger.warn("[run]", e);
}
}
/**
* Method to return socket status.
*
* #return
*/
public boolean isConencted() {
boolean status = false;
try {
status = serverSocketChannel.isOpen();
} catch (Exception e) {
}
return status;
}
/**
* Utility method to stop the server thread.
*
* #param runServer
* Flag decides to stop Server
*/
public void shutDown() {
this.runServer = false;
logger.info("[shutDown] Server is stopped");
}
/**
* Method to accept connections from clients and registering for reading
* data from clients.Set's a KeepAlive option of the socket true and
* register the connected socket for READ option.
*
* #param key
* which is ready to acceptable
*/
private void accept(SelectionKey key) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
/* Set the KeepAlive flag to avoid continuous open of files */
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.configureBlocking(false);
/* Register the client connected with our interested Option Read */
socketChannel.register(this.selector, SelectionKey.OP_READ);
// key.attach(System.currentTimeMillis());
clients.put(socketChannel, System.currentTimeMillis());
logger.debug("[accept] New Client connected from " + socketChannel.getRemoteAddress());
logger.info("[accept] Total connected : " + clients.size());
// System.out.println(socketChannel.hashCode());
} else {
key.cancel();
}
} catch (IOException e) {
key.cancel();
logger.error("[accept] Error while accepting new connectins", e);
}
}
/**
* * Method to read data from key having read event pending.
*
* #param key
* SelectionKey having read event.
*/
private void read(SelectionKey key) {
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (socketChannel) {
if (socketChannel.isOpen()) {
try {
ByteBuffer readBuffer = ByteBuffer.allocate(150);
readBuffer.clear();
int numRead = 0;
try {
/* ".read" is nonblocking */
numRead = socketChannel.read(readBuffer);
/*
* Some other IO error on reading socketChannel.
*/
} catch (IOException e) {
logger.debug("[run] Connection abruptly terminated from client", e);
key.channel().close();
clients.remove(socketChannel);
return;
}
if (numRead == -1) {// socket closed cleanly
key.channel().close();
clients.remove(socketChannel);
return;
}
String data = null;
data = new String(readBuffer.array(), Charset.forName("ASCII"));
logger.info(data);
/* Send the read data to the DataDispatcher Actor */
clients.put(socketChannel, System.currentTimeMillis());
} catch (IOException e) {
logger.debug("[run] ", e);
return;
}
} else {// socketChannel is closed
try {
key.channel().close();// Sanitary close operation
clients.remove(key);
return;
} catch (IOException e) {
}
}
}
logger.info("[checkIdleSockets] Total connected : " + clients.size());
}
/**
* Method to check the sockets idle for 15 minutes and close the socket.
*/
private void checkIdleSockets() {
// synchronized (clients) {
Iterator<Entry<SocketChannel, Long>> iter = clients.entrySet().iterator();
while (iter.hasNext()) {
try {
Map.Entry<SocketChannel, Long> entry = iter.next();
SocketChannel client = entry.getKey();
long mills = entry.getValue();
double minutes = (System.currentTimeMillis() - mills) / (double) (1000 * 60);
if (minutes > 5) {
/* key is idle for */
logger.info("[IdleSocketChecker] Socket is idle for " + Math.round(minutes) + ", closing......");
try {
client.close();
} catch (IOException e) {
} finally {
iter.remove();
}
}
} catch (Exception e) {
logger.info("[IdleSocketChecker] ", e);
}
}
// }
logger.info("[checkIdleSockets] Total connected : " + clients.size());
}
/**
* {#link IdleSocketChecker} is a thread to check for any idle sockets in
* the selector.
*
* #author AchuthaRanga.Chenna
*
*/
private class IdleSocketChecker implements Runnable {
private boolean RUN = true;
#Override
public void run() {
try {
while (RUN) {
/* Wait for 5 Minutes */
Thread.sleep(5 * 60 * 1000);
checkIdleSockets();
}
} catch (InterruptedException e) {
logger.warn("[IdleSocketChecker]<run> IdleSocketChecker thread stopped", e);
}
}
}
}
This question already has answers here:
Continuously read objects from an ObjectInputStream in Java
(4 answers)
Closed 7 years ago.
A couple of weeks ago, I posted the following question because I had problems with reading objects from an ObjectInputStream using readObject:
Continuously read objects from an ObjectInputStream in Java
With the responds I got, I think I was able to understand what is going wrong -> I am calling readObject in a loop, even if no data has been send en therefore I receive an EOFException.
However, because I really want a mechanism where I am continuesly reading from the input stream I am looking for a solution for this problem.
I tried to use the following to create a mechanism where I only call readObject when there is data available:
if(mObjectIn.available() > 0)
mObjectIn.readObject()
But unfornately, mObjectIn.available() always returns 0.
Can anyone get me in the good direction. Is it possible at all to implement what I want??
You can send an int through the ObjectOutputStream to let the other side know when you will stop sending objects.
For example:
public static void main(String[] args) {
//SERVER
new Thread(new Runnable() {
#Override
public void run() {
try (ServerSocket ss = new ServerSocket(1234)) {
try (Socket s = ss.accept()) {
try (ObjectInputStream ois = new ObjectInputStream(
s.getInputStream())) {
while (ois.readInt() != -1) {//Read objects until the other side sends -1.
System.out.println(ois.readObject());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//CLIENT
try (Socket s = new Socket(InetAddress.getByName("localhost"), 1234)) {
try (ObjectOutputStream oos = new ObjectOutputStream(
s.getOutputStream())) {
for (int i = 0; i < 10; i++) {
oos.writeInt(1);//Specify that you are still sending objects.
oos.writeObject("Object" + i);
oos.flush();
}
oos.writeInt(-1);//Let the other side know that you've stopped sending object.
}
} catch (Exception e) {
e.printStackTrace();
}
}
Or you can write a null object at the end to let the other side know you won't be sending any more objects. This will work only if you are sure that none of the objects you need to send are null.
new Thread(new Runnable() {
#Override
public void run() {
try (ServerSocket ss = new ServerSocket(1234)) {
try (Socket s = ss.accept()) {
try (ObjectInputStream ois = new ObjectInputStream(
s.getInputStream())) {
String obj;
while ((obj = (String) ois.readObject()) != null) {
System.out.println(obj);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
try (Socket s = new Socket(InetAddress.getByName("localhost"), 1234)) {
try (ObjectOutputStream oos = new ObjectOutputStream(
s.getOutputStream())) {
for (int i = 0; i < 10; i++) {
oos.writeObject("Object" + i);
oos.flush();
}
oos.writeObject(null);
}
} catch (Exception e) {
e.printStackTrace();
}
I have written client/server tcp code in Java before. Streams can be tricky. When you work with streams you cannot assume all the data is there when you do a read. Instead you have to pull bytes out of the stream and if some msg delimiter is reached, then you take all those acquired bytes and process them. In your case you will turn them into an object.
Below are three classes I use to extract msg's from a socket.
First the MsgExtractor that get as input bytes that would essentially come from a socket. It checks to see if a msg has arrived (via the delimiter).
import org.apache.log4j.Logger;
//~--- JDK imports ------------------------------------------------------------
import java.text.ParseException;
import java.util.Arrays;
import java.util.MissingResourceException;
import java.util.concurrent.BlockingQueue;
/**
* This class parses the data retrieved from the TCP socket streams for new messages. All new messages found are inserted into the shared message queue.
*
* #author jmartinez
*/
public class MsgExtractor {
//////////////////////////////////////////////////////////////////////////
// STATIC VARIBLES
private static final Logger logger = Logger.getLogger(MsgExtractor.class);
///////////////////////////////////////////////////////////////////////
// CONSTANTS
private final int INIT_BUFFER_SIZE = 10000;
// <buffer variables>
private byte[] bufferedMsg = new byte[INIT_BUFFER_SIZE];
private int bufferSize = INIT_BUFFER_SIZE;
private int curBufferPos = 0; // ...current position on the buffered message
private int curMsgSize = 0; // ...current amount of buffered chars
///////////////////////////////////////////////////////////////////////
// VARIABLES
private final byte[] delimiter;
private final int delimiterSize;
private final BlockingQueue msgQueue;
private final int maxMsgSize;
// </>
////////////////////////////////////////////////////////////////////////
// FUNCTIONS
/**
* Creates a new MsgExtractor.
*
* #param msgService
*/
public MsgExtractor(MessageService msgService) {
ServerProperties properties = ServerProperties.getInstance();
if (properties == null) {
throw new MissingResourceException("unable to obtain properties", MsgExtractor.class.getName(),
"ServerProperties");
}
this.maxMsgSize = Integer.parseInt(properties.getProperty(ServerProperties.MAX_MESSAGE_SIZE));
this.delimiter = Arrays.copyOf(msgService.getMsgHandler().getMessageDelmiter(),
msgService.getMsgHandler().getMessageDelmiter().length);
this.delimiterSize = delimiter.length;
this.msgQueue = msgService.getSharedMsgQueue();
}
/**
* Inserts new chars into the message buffer. It then extracts any messages found in the buffer by checking for any occurrences of the message delimiter. Extracted messages are removed from the buffer, converted to String, and inserted into the ManagedQueue.
*
* #param cbuf - An array containing the new chars that need to be added to the message buffer.
* #param offset - Array offset from where on the array to start collecting the new chars.
* #param length - The number of chars that need to be collected.
* #throws java.lang.InterruptedException
* #throws java.text.ParseException
*/
public void insertNewChars(byte[] cbuf, int offset, int length) throws InterruptedException, ParseException {
// ...check if the message buffer has enough room to add the new chars... if not, increase the buffer size.
if (bufferSize < curMsgSize + length) {
increaseBufferSize();
}
// ...add the new chars to the buffer one at a time
for (int i = 0; i < length; i++) {
bufferedMsg[curMsgSize++] = cbuf[i + offset];
}
// ...keep checking for new messages as long as they are being found
boolean rv;
do {
rv = checkForNewMsg();
} while (rv == true);
if (curMsgSize > maxMsgSize) {
throw new ParseException("max message size reached and still not found delimiter", curMsgSize);
}
}
/**
* Doubles the message buffer size.
*/
private void increaseBufferSize() {
bufferSize *= 2;
byte[] temp = new byte[bufferSize];
System.arraycopy(bufferedMsg, 0, temp, 0, curMsgSize);
bufferedMsg = temp;
}
/**
* Checks if the delimiter is found in the currently buffered message.
* checkForNewMsg starts its search where it last left off at.
*
* Performance can be improved if this method checks for all occurrences of the message delimiter, instead of one.
*
* #return true if delimiter was found in buffer, else false
*/
private boolean checkForNewMsg() throws InterruptedException {
while (curBufferPos <= curMsgSize - delimiterSize) {
boolean delimitterFound = true;
for (int i = 0; i < delimiterSize; i++) {
if (delimiter[i] != bufferedMsg[i + curBufferPos]) {
delimitterFound = false;
break;
}
}
if (delimitterFound) {
extractNewMsg(curBufferPos);
return true;
} else {
curBufferPos++;
}
}
return false;
}
/**
* A new message is located at index = 0 through delimiterPos - 1. the method extracts that message and inserts it into a local String array.
*
* Performance can be improved if this method extracted a messages for all occurrences of the message delimiter, instead of one.
*
* #param delimiterPos - The position where the delimiter was found.
*/
private void extractNewMsg(int delimiterPos) throws InterruptedException {
try {
msgQueue.put(new String(bufferedMsg, 0, delimiterPos - 1));
} catch (InterruptedException ie) {
logger.error("Interrupted while putting message to ManagedQueue", ie);
throw ie;
} catch (Exception e) {
logger.error("Unable to put message to ManagedQueue", e);
}
// <reset the message buffer and corresponding variables>
byte[] tmpBuffer = new byte[this.bufferSize];
int tmpMsgSize = 0;
for (int i = delimiterPos + this.delimiterSize; i < curMsgSize; i++) {
tmpBuffer[tmpMsgSize++] = bufferedMsg[i];
}
curBufferPos = 0;
bufferedMsg = tmpBuffer;
curMsgSize = tmpMsgSize;
// </>
}
}
Here is the ConnectionHandler that manages the connection and feeds the MsgExtractor with bytes:
import org.apache.log4j.Logger;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class handles all new connections. It reads data from the socket stream
* and sends it to a MsgExtractor for further processing.
*
* This class in Runnable. use the run method to start it up and interrupt it to
* shut it down.
*
* #author Jose
*/
public class ConnectionHandler implements Runnable {
//////////////////////////////////////////////////////////////////////////
// STATIC VARIBLES
// ...log4j's Logger is thread safe
private static final Logger logger = Logger.getLogger(ConnectionHandler.class);
private final static AtomicBoolean isRunning = new AtomicBoolean(false);
/////////////////////////////////////////////////////////////////////////
// Constants
private final int BUFFER_SIZE = 8000;
private final byte[] rcvdChars = new byte[BUFFER_SIZE];
////////////////////////////////////////////////////////////////////////
// INSTANCE VARIABLES
private final Socket socket;
private final MsgExtractor msgExtractor;
/////////////////////////////////////////////////////////////////////////
// FUNCTIONS
/**
* Creates a new ConnectionHandler.
*
* #param socket - The socket that this object is to read from.
* #param msgService - The MessageService that is used to create the
* MsgExtractor object that this object uses.
*/
public ConnectionHandler(Socket socket, MessageService msgService) {
this.socket = socket;
logger.info("ConnectionHandler thread ID:" + Thread.currentThread().getId() + " instanctiated for listen port "
+ socket.getLocalPort());
msgExtractor = new MsgExtractor(msgService);
}
/**
* Starts the ConnectionHandler. Creates an input stream from this objects
* socket to read data from. all read data is sent to a MsgExtractor. The
* MSgExtractor will extract messages from the read data and will add any
* messages to this objects ManagedQueue. This method continues operating
* till the thread is interrupted or the socket is no longer available for
* providing input. Returns right away if validation of this object fails.
*/
public void run() {
// ...if validation fails, return
if (isValid() == false) {
return;
}
// ...if already running, return
if (!isRunning.compareAndSet(false, true)) {
logger.warn("ConnectionHandler thead ID:" + Thread.currentThread().getId()
+ " is already running, not going to run again.");
return;
}
logger.info("ConnectionHandler thead ID:" + Thread.currentThread().getId() + " is starting up.");
// <get input reader from socket>
InputStream inputReader;
try {
inputReader = socket.getInputStream();
} catch (IOException ex) {
logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
+ ", failed to get socket input stream in ParserThread.run", ex);
return;
}
// </>
// ...bytes read from the socket
int bytesRead;
try {
// ...stops when the thread is interrupted or the socket no longer provides input
while ((socket.isInputShutdown() == false) || (Thread.interrupted() == false)) {
try {
// ...get data from socket stream
bytesRead = inputReader.read(rcvdChars, 0, BUFFER_SIZE);
} catch (IOException e) { // ... catch any exception and call it a day for this thread
logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
+ ", encountered error reading from socket, could be a closed connection.", e);
break;
}
try {
msgExtractor.insertNewChars(rcvdChars, 0, bytesRead);
} catch (ParseException pe) {
logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
+ ", encountered parsing error, closing connection.", pe);
break;
} catch (InterruptedException ex) {
break;
}
}
} finally {
// ...close the socket if it is still open
if (socket.isClosed() == false) {
try {
socket.close();
} catch (IOException ex) {
logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
+ ", failed to close socket.", ex);
}
}
isRunning.set(false);
} // end of: finally
logger.info("ConnectionHandler thead ID:" + Thread.currentThread().getId() + " is shutting down.");
}
/**
* Used by the run() method to validate this object. If validation fails,
* the run() method returns right away.
*
* #return - Returns true is this object is valid, else false.
*/
private boolean isValid() {
if (socket == null) {
logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
+ ", validation failed, Socket is null");
return false;
}
return true;
}
}
And for completion here is the MsgService that pops up in the above code:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* The Model Object, or DTO, of the CMS server. It contains information that is needed by just about every component of the CMS server.
*
* There is a separate instance of this class for each messaging service that the CMS server is configured for.
*
* #author Jose
*/
public class MessageService {
/**
* the shared message queue where new messages are inserted into by the MsgExtractor, and taken by the MessageQueueWorker.
*/
private final BlockingQueue<byte[]> sharedMsgQueue = new LinkedBlockingQueue<byte[]>();
/**
* the TCP listen port
*/
private final int port;
/**
* the MessageHandler that will process new messages
*/
private final MessageHandler msgHandler;
/**
* optional. max number of TCP connections
*/
private final int maxConnections;
/**
* optional. max number of worker threads that will be used to process new messages
*/
private final int maxWorkerThreads;
/**
* Creates new message service object. Sets max connections and max worker threads to 0.
*
* #param port - TCP port this message service will listen on.
* #param msgHandler - the MessageHandler that will be called to process messages received for this message service.
*/
public MessageService(int port, MessageHandler msgHandler) {
this.port = port;
this.msgHandler = msgHandler.getNewInstance();
this.maxConnections = 0;
this.maxWorkerThreads = 0;
}
/**
* Creates new message service object. Sets max worker threads to 0.
*
* #param port - TCP port this message service will listen on.
* #param msgHandler - the MessageHandler that will be called to process messages received for this message service.
* #param connections - max concurrent connections available for this service.
*/
public MessageService(int port, MessageHandler msgHandler, int connections) {
this.port = port;
this.msgHandler = msgHandler.getNewInstance();
this.maxConnections = connections;
this.maxWorkerThreads = 0;
}
/**
* Creates new message service object.
*
* #param port - TCP port this message service will listen on.
* #param msgHandler - the MessageHandler that will be called to process messages received for this message service.
* #param connections - max concurrent connections available for this service.
* #param workerThreads - max worker threads that will process messages for this message service.
*/
public MessageService(int port, MessageHandler msgHandler, int connections, int workerThreads) {
this.port = port;
this.msgHandler = msgHandler.getNewInstance();
this.maxConnections = connections;
this.maxWorkerThreads = workerThreads;
}
/**
*
* #return this object's MessageHandler
*/
public MessageHandler getMsgHandler() {
return msgHandler.getNewInstance();
}
/**
*
* #return the TCP port this MessageService will listen on
*/
public int getPort() {
return port;
}
/**
*
* #return the BlockingQueue used to store new messages.
*/
public BlockingQueue<byte[]> getSharedMsgQueue() {
return sharedMsgQueue;
}
/**
*
* #return max concurrent connections available for this service
*/
public int getMaxConnections() {
return maxConnections;
}
/**
*
* #return max worker threads that will process messages for this message service
*/
public int getMaxWorkerThreads() {
return this.maxWorkerThreads;
}
}
I am using a Teensy microcontroller (https://www.pjrc.com/teensy/teensy31.html) to connect to an Android application to get Bluetooth.
I have used this Android application with other embedded bluetooth devices successfully, however now I am running into the following error I see in LogCat:
04-02 20:06:29.713: E/BTLD(5499):
######################################################################
04-02 20:06:29.713: E/BTLD(5499): #
04-02 20:06:29.713: E/BTLD(5499): # WARNING : BTU HCI(id=0) command timeout. opcode=0x405
04-02 20:06:29.713: E/BTLD(5499): #
04-02 20:06:29.713: E/BTLD(5499): ######################################################################
I have zero idea where this error is coming from. I also see the following error occasionally:
04-02 20:25:19.242: E/bt-btif(914): DISCOVERY_COMP_EVT slot id:11, failed to find channle, status:1, scn:0
The worst part is, with my current hardware setup, half the time it will work, the other half it wont. And I have no clue why. For reference, I am using this bluetooth module: https://www.sparkfun.com/products/12577 And I have the pins connected:
Vcc <---> Vcc
GND <---> GND
TX (BT) <---> RX1 (Teensy)
RX (BT) <---> TX1 (Teensy)
The lack of consistency is killing me. Sometimes just pushing the reset button on the Teensy fixes it.
I am working off of this tutorial: http://stafava.blogspot.ca/2012/12/connect-teensy-to-bluetooth-module.html Here is the Ardunio Microcontroller code running on the Teensy:
HardwareSerial bt = HardwareSerial();
#define Seria1_PORT_SPEED 115200
void setup()
{
bt.begin(Seria1_PORT_SPEED);
bt.println();
Wire.begin();
}
void loop()
{
////////////////// Bluetooth stuff /////////////////////////
if (bt.available() >= 2) {
if (bt.read() == '#') {// Start of new control message
int command = bt.read(); // Commands
if (command == 'f') {// request one output _f_rame
//output_single_on = true; //unused?
} else if (command == 's') { // _s_ynch request
// Read ID
byte id[2];
id[0] = readChar();
id[1] = readChar();
// Reply with synch message
bt.print("#SYNCH");
bt.write(id, 2);
bt.println();
}
}
}
sendDataToAndroid();
}
void sendDataToAndroid() { //arrays defined as float array[3] = ...
bt.write((byte*) array1, 12);
bt.write((byte*) array2, 12);
bt.write((byte*) array3, 12);
}
char readChar()
{
while (bt.available() < 1) { } // Block
return bt.read();
}
And here is the relevant Android code taken from this tutorial: https://github.com/ptrbrtz/razor-9dof-ahrs/tree/master/Arduino/Razor_AHRS
package com.jest.razor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import org.apache.http.util.EncodingUtils;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
/**
* Class to easily interface the Razor AHRS via Bluetooth.
* <p>
* Bluetooth seems to be even more picky on Android than it is anyway. Be sure to have a look at the
* section about Android Bluetooth in the tutorial at
* <a href="https://github.com/ptrbrtz/razor-9dof-ahrs">
* https://github.com/ptrbrtz/razor-9dof-ahrs</a>!
* <p>
* The app using this class has to
* <ul>
* <li>target Android 2.0 (API Level 5) or later.
* <li>specify the uses-permissions <code>BLUETOOTH</code> and <code>BLUETOOTH_ADMIN</code> in it's
* AndroidManifest.xml.
* <li>add this Library Project as a referenced library (Project Properties -> Android -> Library)
* </ul>
* <p>
* TODOs:
* <ul>
* <li>Add support for USB OTG (Android device used as USB host), if using FTDI is possible.
* </ul>
*
* #author Peter Bartz
*/
public class RazorAHRS {
private static final String TAG = "RazorAHRS";
private static final boolean DEBUG = false;
private static final String SYNCH_TOKEN = "#SYNCH";
private static final String NEW_LINE = "\r\n";
// Timeout to init Razor AHRS after a Bluetooth connection has been established
public static final int INIT_TIMEOUT_MS = 10000;
// IDs passed to internal message handler
private static final int MSG_ID__YPR_DATA = 0;
private static final int MSG_ID__IO_EXCEPTION_AND_DISCONNECT = 1;
private static final int MSG_ID__CONNECT_OK = 2;
private static final int MSG_ID__CONNECT_FAIL = 3;
private static final int MSG_ID__CONNECT_ATTEMPT = 4;
private static final int MSG_ID__AMG_DATA = 5;
private static final UUID UUID_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
/**
* Razor output modes.
* Use <code>YAW_PITCH_ROLL_ANGLES</code> to receive yaw, pitch and roll in degrees. <br>
* Use <code>RAW_SENSOR_DATA</code> or <code>CALIBRATED_SENSOR_DATA</code> to read raw or
* calibrated xyz sensor data of the accelerometer, magnetometer and the gyroscope.
*/
public enum RazorOutputMode {
YAW_PITCH_ROLL_ANGLES,
RAW_SENSOR_DATA,
CALIBRATED_SENSOR_DATA
}
private RazorOutputMode razorOutputMode;
private enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
USER_DISCONNECT_REQUEST
}
volatile private ConnectionState connectionState = ConnectionState.DISCONNECTED;
volatile private BluetoothSocket btSocket;
volatile private BluetoothDevice btDevice;
volatile private InputStream inStream;
volatile private OutputStream outStream;
private RazorListener razorListener;
private boolean callbacksEnabled = true;
BluetoothThread btThread;
private int numConnectAttempts;
// Object pools
private ObjectPool<float[]> float3Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() {
#Override
public float[] newObject() {
return new float[3];
}
});
private ObjectPool<float[]> float9Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() {
#Override
public float[] newObject() {
return new float[9];
}
});
/**
* Constructor.
* Must be called from the thread where you want receive the RazorListener callbacks! So if you
* want to manipulate Android UI from the callbacks you have to call this from your main/UI
* thread.
*
* #param btDevice {#link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor
* AHRS to connect to.
* #param razorListener {#link RazorListener} that will be notified of Razor AHRS events.
* #throws RuntimeException thrown if one of the parameters is null.
*/
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener)
throws RuntimeException {
this(btDevice, razorListener, RazorOutputMode.CALIBRATED_SENSOR_DATA);
}
/**
* Constructor.
* Must be called from the thread where you want receive the RazorListener callbacks! So if you
* want to manipulate Android UI from the callbacks you have to call this from your main/UI
* thread.
*
* #param btDevice {#link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor
* AHRS to connect to.
* #param razorListener {#link RazorListener} that will be notified of Razor AHRS events.
* #param razorOutputMode {#link RazorOutputMode} that you desire.
* #throws RuntimeException thrown if one of the parameters is null.
*/
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener, RazorOutputMode razorOutputMode)
throws RuntimeException {
if (btDevice == null)
throw new RuntimeException("BluetoothDevice can not be null.");
this.btDevice = btDevice;
if (razorListener == null)
throw new RuntimeException("RazorListener can not be null.");
this.razorListener = razorListener;
if (razorOutputMode == null)
throw new RuntimeException("RazorMode can not be null.");
this.razorOutputMode = razorOutputMode;
}
/**
* #return <code>true</code> if listener callbacks are currently enabled, <code>false</code> else.
*/
public boolean getCallbacksEnabled() {
return callbacksEnabled;
}
/**
* Enables/disables listener callbacks.
* #param enabled
*/
public void setCallbacksEnabled(boolean enabled) {
callbacksEnabled = enabled;
}
/**
* Connect and start reading. Both is done asynchronously. {#link RazorListener#onConnectOk()}
* or {#link RazorListener#onConnectFail(IOException)} callbacks will be invoked.
*
* #param numConnectAttempts Number of attempts to make when trying to connect. Often connecting
* only works on the 2rd try or later. Bluetooth hooray.
*/
public void asyncConnect(int numConnectAttempts) {
if (DEBUG) Log.d(TAG, "asyncConnect() BEGIN");
// Disconnect and wait for running thread to end, if needed
if (btThread != null) {
asyncDisconnect();
try {
btThread.join();
} catch (InterruptedException e) { }
}
// Bluetooth thread not running any more, we're definitely in DISCONNECTED state now
// Create new thread to connect to Razor AHRS and read input
this.numConnectAttempts = numConnectAttempts;
connectionState = ConnectionState.CONNECTING;
btThread = new BluetoothThread();
btThread.start();
if (DEBUG) Log.d(TAG, "asyncConnect() END");
}
/**
* Disconnects from Razor AHRS. If still connecting this will also cancel the connection process.
*/
public void asyncDisconnect() {
if (DEBUG) Log.d(TAG, "asyncDisconnect() BEGIN");
synchronized (connectionState) {
if (DEBUG) Log.d(TAG, "asyncDisconnect() SNYNCHRONIZED");
// Don't go to USER_DISCONNECT_REQUEST state if we are disconnected already
if (connectionState == ConnectionState.DISCONNECTED)
return;
// This is a wanted disconnect, so we force (blocking) I/O to break
connectionState = ConnectionState.USER_DISCONNECT_REQUEST;
closeSocketAndStreams();
}
if (DEBUG) Log.d(TAG, "asyncDisconnect() END");
}
/**
* Writes out a string using ASCII encoding. Assumes we're connected. Does not handle
* exceptions itself.
*
* #param text Text to send out
* #throws IOException
*/
private void write(String text) throws IOException {
outStream.write(EncodingUtils.getAsciiBytes(text));
}
/**
* Closes I/O streams and Bluetooth socket.
*/
private void closeSocketAndStreams() {
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() BEGIN");
// Try to switch off streaming output of Razor in preparation of next connect
try {
if (outStream != null)
write("#o0");
} catch (IOException e) { }
// Close Bluetooth socket => I/O operations immediately will throw exception
try {
if (btSocket != null)
btSocket.close();
} catch (IOException e) { }
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() BT SOCKET CLOSED");
// Close streams
try {
if (inStream != null)
inStream.close();
} catch (IOException e) { }
try {
if (outStream != null)
outStream.close();
} catch (IOException e) { }
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() STREAMS CLOSED");
// Do not set socket and streams null, because input thread might still access them
//inStream = null;
//outStream = null;
//btSocket = null;
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() END");
}
/**
* Thread that handles connecting to and reading from Razor AHRS.
*/
private class BluetoothThread extends Thread {
byte[] inBuf = new byte[512];
int inBufPos = 0;
/**
* Blocks until it can read one byte of input, assumes we have a connection up and running.
*
* #return One byte from input stream
* #throws IOException If reading input stream fails
*/
private byte readByte() throws IOException {
int in = inStream.read();
if (in == -1)
throw new IOException("End of Stream");
return (byte) in;
}
/**
* Converts a buffer of bytes to an array of floats. This method does not do any error
* checking on parameter array sizes.
* #param byteBuf Byte buffer with length of at least <code>numFloats * 4</code>.
* #param floatArr Float array with length of at least <code>numFloats</code>.
* #param numFloats Number of floats to convert
*/
private void byteBufferToFloatArray(byte[] byteBuf, float[] floatArr, int numFloats) {
//int numFloats = byteBuf.length / 4;
for (int i = 0; i < numFloats * 4; i += 4) {
// Convert from little endian (Razor) to big endian (Java) and interpret as float
floatArr[i/4] = Float.intBitsToFloat((byteBuf[i] & 0xff) + ((byteBuf[i+1] & 0xff) << 8) +
((byteBuf[i+2] & 0xff) << 16) + ((byteBuf[i+3] & 0xff) << 24));
}
}
/**
* Parse input stream for given token.
* #param token Token to find
* #param in Next byte from input stream
* #return <code>true</code> if token was found
*/
private boolean readToken(byte[] token, byte in) {
if (in == token[inBufPos++]) {
if (inBufPos == token.length) {
// Synch token found
inBufPos = 0;
if (DEBUG) Log.d(TAG, "Token found");
return true;
}
} else {
inBufPos = 0;
}
return false;
}
/**
* Synches with Razor AHRS and sets parameters.
* #throws IOException
*/
private void initRazor() throws IOException {
long t0, t1, t2;
// Start time
t0 = SystemClock.uptimeMillis();
/* See if Razor is there */
// Request synch token to see when Razor is up and running
final String contactSynchID = "00";
final String contactSynchRequest = "#s" + contactSynchID;
final byte[] contactSynchReply = EncodingUtils.getAsciiBytes(SYNCH_TOKEN + contactSynchID + NEW_LINE);
write(contactSynchRequest);
t1 = SystemClock.uptimeMillis();
while (true) {
// Check timeout
t2 = SystemClock.uptimeMillis();
if (t2 - t1 > 200) {
// 200ms elapsed since last request and no answer -> request synch again.
// (This happens when DTR is connected and Razor resets on connect)
write(contactSynchRequest);
t1 = t2;
}
if (t2 - t0 > INIT_TIMEOUT_MS)
// Timeout -> tracker not present
throw new IOException("Can not init Razor: response timeout");
// See if we can read something
if (inStream.available() > 0) {
// Synch token found?
if (readToken(contactSynchReply, readByte()))
break;
} else {
// No data available, wait
delay(5); // 5ms
}
}
/* Configure tracker */
// Set binary output mode, enable continuous streaming, disable errors and request synch
// token. So we're good, no matter what state the tracker currently is in.
final String configSynchID = "01";
final byte[] configSynchReply = EncodingUtils.getAsciiBytes(SYNCH_TOKEN + configSynchID + NEW_LINE);
write("#ob#o1#oe0#s" + configSynchID);
while (!readToken(configSynchReply, readByte())) { }
}
/**
* Opens Bluetooth connection to Razor AHRS.
* #throws IOException
*/
private void connect() throws IOException {
// Create Bluetooth socket
btSocket = btDevice.createRfcommSocketToServiceRecord(UUID_SPP);
if (btSocket == null) {
if (DEBUG) Log.d(TAG, "btSocket is null in connect()");
throw new IOException("Could not create Bluetooth socket");
}
// This could be used to create the RFCOMM socekt on older Android devices where
//createRfcommSocketToServiceRecord is not present yet.
/*try {
Method m = btDevice.getClass().getMethod("createRfcommSocket", new Class[] { int.class });
btSocket = (BluetoothSocket) m.invoke(btDevice, Integer.valueOf(1));
} catch (Exception e) {
throw new IOException("Could not create Bluetooth socket using reflection");
}*/
// Connect socket to Razor AHRS
if (DEBUG) Log.d(TAG, "Canceling bt discovery");
BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); // Recommended
if (DEBUG) Log.d(TAG, "Trying to connect() btSocket");
btSocket.connect();
// Get the input and output streams
if (DEBUG) Log.d(TAG, "Trying to create streams");
inStream = btSocket.getInputStream();
outStream = btSocket.getOutputStream();
if (inStream == null || outStream == null) {
if (DEBUG) Log.d(TAG, "Could not create I/O stream(s) in connect()");
throw new IOException("Could not create I/O stream(s)");
}
}
/**
* Bluetooth I/O thread entry method.
*/
public void run() {
if (DEBUG) Log.d(TAG, "Bluetooth I/O thread started");
try {
// Check if btDevice is set
if (btDevice == null) {
if (DEBUG) Log.d(TAG, "btDevice is null in run()");
throw new IOException("Bluetooth device is null");
}
// Make several attempts to connect
int i = 1;
while (true) {
if (DEBUG) Log.d(TAG, "Connect attempt " + i + " of " + numConnectAttempts);
sendToParentThread(MSG_ID__CONNECT_ATTEMPT, i);
try {
connect();
break; // Alrighty!
} catch (IOException e) {
if (DEBUG) Log.d(TAG, "Attempt failed: " + e.getMessage());
// Maximum number of attempts reached or cancel requested?
if (i == numConnectAttempts || connectionState == ConnectionState.USER_DISCONNECT_REQUEST)
throw e;
// We couldn't connect on first try, manually starting Bluetooth discovery
// often helps
if (DEBUG) Log.d(TAG, "Starting BT discovery");
BluetoothAdapter.getDefaultAdapter().startDiscovery();
delay(5000); // 5 seconds - long enough?
i++;
}
}
// Set Razor output mode
if (DEBUG) Log.d(TAG, "Trying to set Razor output mode");
initRazor();
// We're connected and initialized (unless disconnect was requested)
synchronized (connectionState) {
if (connectionState == ConnectionState.USER_DISCONNECT_REQUEST) {
closeSocketAndStreams();
throw new IOException(); // Dummy exception to force disconnect
}
else connectionState = ConnectionState.CONNECTED;
}
// Tell listener we're ready
sendToParentThread(MSG_ID__CONNECT_OK, null);
// Keep reading inStream until an exception occurs
if (DEBUG) Log.d(TAG, "Starting input loop");
while (true) {
// Read byte from input stream
inBuf[inBufPos++] = (byte) readByte();
if (razorOutputMode == RazorOutputMode.YAW_PITCH_ROLL_ANGLES) {
if (inBufPos == 12) { // We received a full frame
float[] ypr = float3Pool.get();
byteBufferToFloatArray(inBuf, ypr, 3);
// Forward to parent thread handler
sendToParentThread(MSG_ID__YPR_DATA, ypr);
// Rewind input buffer position
inBufPos = 0;
}
} else { // Raw or calibrated sensor data mode
if (inBufPos == 36) { // We received a full frame
float[] amg = float9Pool.get();
byteBufferToFloatArray(inBuf, amg, 9);
// Forward to parent thread handler
sendToParentThread(MSG_ID__AMG_DATA, amg);
// Rewind input buffer position
inBufPos = 0;
}
}
}
} catch (IOException e) {
if (DEBUG) Log.d(TAG, "IOException in Bluetooth thread: " + e.getMessage());
synchronized (connectionState) {
// Don't forward exception if it was thrown because we broke I/O on purpose in
// other thread when user requested disconnect
if (connectionState != ConnectionState.USER_DISCONNECT_REQUEST) {
// There was a true I/O error, cleanup and forward exception
closeSocketAndStreams();
if (DEBUG) Log.d(TAG, "Forwarding exception");
if (connectionState == ConnectionState.CONNECTING)
sendToParentThread(MSG_ID__CONNECT_FAIL, e);
else
sendToParentThread(MSG_ID__IO_EXCEPTION_AND_DISCONNECT, e);
} else {
// I/O error was caused on purpose, socket and streams are closed already
}
// I/O closed, thread done => we're disconnected now
connectionState = ConnectionState.DISCONNECTED;
}
}
}
/**
* Sends a message to Handler assigned to parent thread.
*
* #param msgId
* #param data
*/
private void sendToParentThread(int msgId, Object o) {
if (callbacksEnabled)
parentThreadHandler.obtainMessage(msgId, o).sendToTarget();
}
/**
* Sends a message to Handler assigned to parent thread.
*
* #param msgId
* #param data
*/
private void sendToParentThread(int msgId, int i) {
if (callbacksEnabled)
parentThreadHandler.obtainMessage(msgId, i, -1).sendToTarget();
}
/**
* Wrapper for {#link Thread#sleep(long)};
* #param ms Milliseconds
*/
void delay(long ms) {
try {
sleep(ms); // Sleep 5ms
} catch (InterruptedException e) { }
}
}
/**
* Handler that forwards messages to the RazorListener callbacks. This handler runs in the
* thread this RazorAHRS object was created in and receives data from the Bluetooth I/O thread.
*/
private Handler parentThreadHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID__YPR_DATA: // Yaw, pitch and roll data
float[] ypr = (float[]) msg.obj;
razorListener.onAnglesUpdate(ypr[0], ypr[1], ypr[2]);
float3Pool.put(ypr);
break;
case MSG_ID__AMG_DATA: // Accelerometer, magnetometer and gyroscope data
float[] amg = (float[]) msg.obj;
razorListener.onSensorsUpdate(amg[0], amg[1], amg[2], amg[3], amg[4], amg[5],
amg[6], amg[7], amg[8]);
float9Pool.put(amg);
break;
case MSG_ID__IO_EXCEPTION_AND_DISCONNECT:
razorListener.onIOExceptionAndDisconnect((IOException) msg.obj);
break;
case MSG_ID__CONNECT_ATTEMPT:
razorListener.onConnectAttempt(msg.arg1, numConnectAttempts);
break;
case MSG_ID__CONNECT_OK:
razorListener.onConnectOk();
break;
case MSG_ID__CONNECT_FAIL:
razorListener.onConnectFail((IOException) msg.obj);
break;
}
}
};
}