Skip to content

Instantly share code, notes, and snippets.

@gwaldvogel
Created July 1, 2022 10:03
Show Gist options
  • Save gwaldvogel/ddfffcc264f42900ab347b3e9f536416 to your computer and use it in GitHub Desktop.
Save gwaldvogel/ddfffcc264f42900ab347b3e9f536416 to your computer and use it in GitHub Desktop.
This gist shows an example implementation of how to access the testo 300 Bluetooth API.
package de.testo.demo.t300interface.ble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
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.Context;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public abstract class BluetoothScanHelper {
private static final String TAG = "BluetoothScanHelper";
protected BluetoothAdapter m_bluetoothAdapter = null;
protected BluetoothLeScanner m_bluetoothScanner = null;
protected String m_nameFilter = null;
private boolean scanRunning = false;
private Handler handler = new Handler();
ScanCallback m_scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.d(TAG, "onScanResult: " + callbackType + " result: " + result.toString());
if(callbackType != ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
handleScanResult(result);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.d(TAG, "onBatchScanResults: " + results.size());
for (ScanResult result : results) {
handleScanResult(result);
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "Scan failed:" + errorCode);
}
};
public BluetoothScanHelper(Context context) {
this("testo 300", context);
}
public BluetoothScanHelper(String nameFilter, Context context) {
Log.d(TAG, "Initializing BluetoothScanHelper");
m_nameFilter = nameFilter;
BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if(manager != null) {
m_bluetoothAdapter = manager.getAdapter();
m_bluetoothScanner = m_bluetoothAdapter.getBluetoothLeScanner();
}
}
public void startScan() {
if(m_bluetoothScanner != null) {
this.scanRunning = true;
Log.d(TAG, "Starting Bluetooth scan");
ArrayList<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(TestoGattDefinitions.UUID_MEASDATA_SERVICE)).build());
filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(TestoGattDefinitions.UUID_CONTROL_SERVICE)).build());
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setReportDelay(0)
.build();
m_bluetoothScanner.startScan(filters, settings, m_scanCallback);
handler.postDelayed(new Runnable() {
@Override
public void run() {
stopScan();
}
}, 10000);
}
}
public void stopScan() {
if(scanRunning) {
Log.d(TAG, "Stopping Bluetooth scan");
m_bluetoothScanner.stopScan(m_scanCallback);
scanRunning = false;
}
}
protected void handleScanResult(ScanResult scanResult) {
Log.d(TAG, "handleScanResult: " + scanResult.toString());
if(scanResult.isConnectable()) {
Log.d(TAG, "handleScanResult: isConnectable() true");
BluetoothDevice device = scanResult.getDevice();
Log.d(TAG, "+--------------------------------------------------");
Log.d(TAG, "| Device name: " + device.getName());
Log.d(TAG, "| Device address: " + device.getAddress());
Log.d(TAG, "| Device type: " + device.getType());
Log.d(TAG, "| Bond state: " + device.getBondState());
Log.d(TAG, "+--------------------------------------------------");
if(device.getName().startsWith(m_nameFilter)) {
Log.d(TAG, "Found device with name: " + device.getName());
bleDeviceFound(device);
}
}
}
abstract public void bleDeviceFound(BluetoothDevice device);
}
package de.testo.demo.t300interface.ble;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import de.testo.demo.t300interface.json.JsonProcessor;
import de.testo.demo.t300interface.device.TestoDevice;
public class TestoBluetoothDevice extends TestoDevice {
private static final String TAG = TestoBluetoothDevice.class.getSimpleName();
private BluetoothGatt bluetoothGatt = null;
private BluetoothGattService measdataService = null;
private List<UUID> jsonDataCharacteristics = new ArrayList<>(
Arrays.asList(
TestoGattDefinitions.UUID_JSON_MEASDATA01_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA02_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA03_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA04_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA05_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA06_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA07_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA08_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA09_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA10_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA11_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA12_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA13_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA14_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA15_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA16_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA17_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA18_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA19_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA20_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA21_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA22_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA23_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA24_CHARACTERISTIC,
TestoGattDefinitions.UUID_JSON_MEASDATA25_CHARACTERISTIC
)
);
private SparseArray<String> jsonResponseArray = new SparseArray<>();
private int activeJsonCharacteristics = 0;
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if(newState == BluetoothGatt.STATE_CONNECTED) {
gatt.requestMtu(512);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
measdataService = gatt.getService(TestoGattDefinitions.UUID_MEASDATA_SERVICE);
gatt.readCharacteristic(measdataService.getCharacteristic(TestoGattDefinitions.UUID_JSON_MEASDATA_META_CHARACTERISTIC));
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(characteristic.getUuid().equals(TestoGattDefinitions.UUID_JSON_MEASDATA_META_CHARACTERISTIC)) {
activeJsonCharacteristics = Integer.parseInt(characteristic.getStringValue(0));
Log.d(TAG, "activeJsonCharacteristics: " + activeJsonCharacteristics);
Log.d(TAG, "Reading characteristic: " + jsonDataCharacteristics.get(0).toString());
gatt.readCharacteristic(measdataService.getCharacteristic(jsonDataCharacteristics.get(0)));
}
else if(jsonDataCharacteristics.contains(characteristic.getUuid())) {
jsonResponseArray.put(jsonDataCharacteristics.indexOf(characteristic.getUuid()), new String(characteristic.getValue(), StandardCharsets.UTF_8));
Log.d(TAG, "Successful Read for characteristic " + characteristic.getUuid().toString());
int nextIndex = jsonDataCharacteristics.indexOf(characteristic.getUuid()) + 1;
if(nextIndex < activeJsonCharacteristics) {
gatt.readCharacteristic(measdataService.getCharacteristic(jsonDataCharacteristics.get(nextIndex)));
Log.d(TAG, "Reading characteristic: " + jsonDataCharacteristics.get(nextIndex).toString());
}
}
if(jsonResponseArray.size() != 0
&& activeJsonCharacteristics != 0
&& jsonResponseArray.size() == activeJsonCharacteristics
&& deviceCallback != null) {
String jsonInput;
StringBuilder jsonInputBuilder = new StringBuilder();
for(int i = 0; i < jsonResponseArray.size(); i++) {
jsonInputBuilder.append(jsonResponseArray.get(i));
}
jsonInput = jsonInputBuilder.toString();
JsonProcessor jsonProcessor = new JsonProcessor();
jsonProcessor.setJsonInput(jsonInput.toString());
jsonProcessor.parseJson();
measurementList = jsonProcessor.getMeasurementList();
title = jsonProcessor.getTitle();
if(deviceCallback != null) {
deviceCallback.onDataFetchedFromDevice(TestoBluetoothDevice.this);
}
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
Log.d(TAG, "MTU changed: " + mtu + " (Status: " + status + ")");
gatt.discoverServices();
}
};
/**
* Creates a new instance from a given BluetoothDevice
*
* @param bluetoothDevice
* @return
*/
public static TestoDevice fromBluetoothDevice(BluetoothDevice bluetoothDevice) {
String serial = bluetoothDevice.getName().replace("testo 300 ", "");
return new TestoBluetoothDevice("testo 300", serial, bluetoothDevice);
}
private TestoBluetoothDevice(String deviceName, String serialNumber, BluetoothDevice device) {
this.deviceName = deviceName;
this.serialNumber = serialNumber;
this.bleDevice = device;
this.type = TYPE_BLE;
}
@Override
public void connect(Context context) {
this.bluetoothGatt = this.bleDevice.connectGatt(context, false, bluetoothGattCallback);
}
@Override
public void fetchData() {
if(measdataService != null) {
jsonResponseArray.clear();
activeJsonCharacteristics = 0;
bluetoothGatt.readCharacteristic(measdataService.getCharacteristic(TestoGattDefinitions.UUID_JSON_MEASDATA_META_CHARACTERISTIC));
}
}
@Override
public void toggleMeasurement() {
if(bluetoothGatt != null) {
BluetoothGattCharacteristic characteristic = bluetoothGatt.getService(TestoGattDefinitions.UUID_CONTROL_SERVICE)
.getCharacteristic(TestoGattDefinitions.UUID_CONTROL_COMMAND_CHARACTERISTIC);
characteristic.setValue("TOGGLE_MEASUREMENT");
bluetoothGatt.writeCharacteristic(characteristic);
Log.d(TAG, "Write value \"TOGGLE_MEASUREMENT\" to " + TestoGattDefinitions.UUID_CONTROL_STATUS_CHARACTERISTIC.toString());
}
}
}
package de.testo.demo.t300interface.ble;
import java.util.UUID;
public abstract class TestoGattDefinitions {
public static final UUID UUID_MEASDATA_SERVICE = UUID.fromString("00002000-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA_META_CHARACTERISTIC = UUID.fromString("00003000-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA01_CHARACTERISTIC = UUID.fromString("00003001-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA02_CHARACTERISTIC = UUID.fromString("00003002-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA03_CHARACTERISTIC = UUID.fromString("00003003-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA04_CHARACTERISTIC = UUID.fromString("00003004-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA05_CHARACTERISTIC = UUID.fromString("00003005-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA06_CHARACTERISTIC = UUID.fromString("00003006-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA07_CHARACTERISTIC = UUID.fromString("00003007-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA08_CHARACTERISTIC = UUID.fromString("00003008-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA09_CHARACTERISTIC = UUID.fromString("00003009-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA10_CHARACTERISTIC = UUID.fromString("0000300A-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA11_CHARACTERISTIC = UUID.fromString("0000300B-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA12_CHARACTERISTIC = UUID.fromString("0000300C-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA13_CHARACTERISTIC = UUID.fromString("0000300D-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA14_CHARACTERISTIC = UUID.fromString("0000300E-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA15_CHARACTERISTIC = UUID.fromString("0000300F-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA16_CHARACTERISTIC = UUID.fromString("00003010-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA17_CHARACTERISTIC = UUID.fromString("00003011-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA18_CHARACTERISTIC = UUID.fromString("00003012-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA19_CHARACTERISTIC = UUID.fromString("00003013-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA20_CHARACTERISTIC = UUID.fromString("00003014-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA21_CHARACTERISTIC = UUID.fromString("00003015-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA22_CHARACTERISTIC = UUID.fromString("00003016-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA23_CHARACTERISTIC = UUID.fromString("00003017-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA24_CHARACTERISTIC = UUID.fromString("00003018-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_JSON_MEASDATA25_CHARACTERISTIC = UUID.fromString("00003019-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CONTROL_SERVICE = UUID.fromString("00002001-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CONTROL_COMMAND_CHARACTERISTIC = UUID.fromString("00003100-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CONTROL_STATUS_CHARACTERISTIC = UUID.fromString("00003101-0000-1000-8000-00805f9b34fb");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment