Skip to content

Commit

Permalink
Flask UART/Serial Port Emulation over BLE
Browse files Browse the repository at this point in the history
  • Loading branch information
AbandonedCart committed May 15, 2022
1 parent 655478d commit 2e5242a
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 142 deletions.
4 changes: 0 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.bin" />
</intent-filter>
<intent-filter>
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
<action android:name="android.bluetooth.device.action.PAIRING_CANCEL" />
</intent-filter>
</activity>
<activity-alias
android:name=".NFCIntentFilter"
Expand Down
86 changes: 40 additions & 46 deletions app/src/main/java/com/hiddenramblings/tagmo/BluetoothLeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,9 @@ public class BluetoothLeService extends Service {
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";

private final UUID FlaskRX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
private UUID serviceRead = null;
private final UUID FlaskTX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
private UUID serviceWrite = null;
public final static UUID FlaskNUS = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
private final static UUID FlaskRX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
private final static UUID FlaskTX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");

public void setListener(BluetoothGattListener listener) {
this.listener = listener;
Expand Down Expand Up @@ -284,43 +283,39 @@ public void readCustomCharacteristic() throws TagLostException {
throw new TagLostException();
}

if (null != serviceRead) {
BluetoothGattService mCustomService = mBluetoothGatt.getService(serviceRead);
/*check if the service is available on the device*/
if (mCustomService == null) {
serviceRead = null;
Log.w(TAG, "BLE Service not found for read");
throw new TagLostException();
}
BluetoothGattCharacteristic mReadCharacteristic =
mCustomService.getCharacteristic(FlaskRX);
if (!mBluetoothGatt.readCharacteristic(mReadCharacteristic)) {
Log.w(TAG, "Failed to read characteristic");
throw new TagLostException();
}
} else {
BluetoothGattService mCustomService = mBluetoothGatt.getService(FlaskNUS);
/*check if the service is available on the device*/
if (null == mCustomService) {
List<BluetoothGattService> services = getSupportedGattServices();
if (null == services || services.isEmpty()) {
Log.w(TAG, "No BLE Services found for read");
throw new TagLostException();
}

for (BluetoothGattService mCustomService : services) {
Log.d("GattReadService", mCustomService.getUuid().toString());
for (BluetoothGattService customService : services) {
Log.d("GattReadService", customService.getUuid().toString());
/*get the read characteristic from the service*/
BluetoothGattCharacteristic mReadCharacteristic =
mCustomService.getCharacteristic(FlaskRX);
customService.getCharacteristic(FlaskRX);
try {
if (mBluetoothGatt.readCharacteristic(mReadCharacteristic)) {
serviceRead = mCustomService.getUuid();
mCustomService = mBluetoothGatt.getService(customService.getUuid());
break;
}
} catch (NullPointerException ignored) { }
}
if (null == serviceRead) {
}

if (null != mCustomService) {
BluetoothGattCharacteristic mReadCharacteristic =
mCustomService.getCharacteristic(FlaskRX);
if (!mBluetoothGatt.readCharacteristic(mReadCharacteristic)) {
Log.w(TAG, "Failed to read characteristic");
throw new TagLostException();
}
} else {
Log.w(TAG, "Failed to read characteristic");
throw new TagLostException();
}
}

Expand All @@ -330,46 +325,45 @@ public void writeCustomCharacteristic(int value) throws TagLostException {
throw new TagLostException();
}

if (null != serviceWrite) {
BluetoothGattService mCustomService = mBluetoothGatt.getService(serviceWrite);
/*check if the service is available on the device*/
if (mCustomService == null) {
serviceWrite = null;
Log.w(TAG, "BLE Service not found for write");
return;
}
BluetoothGattCharacteristic mWriteCharacteristic = mCustomService.getCharacteristic(FlaskTX);

mWriteCharacteristic.setValue(value, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
if (mBluetoothGatt.writeCharacteristic(mWriteCharacteristic)) {
Log.w(TAG, "Failed to write characteristic");
throw new TagLostException();
}
} else {
BluetoothGattService mCustomService = mBluetoothGatt.getService(FlaskNUS);
/*check if the service is available on the device*/
if (null == mCustomService) {
List<BluetoothGattService> services = getSupportedGattServices();
if (null == services || services.isEmpty()) {
Log.w(TAG, "No BLE Services found for write");
throw new TagLostException();
}

for (BluetoothGattService mCustomService : services) {
Log.d("GattWriteService", mCustomService.getUuid().toString());
for (BluetoothGattService customService : services) {
Log.d("GattWriteService", customService.getUuid().toString());
/*get the read characteristic from the service*/
BluetoothGattCharacteristic mWriteCharacteristic = mCustomService.getCharacteristic(FlaskTX);
BluetoothGattCharacteristic mWriteCharacteristic =
customService.getCharacteristic(FlaskTX);

mWriteCharacteristic.setValue(value, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
mWriteCharacteristic.setValue(value,
BluetoothGattCharacteristic.FORMAT_UINT8, 0);
try {
if (mBluetoothGatt.writeCharacteristic(mWriteCharacteristic)) {
serviceWrite = mCustomService.getUuid();
mCustomService = mBluetoothGatt.getService(customService.getUuid());
break;
}
} catch (NullPointerException ignored) { }
}
}

if (null != mCustomService) {
BluetoothGattCharacteristic mWriteCharacteristic =
mCustomService.getCharacteristic(FlaskTX);

if (null == serviceWrite) {
mWriteCharacteristic.setValue(value,
BluetoothGattCharacteristic.FORMAT_UINT8, 0);
if (mBluetoothGatt.writeCharacteristic(mWriteCharacteristic)) {
Log.w(TAG, "Failed to write characteristic");
throw new TagLostException();
}
} else {
Log.w(TAG, "Failed to write characteristic");
throw new TagLostException();
}
}
}
125 changes: 42 additions & 83 deletions app/src/main/java/com/hiddenramblings/tagmo/FlaskFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.nfc.TagLostException;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.os.ParcelUuid;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
Expand All @@ -36,14 +38,12 @@
import androidx.fragment.app.Fragment;

import com.google.android.material.snackbar.Snackbar;
import com.hiddenramblings.tagmo.eightbit.charset.CharsetCompat;
import com.hiddenramblings.tagmo.eightbit.io.Debug;
import com.hiddenramblings.tagmo.eightbit.material.IconifiedSnackbar;
import com.hiddenramblings.tagmo.widget.Toasty;

import java.util.Locale;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
@SuppressLint("MissingPermission")
Expand All @@ -55,6 +55,7 @@ public class FlaskFragment extends Fragment {
private ProgressBar progressBar;
private Snackbar statusBar;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothAdapter.LeScanCallback scanCallback;
private BluetoothLeService flaskService;
private String flaskAddress;

Expand Down Expand Up @@ -236,61 +237,39 @@ private BluetoothAdapter getBluetoothAdapter() {
return null;
}

ActivityResultLauncher<Intent> onRequestPairing = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> selectBluetoothDevice());

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private final BroadcastReceiver pairingRequest = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.bluetooth.device.action.PAIRING_REQUEST")) {
try {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getName().toLowerCase(Locale.ROOT).startsWith("flask")) {
device.setPin((String.valueOf(intent.getIntExtra(
"android.bluetooth.device.extra.PAIRING_KEY", 0
))).getBytes(CharsetCompat.UTF_8));
device.setPairingConfirmation(true);
flaskAddress = device.getAddress();
dismissFlaskDiscovery();
showConnectionNotice(true);
startFlaskService();
} else {
View bonded = getLayoutInflater().inflate(R.layout.bluetooth_device,
fragmentView, false);
bonded.setOnClickListener(view1 -> {
device.setPin((String.valueOf(intent.getIntExtra(
"android.bluetooth.device.extra.PAIRING_KEY", 0
))).getBytes(CharsetCompat.UTF_8));
device.setPairingConfirmation(true);
flaskAddress = device.getAddress();
dismissFlaskDiscovery();
showConnectionNotice(false);
startFlaskService();
});
setButtonText(bonded, device);
}
} catch (Exception ex) {
Debug.Log(ex);
new Toasty(requireActivity()).Short(R.string.flask_failed);
private void scanBluetoothServices() {
progressBar.setVisibility(View.VISIBLE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
ParcelUuid FlaskUUID = new ParcelUuid(BluetoothLeService.FlaskNUS);
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(FlaskUUID).build();
ScanSettings settings = new ScanSettings.Builder().build();
ScanCallback callback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
flaskAddress = result.getDevice().getAddress();
dismissFlaskDiscovery();
showConnectionNotice(true);
startFlaskService();
}
}
};
scanner.startScan(Collections.singletonList(filter), settings, callback);
} else {
scanCallback = (bluetoothDevice, i, bytes) -> {
flaskAddress = bluetoothDevice.getAddress();
dismissFlaskDiscovery();
showConnectionNotice(true);
startFlaskService();
};
mBluetoothAdapter.startLeScan(new UUID[]{ BluetoothLeService.FlaskNUS }, scanCallback);
}
};
}

private void selectBluetoothDevice() {
deviceList.removeAllViews();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

for (BluetoothDevice device : pairedDevices) {
// if (device.getName().toLowerCase(Locale.ROOT).startsWith("flask")) {
// flaskAddress = device.getAddress();
// dismissFlaskDiscovery();
// showConnectionNotice(true);
// startFlaskService();
// break;
// }
for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
View bonded = getLayoutInflater().inflate(R.layout.bluetooth_device,
fragmentView, false);
bonded.setOnClickListener(view1 -> {
Expand All @@ -301,26 +280,11 @@ private void selectBluetoothDevice() {
});
setButtonText(bonded, device);
}
View paired = getLayoutInflater().inflate(R.layout.bluetooth_device,
View scan = getLayoutInflater().inflate(R.layout.bluetooth_device,
fragmentView, false);
paired.setOnClickListener(view1 -> {
try {
onRequestPairing.launch(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
} catch (ActivityNotFoundException anf) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
IntentFilter filter = new IntentFilter(
"android.bluetooth.device.action.PAIRING_REQUEST"
);
requireActivity().registerReceiver(pairingRequest, filter);
if (mBluetoothAdapter.isDiscovering())
mBluetoothAdapter.cancelDiscovery();
mBluetoothAdapter.startDiscovery();
progressBar.setVisibility(View.VISIBLE);
}
}
});
((TextView) paired.findViewById(R.id.bluetooth_text)).setText(R.string.bluetooth_pair);
deviceList.addView(paired, 0);
scan.setOnClickListener(view1 -> scanBluetoothServices());
((TextView) scan.findViewById(R.id.bluetooth_text)).setText(R.string.bluetooth_scan);
deviceList.addView(scan, 0);
}

private void setButtonText(View button, BluetoothDevice device) {
Expand Down Expand Up @@ -354,15 +318,10 @@ public void stopFlaskService() {
}

private void dismissFlaskDiscovery() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
requireActivity().unregisterReceiver(pairingRequest);
} catch (Exception ignored) { }
}
if (null != mBluetoothAdapter) {
if (mBluetoothAdapter.isDiscovering())
mBluetoothAdapter.cancelDiscovery();
progressBar.setVisibility(View.INVISIBLE);
if (null != scanCallback)
mBluetoothAdapter.stopLeScan(scanCallback);
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="flask_permissions">Required permission denied!</string>
<string name="custom_tab_back">Back to TagMo</string>
<string name="flask_failed">Flask pairing failed!</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="flask_permissions">Required permission denied!</string>
<string name="flask_bluetooth">Bluetooth adapter unavailable!</string>
<string name="flask_connected">Bluup Flask connected!</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="flask_permissions">Required permission denied!</string>
<string name="flask_bluetooth">Bluetooth adapter unavailable!</string>
<string name="flask_connected">Bluup Flask connected!</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="flask_permissions">Required permission denied!</string>
<string name="custom_tab_back">Back to TagMo</string>
<string name="flask_failed">Flask pairing failed!</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="custom_tab_back">Back to TagMo</string>
<string name="flask_failed">Flask pairing failed!</string>
<string name="flask_invalid">Not a valid Flask device!</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<string name="storage_unavailable">Device storage inaccessible!</string>
<string name="view_device_options">Switch to N2 Elite Options</string>
<string name="view_amiibo_details">Switch to amiibo&#8482; Details</string>
<string name="bluetooth_pair">Pair new Bluetooth device</string>
<string name="bluetooth_scan">Scan BLE for Bluup Flask</string>
<string name="custom_tab_back">Back to TagMo</string>
<string name="flask_failed">Flask pairing failed!</string>
<string name="flask_invalid">Not a valid Flask device!</string>
Expand Down
Loading

0 comments on commit 2e5242a

Please sign in to comment.