0% found this document useful (0 votes)
6 views12 pages

Bluetooth Connection Service Code

The document outlines the implementation of a Bluetooth connection service for managing connections to weight scales in an Android application. It includes features for connecting, disconnecting, sending commands, and handling responses, as well as managing tare operations and weight data processing. The service operates in the foreground and utilizes callbacks for disconnection and tare status updates, while ensuring Bluetooth permissions are checked and managed appropriately.

Uploaded by

marycentbirichia
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views12 pages

Bluetooth Connection Service Code

The document outlines the implementation of a Bluetooth connection service for managing connections to weight scales in an Android application. It includes features for connecting, disconnecting, sending commands, and handling responses, as well as managing tare operations and weight data processing. The service operates in the foreground and utilizes callbacks for disconnection and tare status updates, while ensuring Bluetooth permissions are checked and managed appropriately.

Uploaded by

marycentbirichia
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

public class BluetoothConnectionService extends Service {

private ScaleState currentState = [Link];


public interface BluetoothDisconnectCallback {
void onDisconnected();
}

public interface TareCallback {


void onTareSet(boolean success);
}
private BlockingQueue<String> responseQueue = new
LinkedBlockingQueue<>();
private static final long RESPONSE_TIMEOUT = 10000; // 5 seconds
private static final int MAX_RETRY_COUNT = 5; // Define your max retry
count
private static final long RETRY_INTERVAL = 5000; // Define your retry
interval in milliseconds
private String lastConnectedDeviceAddress; // Ensure this is set when you
connect to a device
private boolean responseReceived = false;
private String tareSetResponse1 = "T S";
private String tareSetResponse2 = "MT S";
private String tareClearedResponse = "CT S";
private static final long COMMAND_TIMEOUT = 10000; // 5 seconds
private final LinkedBlockingQueue<String> commandQueue = new
LinkedBlockingQueue<>();
private boolean isProcessingCommands = false;
private static final String TARE_COMMAND = "T\r\n";
private static final String CLEAR_TARE_COMMAND = "CT\r\n";
private static final long TARE_RESPONSE_TIMEOUT = 5000;
private TareCallback tareCallback;
private static final int CONNECTION_TIMEOUT_MS = 10000; // Timeout for
connection attempts in milliseconds
private static final int NOTIFICATION_ID = 1; // Notification ID for
foreground service
private static final String CHANNEL_ID =
"BluetoothConnectionServiceChannel"; // Notification channel ID
private Handler connectionTimeoutHandler;
private boolean tareActive = false; // Private member variable to track
tare status
private WeightScaleManager weightScaleManager;
private BluetoothDevice connectedDevice;
private BluetoothSocket socket;
private boolean isConnected = false;
private boolean isConnecting = false; // Track if a connection attempt is
in progress
private static final int BLUETOOTH_PERMISSION_REQUEST_CODE = 1;
private static final UUID SPP_UUID = [Link]("00001101-0000-1000-
8000-00805F9B34FB");
private ReadsViewModel viewModel;
private static BluetoothConnectionService instance;
private BluetoothDisconnectCallback disconnectCallback; // Callback to
notify disconnection
public static final String ACTION_CONNECT =
"[Link].ACTION_CONNECT";

public BluetoothConnectionService() {
// Constructor - no initialization needed here
}

public static synchronized BluetoothConnectionService getInstance() {


if (instance == null) {
instance = new BluetoothConnectionService();
}
return instance;
}
// Add more potential tare success responses
private final List<String> tareSuccessResponses = [Link](
"T S", "MT S", "Tare Set", "Tare Success", "0.00", "0.000"
);
@Override
public void onCreate() {
[Link]();
instance = this; // Set the singleton instance
weightScaleManager = new WeightScaleManager(this); // Correctly
passing the service context
viewModel = [Link](this);

// Create a notification channel for the foreground service


createNotificationChannel();
}

private void enqueueCommand(String command) {


[Link](command);
if (!isProcessingCommands) {
processCommandQueue();
}
}

private void processCommandQueue() {


new Thread(() -> {
isProcessingCommands = true;
while (![Link]()) {
String command = [Link]();
boolean success = sendCommand(command);
if ([Link](TARE_COMMAND) ||
[Link](CLEAR_TARE_COMMAND)) {
handleTareResult(success, [Link](TARE_COMMAND));
}
try {
[Link](1000); // Wait between commands
} catch (InterruptedException e) {
Log.e(TAG, "Command processing interrupted", e);
}
}
isProcessingCommands = false;
}).start();
}

private void handleTareResult(boolean success, boolean isSetting) {


if (success) {
updateScaleState(isSetting ? [Link] :
ScaleState.TARE_CLEARED);
} else {
// If tare operation failed, revert to previous state or WEIGHING
updateScaleState(currentState == [Link] ?
[Link] : currentState);
}
handleTareStatus(success && isSetting);
if (tareCallback != null) {
new Handler([Link]()).post(() ->
[Link](success));
}
}
public ScaleState getCurrentState() {
return currentState;
}
public void setTare() {
Log.d(TAG, "setTare() called. Current state: " + currentState);
if (currentState != [Link]) {
Log.i(TAG, "Enqueueing TARE_COMMAND");
enqueueCommand(TARE_COMMAND);
} else {
Log.i(TAG, "Scale is already tared");
}
}

public void clearTare() {


if (currentState == [Link]) {
enqueueCommand(CLEAR_TARE_COMMAND);
} else {
Log.i(TAG, "Scale is not currently tared");
}
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
// Start the service in the foreground immediately to avoid the
RemoteServiceException
startForegroundServiceWithNotification();

String action = [Link]();


if (ACTION_CONNECT.equals(action)) {
String deviceAddress =
[Link]("DEVICE_ADDRESS");
if (deviceAddress != null) {
connectToDevice(deviceAddress);
} else {
Log.i("BluetoothConnectionService", "No device address
provided.");
}
}
}
return START_STICKY;
}

private void startForegroundServiceWithNotification() {


Notification notification = new [Link](this,
CHANNEL_ID)
.setContentTitle("Bluetooth Connection")
.setContentText("Connecting to the device...")
.setSmallIcon([Link].ic_bluetooth)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();

startForeground(NOTIFICATION_ID, notification);
}

private void createNotificationChannel() {


if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Bluetooth Connection Service";
String description = "Channel for Bluetooth Connection Service";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
name, importance);
[Link](description);

NotificationManager notificationManager =
getSystemService([Link]);
if (notificationManager != null) {
[Link](channel);
}
}
}

public boolean isConnected() {


return isConnected;
}

public boolean isConnecting() {


return isConnecting;
}

public void setDisconnectCallback(BluetoothDisconnectCallback callback) {


[Link] = callback;
}

public void setTareCallback(TareCallback callback) {


[Link] = callback;
}

public void connectToDevice(String deviceAddress) {


if (isConnected || isConnecting) {
Log.i("BluetoothConnectionService", "Already in connection
process.");
return; // Exit if already connected or in connecting process
}

if (!checkBluetoothPermissions()) {
Log.e("BluetoothConnectionService", "Bluetooth permissions not
granted");
requestBluetoothPermissions();

onConnectionStateChanged([Link]);
return;
}
setConnecting(true);

onConnectionStateChanged([Link]);
Log.i("BluetoothConnectionService", "Connecting to device: " +
deviceAddress);

connectionTimeoutHandler = new Handler([Link]());


[Link](() -> {
if (isConnecting) {
Log.e("BluetoothConnectionService", "Connection attempt timed
out.");
handleConnectionFailure("Connection timed out. Please try
again.");
}
}, CONNECTION_TIMEOUT_MS);

new Thread(() -> {


try {
if (socket != null) {
disconnect();
}

connectedDevice =
[Link]().getRemoteDevice(deviceAddress);
socket =
[Link](SPP_UUID);
[Link]();
isConnected = true;

[Link](null);
Log.i("BluetoothConnectionService", "Connection
successful.");

onConnectionStateChanged([Link]);
startReading();

} catch (IOException e) {
Log.e("BluetoothConnectionService", "Connection failed", e);
handleConnectionFailure("Connection failed. Please check the
device.");
} catch (SecurityException e) {
Log.e("BluetoothConnectionService", "Bluetooth permission
denied", e);
handleConnectionFailure("Bluetooth permission denied. Please
enable permissions.");
} catch (Exception e) {
Log.e("BluetoothConnectionService", "Unexpected error", e);
handleConnectionFailure("An unexpected error occurred: " +
[Link]());
}
}).start();
}

private void handleConnectionFailure(String message) {


isConnected = false;
setConnecting(false);
[Link](null);
onConnectionStateChanged([Link]);
showToast(message);
}

private void setConnecting(boolean connecting) {


isConnecting = connecting;
Log.d("BluetoothConnectionService", "isConnecting set to: " +
isConnecting);
}

private void showToast(String message) {


new Handler([Link]()).post(() -> {
[Link](this, message, Toast.LENGTH_SHORT).show();
});
}

private boolean checkBluetoothPermissions() {


if ([Link].SDK_INT >= Build.VERSION_CODES.S) {
return [Link](this,
[Link].BLUETOOTH_CONNECT)
== PackageManager.PERMISSION_GRANTED;
} else {
return [Link](this,
[Link])
== PackageManager.PERMISSION_GRANTED;
}
}

private void requestBluetoothPermissions() {


// Requesting permissions directly from the service is not
recommended
// Permission requests should generally be handled from an Activity
or Fragment
}

private void startReading() {


new Thread(() -> {
byte[] buffer = new byte[1024];
int bytes;
while (isConnected) {
try {
bytes = [Link]().read(buffer);
logRawBytes(buffer); // Log raw bytes for debugging
String data = new String(buffer, 0, bytes).trim();
if (![Link]()) {
Log.d(TAG, "Received data: " + data);
[Link](data);
[Link](data);

// Process weight data using robust parsing


Double parsedWeight = parseWeightResponse(data);
if (parsedWeight != null) {
float weight = [Link]();
String unit = [Link]();
boolean isStable =
[Link]();
new Handler([Link]()).post(() -> {
handleWeightUpdate(weight, unit, isStable);
sendWeightUpdate(weight, unit, isStable);
broadcastWeightData(data);
});
} else {
Log.w(TAG, "Failed to parse weight from data: " +
data);
}
}
} catch (IOException e) {
handleDisconnection();
Log.e(TAG, "Error reading data", e);
break;
}
}
cleanupConnection();
}).start();
}
private void logRawBytes(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
[Link]([Link]("%02X", b));
}
Log.d(TAG, "Raw bytes: " + [Link]());
Log.d(TAG, "As UTF-8 string: " + new String(bytes,
StandardCharsets.UTF_8));
Log.d(TAG, "As ISO-8859-1 string: " + new String(bytes,
StandardCharsets.ISO_8859_1));
}
private void updateScaleState(ScaleState newState) {
currentState = newState;
Log.i(TAG, "Scale state changed to: " + newState);
new Handler([Link]()).post(() -> {
if (viewModel != null) {
[Link](newState);
} else {
Log.e(TAG, "ViewModel is null in updateScaleState");
}
});
}

private void cleanupConnection() {


disconnect();
if (disconnectCallback != null) {
[Link]();
}
}

private void broadcastConnectionStatus(boolean isConnected) {


Intent intent = new Intent("BLUETOOTH_CONNECTION_STATUS");
[Link]("IS_CONNECTED", isConnected);
[Link](this).sendBroadcast(intent);
}
private void handleDisconnection() {
closeSocket();
isConnected = false;

onConnectionStateChanged([Link]);
showToast("Device disconnected.");
Log.e(TAG, "Disconnected from device");

// Reset state to IDLE


updateScaleState([Link]);

// Attempt to reconnect
new Thread(() -> {
int retryCount = 0;
while (!isConnected && retryCount < MAX_RETRY_COUNT) {
try {
[Link](RETRY_INTERVAL);
connectToDevice(lastConnectedDeviceAddress);
retryCount++;
} catch (InterruptedException e) {
Log.e(TAG, "Reconnection attempt interrupted", e);
}
}
if (!isConnected) {
Log.e(TAG, "Failed to reconnect after " + MAX_RETRY_COUNT + "
attempts");
}
}).start();
}

private void closeSocket() {


try {
if (socket != null) {
[Link]();
}
} catch (IOException e) {
Log.e("BluetoothConnectionService", "Error closing socket", e);
}
}

private void broadcastWeightData(String data) {


Intent intent = new Intent("WEIGHT_DATA");
[Link]("DATA", data);
[Link](this).sendBroadcast(intent);
}
private void handleWeightUpdate(float weight, String unit, boolean
isStable) {
if (viewModel != null) {
WeightData weightData = new WeightData(weight, unit, isStable);
[Link](weightData);

// Update state based on weight and stability


if (isStable && weight == 0 && currentState != [Link])
{
updateScaleState([Link]);
} else if (isStable && currentState != [Link]) {
updateScaleState([Link]);
}
} else {
Log.e("BluetoothConnectionService", "ViewModel is null in
handleWeightUpdate");
}
}

public void disconnect() {


if (socket != null) {
try {
[Link]();
Log.i("BluetoothConnectionService", "Socket closed
successfully.");
} catch (IOException e) {
Log.e("BluetoothConnectionService", "Error closing socket",
e);
} finally {
socket = null;
}
}
isConnected = false;

onConnectionStateChanged([Link]);
setConnecting(false);
}

@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
[Link]();
disconnect(); // Ensure the Bluetooth connection is closed
}

private void sendWeightUpdate(float weight, String unit, boolean


isStable) {
WeightData weightData = new WeightData(weight, unit, isStable);
Intent intent = new Intent("WEIGHT_UPDATE");
[Link]("WEIGHT_DATA", weightData); // WeightData is
Parcelable
[Link](this).sendBroadcast(intent);
}

private void onConnectionStateChanged([Link]


connectionState) {
Handler mainHandler = new Handler([Link]());
[Link](() -> {
if (viewModel != null) {
[Link](connectionState);
} else {
Log.e("BluetoothConnectionService", "ViewModel is null in
onConnectionStateChanged");
}
broadcastConnectionStatus(connectionState ==
[Link]);
});
}
private boolean sendCommand(String command) {
if (!isConnected || socket == null) {
Log.e(TAG, "Cannot send command, device not connected.");
return false;
}

Log.d(TAG, "Attempting to send command: " + [Link]());

CompletableFuture<Boolean> future = new CompletableFuture<>();

new Thread(() -> {


try {
byte[] commandBytes = [Link]();
[Link]().write(commandBytes);
[Link]().flush();
Log.i(TAG, "Command sent successfully: " + [Link]());
logRawBytes(commandBytes);

String response =
waitForResponse([Link](TARE_COMMAND) ? TARE_RESPONSE_TIMEOUT :
RESPONSE_TIMEOUT);
Log.d(TAG, "Response received: " + (response != null ?
response : "null"));
boolean success = (response != null) &&
handleResponse(response, command);
Log.i(TAG, "Command execution result: " + (success ?
"Success" : "Failure"));

if ([Link](TARE_COMMAND) && success) {


updateScaleState([Link]);
handleTareStatus(true);
} else if ([Link](CLEAR_TARE_COMMAND) && success) {
updateScaleState(ScaleState.TARE_CLEARED);
handleTareStatus(false);
}

[Link](success);
} catch (IOException e) {
Log.e(TAG, "Failed to send command: " + [Link](), e);
[Link](false);
}
}).start();

try {
boolean result = [Link](COMMAND_TIMEOUT,
[Link]);
Log.d(TAG, "Command execution completed with result: " + result);
return result;
} catch (InterruptedException | ExecutionException | TimeoutException
e) {
Log.e(TAG, "Command execution failed or timed out: " +
[Link](), e);
return false;
}
}
private String waitForResponse(long timeout) {
try {
String response = [Link](timeout,
[Link]);
Log.d(TAG, "Received response: " + (response != null ? response :
"null") + " after waiting for " + timeout + "ms");
return response;
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for response", e);
return null;
}
}

private boolean handleResponse(String response, String command) {


Log.d(TAG, "Handling response: " + response + " for command: " +
command);
boolean success = false;

if ([Link](TARE_COMMAND)) {
success = isTareSuccessResponse(response);
Log.i(TAG, "Tare command response: " + response + ", Success: " +
success);
if (success) {
updateScaleState([Link]);
handleTareStatus(true);
} else {
// Handle potential protocol-specific indicators of tare
failure
if ([Link]("ε") || [Link]("error")) {
success = false; // Explicitly mark failure if certain
characters are present
Log.w(TAG, "Detected potential tare failure indication in
response.");
} else {
// Check for weight near zero as a possible indication of
tare success
Double parsedWeight = parseWeightResponse(response);
if (parsedWeight != null && [Link](parsedWeight) <
0.01) {
success = true;
Log.i(TAG, "Tare successful based on weight near
zero");
updateScaleState([Link]);
handleTareStatus(true);
}
}
}
} else if ([Link](CLEAR_TARE_COMMAND)) {
success = [Link]("CT S");
Log.i(TAG, "Clear tare command response: " + (success ? "Success"
: "Failure"));
if (success) {
updateScaleState(ScaleState.TARE_CLEARED);
handleTareStatus(false);
}
}
return success;
}

private boolean isTareSuccessResponse(String response) {


return [Link]().anyMatch(response::contains) ||
([Link](".*[Tt]are.*[Ss]uccess.*")) ||
([Link](".*[Tt]are.*[Ss]et.*"));
}
public boolean isTareActive() {
// Implement logic to check if tare is active based on tareActive
variable
return tareActive;
}

private static final String TAG = "BluetoothConnectionService";

public void handleTareStatus(boolean isTareActive) {


tareActive = isTareActive;
Log.i(TAG, "Tare status updated: " + (isTareActive ? "Active" :
"Inactive"));
new Handler([Link]()).post(() -> {
if (viewModel != null) {
[Link](isTareActive);
} else {
Log.e(TAG, "ViewModel is null in handleTareStatus");
}
broadcastTareStatus(isTareActive);
});
}

private void broadcastTareStatus(boolean isTareActive) {


Intent intent = new Intent("TARE_STATUS_UPDATE");
[Link]("IS_TARE_ACTIVE", isTareActive);
[Link](this).sendBroadcast(intent);
}
enum ScaleState {
IDLE, TARED, TARE_CLEARED, WEIGHING
}

private Double parseWeightResponse(String response) {


Pattern pattern = [Link]("(-?\\d+(?:\\.\\d+)?)");
Matcher matcher = [Link](response);
if ([Link]()) {
try {
return [Link]([Link](1));
} catch (NumberFormatException e) {
Log.e(TAG, "Failed to parse weight", e);
}
}
return null;
}

You might also like