Java tutorial
package nl.dobots.bluenet.ble.extended; import android.app.Activity; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import nl.dobots.bluenet.ble.base.BleBase; import nl.dobots.bluenet.ble.base.callbacks.IAlertCallback; import nl.dobots.bluenet.ble.base.callbacks.IBooleanCallback; import nl.dobots.bluenet.ble.base.callbacks.IMeshDataCallback; import nl.dobots.bluenet.ble.base.callbacks.IPowerSamplesCallback; import nl.dobots.bluenet.ble.base.callbacks.IStateCallback; import nl.dobots.bluenet.ble.base.structs.AlertState; import nl.dobots.bluenet.ble.base.structs.CommandMsg; import nl.dobots.bluenet.ble.base.structs.PowerSamples; import nl.dobots.bluenet.ble.base.structs.StateMsg; import nl.dobots.bluenet.ble.cfg.BleErrors; import nl.dobots.bluenet.ble.cfg.BluenetConfig; import nl.dobots.bluenet.ble.core.BleCore; import nl.dobots.bluenet.ble.base.callbacks.IBaseCallback; import nl.dobots.bluenet.ble.core.BleCoreTypes; import nl.dobots.bluenet.ble.extended.callbacks.IBleDeviceCallback; import nl.dobots.bluenet.ble.base.callbacks.IByteArrayCallback; import nl.dobots.bluenet.ble.base.callbacks.IDataCallback; import nl.dobots.bluenet.ble.base.callbacks.IDiscoveryCallback; import nl.dobots.bluenet.ble.extended.callbacks.IExecuteCallback; import nl.dobots.bluenet.ble.base.callbacks.IIntegerCallback; import nl.dobots.bluenet.ble.base.callbacks.IStatusCallback; import nl.dobots.bluenet.ble.extended.structs.BleDevice; import nl.dobots.bluenet.ble.extended.structs.BleDeviceMap; import nl.dobots.bluenet.ble.base.structs.MeshMsg; import nl.dobots.bluenet.ble.base.structs.TrackedDeviceMsg; import nl.dobots.bluenet.utils.BleLog; import nl.dobots.bluenet.utils.BleUtils; /** * Copyright (c) 2015 Dominik Egger <dominik@dobots.nl>. All rights reserved. * <p/> * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as * published by the Free Software Foundation. * <p/> * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 3 for more details (a copy is included in the LICENSE file that * accompanied this code). * <p/> * Created on 15-7-15 * * @author Dominik Egger */ public class BleExt { private static final String TAG = BleExt.class.getCanonicalName(); // default timeout for connection attempt private static final int CONNECT_TIMEOUT = 10; // 10 seconds // default time used for delayed disconnects public static final int DELAYED_DISCONNECT_TIME = 5000; // 5 seconds private static final int MAX_RETRIES = 3; private BleBase _bleBase; // list of devices. scanned devices that pass through the filter will be stored in the list private BleDeviceMap _devices = new BleDeviceMap(); // address of the device we are connecting / talking to private String _targetAddress; // filter, used to filter devices based on "type", eg. only report crownstone devices, or // only report guidestone devices private BleDeviceFilter _scanFilter = BleDeviceFilter.all; // current connection state private BleDeviceConnectionState _connectionState = BleDeviceConnectionState.uninitialized; // keep a list of discovered services and characteristics private ArrayList<String> _detectedCharacteristics = new ArrayList<>(); // handler used for delayed execution and timeouts private Handler _handler; private ArrayList<String> _blackList; private ArrayList<String> _whiteList; private IBleDeviceCallback _cloudScanCB; private BleExtState _bleExtState; private HashMap<String, Integer> _subscriberIds = new HashMap<>(); public BleExt() { _bleBase = new BleBase(); _bleExtState = new BleExtState(this); // create handler with its own thread HandlerThread handlerThread = new HandlerThread("BleExtHandler"); handlerThread.start(); _handler = new Handler(handlerThread.getLooper()); } /** * Get access to the base bluenet object. Only use it if you need to change some low level * settings. Usually this is Not necessary. * @return BleBase object used by this exented object to interact with the Android Bluetooth * functions */ public BleBase getBleBase() { return _bleBase; } public BleExtState getBleExtState() { return _bleExtState; } /** * Set the scan device filter. by setting a filter, only the devices specified will * pass through the filter and be reported to the application, any other detected devices * will be ignored. * @param filter the filter to be used */ public void setScanFilter(BleDeviceFilter filter) { _scanFilter = filter; } /** * Get the currently set filter * @return the device filter */ public BleDeviceFilter getScanFilter() { return _scanFilter; } /** * Get the current target address, i.e. the address of the device we are connected to * @return the MAC address of the device */ public String getTargetAddress() { return _targetAddress; } /** * Get the list of scanned devices. The list is updated every time a device is detected, the * rssi is updated, the average rssi is computed and if the device is an iBeacon, the distance * is estimated * @return the list of scanned devices */ public synchronized BleDeviceMap getDeviceMap() { // make sure it is refreshed _devices.refresh(); return _devices; } /** * Clear the list of scanned devices. */ public synchronized void clearDeviceMap() { _devices.clear(); } /** * Get the current connection state * @return connection state */ public BleDeviceConnectionState getConnectionState() { return _connectionState; } /** * Initializes the BLE Modules and tries to enable the Bluetooth adapter. Note, the callback * provided as parameter will persist. The callback will be triggered whenever the state of * the bluetooth adapter changes. That means if the user turns off bluetooth, then the onError * of the callback will be triggered. And again if the user turns bluetooth on, the onSuccess * will be triggered. If the user denies enabling bluetooth, then onError will be called after * a timeout expires * @param context the context used to enable bluetooth, this can be a service or an activity * @param callback callback, used to report back if bluetooth is enabled / disabled */ public void init(Context context, final IStatusCallback callback) { // wrap the callback to update the connection state _bleBase.init(context, new IStatusCallback() { @Override public void onSuccess() { _connectionState = BleDeviceConnectionState.initialized; callback.onSuccess(); } @Override public void onError(int error) { _connectionState = BleDeviceConnectionState.uninitialized; callback.onError(error); } }); } /** * Close the library and release all callbacks */ public void destroy() { _handler.removeCallbacksAndMessages(null); _bleBase.destroy(); } /** * Set the given addresses as black list. any address on the black list will be ignored * during a scan and not returned as a scan result * @param addresses the MAC addresses of the devices which should be ignored during a scan */ public void setBlackList(String[] addresses) { _blackList = new ArrayList<>(Arrays.asList(addresses.clone())); } /** * Clear the black list again in order to get all devices during a scan */ public void clearBlackList() { _blackList = null; } /** * Set the given addresses as white list. only devices on the white list will be returned * during a scan. any other device will be ignored. * @param addresses the MAC addresses of the devices which should be returned during a scan */ public void setWhiteList(String[] addresses) { _whiteList = new ArrayList<>(Arrays.asList(addresses.clone())); } /** * Clear the white list again in order to get all devices during a scan */ public void clearWhiteList() { _whiteList = null; } /** * Start scanning for devices. devices will be provided through the callback. see * startEndlessScan for details * * Note: clears the device list on start * * @param callback callback used to report back scanned devices * @return true if the scan was started, false if an error occurred */ public boolean startScan(final IBleDeviceCallback callback) { return startScan(true, callback); } /** * Starts a scan used for interval scanning, devices will be provided through the callback. see * startEndlessScan for details * * @param clearList if true, clears the list before starting the scan * @param callback callback used to report back scanned devices * @return true if the scan was started, false if an error occurred */ public boolean startScan(boolean clearList, final IBleDeviceCallback callback) { if (clearList) { clearDeviceMap(); } return startEndlessScan(callback); } /** * Helper function to start an endless scan. endless meaning, it will continue scanning * until stopScan is called. If a device is detected, the data is parsed into a BleDevice * object. Then the filter is checked to see if the device passes through the filter. If * it passes, the device list is updated, which triggers a recalculation of the devices * average RSSI (and the distance if it is an iBeacon). After the update, it is * reported back through the callbacks onDeviceScanned function. * * @param callback callback used to report back scanned devices * @return true if the scan was started, false if an error occurred */ private boolean startEndlessScan(final IBleDeviceCallback callback) { // checkConnectionState(BleDeviceConnectionState.initialized, null); if (_connectionState != BleDeviceConnectionState.initialized) { BleLog.LOGe(TAG, "State is not initialized: %s", _connectionState.toString()); callback.onError(BleErrors.ERROR_WRONG_STATE); return false; } _connectionState = BleDeviceConnectionState.scanning; return _bleBase.startEndlessScan(new IBleDeviceCallback() { @Override public void onDeviceScanned(BleDevice device) { if (_blackList != null && _blackList.contains(device.getAddress())) { return; } if (_whiteList != null && !_whiteList.contains(device.getAddress())) { return; } switch (_scanFilter) { case crownstone: // if filter set to crownstone, but device is not a crownstone, abort if (!device.isCrownstone()) return; break; case guidestone: if (!device.isGuidestone()) return; break; case iBeacon: // if filter set to beacon, but device is not a beacon, abort if (!device.isIBeacon()) return; break; case fridge: if (!device.isFridge()) return; break; case all: // return any device that was detected break; } // update the device list, this triggers recalculation of the average RSSI (and // distance estimation if it is a beacon) device = updateDevice(device); // report the updated device callback.onDeviceScanned(device); if (_cloudScanCB != null) { _cloudScanCB.onDeviceScanned(device); } } @Override public void onError(int error) { if (error == BleErrors.ERROR_ALREADY_SCANNING) { _bleBase.stopEndlessScan(null); } _connectionState = BleDeviceConnectionState.initialized; callback.onError(error); } }); } private synchronized BleDevice updateDevice(BleDevice device) { return _devices.updateDevice(device); } /** * Stop scanning for devices * @param callback the callback used to report success or failure of the stop scan * @return true if the scan was stopped, false otherwise */ public boolean stopScan(final IStatusCallback callback) { _connectionState = BleDeviceConnectionState.initialized; return _bleBase.stopEndlessScan(callback); } /** * Check if currently scanning for devices * @return true if scanning, false otherwise */ public boolean isScanning() { return _bleBase.isScanning(); } /** * Every time a device is scanned, the onDeviceScanned function of the * callback provided as scanCB parameter will trigger. Use this to enable * cloud upload, i.e. forward the scan to the crownstone-loopack-sdk * @param scanCB */ public void enableCloudUpload(IBleDeviceCallback scanCB) { _cloudScanCB = scanCB; } /** * Disable cloud upload again */ public void disableCloudUpload() { _cloudScanCB = null; } /** * Connect to the device with the given MAC address. Scan first for devices to find possible * devices or make sure that the device you want to connect to is there. * @param address the MAC address of the device, in the form of "12:34:56:AB:CD:EF" * @param callback the callback used to report success or failure. onSuccess will be called * if the device was successfully connected. onError will be called with an * ERROR to report failure. */ private void connect(final String address, final IStatusCallback callback) { if (checkConnectionState(BleDeviceConnectionState.initialized, null)) { if (address != null) { _targetAddress = address; } if (_targetAddress == null) { callback.onError(BleErrors.ERROR_NO_ADDRESS_PROVIDED); return; } _connectionState = BleDeviceConnectionState.connecting; IDataCallback dataCallback = new IDataCallback() { @Override public void onData(JSONObject json) { String status = BleCore.getStatus(json); if (status == "connected") { onConnect(); callback.onSuccess(); } else { BleLog.LOGe(TAG, "wrong status received: %s", status); _connectionState = BleDeviceConnectionState.initialized; callback.onError(BleErrors.ERROR_CONNECT_FAILED); } } @Override public void onError(int error) { _connectionState = BleDeviceConnectionState.initialized; if (!retry(error)) { callback.onError(error); } else { connect(address, callback); } } }; // if (_bleBase.isClosed(_targetAddress)) { // _bleBase.connectDevice(_targetAddress, CONNECT_TIMEOUT, dataCallback); // } else if (_bleBase.isDisconnected(_targetAddress)) { // _bleBase.reconnectDevice(_targetAddress, 30, dataCallback); if (_bleBase.isClosed(_targetAddress) || _bleBase.isDisconnected(_targetAddress)) { _bleBase.connectDevice(_targetAddress, CONNECT_TIMEOUT, dataCallback); } } else if (checkConnectionState(BleDeviceConnectionState.connected, null)) { if (_targetAddress.equals(address)) { callback.onSuccess(); } else { callback.onError(BleErrors.ERROR_STILL_CONNECTED); } } else { callback.onError(BleErrors.ERROR_WRONG_STATE); } } /** * Helper function to handle connect events. E.g. update the connection state */ private void onConnect() { BleLog.LOGd(TAG, "successfully connected"); // todo: timeout? _connectionState = BleDeviceConnectionState.connected; _subscriberIds.clear(); } /** * Disconnect from the currently connected device. use the callback to report back * success or failure * @param callback the callback used to report back if the disconnect was successful or not * @return true if disconnect procedure is started, false if an error occurred and disconnect * procedure could not be started */ public boolean disconnect(final IStatusCallback callback) { checkConnectionState(BleDeviceConnectionState.connected, null); // if (!checkConnectionState(BleDeviceConnectionState.connected, callback)) return false; _connectionState = BleDeviceConnectionState.disconnecting; return _bleBase.disconnectDevice(_targetAddress, new IDataCallback() { @Override public void onData(JSONObject json) { String status = BleCore.getStatus(json); if (status == "disconnected") { onDisconnect(); callback.onSuccess(); } else { BleLog.LOGe(TAG, "wrong status received: %s", status); callback.onError(BleErrors.ERROR_DISCONNECT_FAILED); } } @Override public void onError(int error) { callback.onError(error); } }); } /** * Helper function to handle disconnect events, e.g. update the connection state */ private void onDisconnect() { BleLog.LOGd(TAG, "successfully disconnected"); // todo: timeout? _connectionState = BleDeviceConnectionState.initialized; clearDelayedDisconnect(); _subscriberIds.clear(); // _detectedCharacteristics.clear(); } /** * Helper function to check the connection state. calles the callbacks onError function * with an ERROR_WRONG_STATE if the current state does not match the one provided as a parameter * @param state the required state, is checked against the current state * @param callback the callback used to report an error if the states don't match, can be null * if error doesn't need to be reported, in which case the return value is enough * @return true if states match, false otherwise */ private boolean checkConnectionState(BleDeviceConnectionState state, IBaseCallback callback) { if (_connectionState != state) { // BleLog.LOGe(TAG, "wrong connection state: %s instead of %s", _connectionState.toString(), state.toString()); if (callback != null) { callback.onError(BleErrors.ERROR_WRONG_STATE); } return false; } return true; } /** * Close the device after a disconnect. A device has to be closed after a disconnect before * you can connect to it again. * By providing true as the clearCache parameter, the device cache will be cleared, making sure * that next time we connect, the most up to date service and characteristic list will be * retrieved. providing false as parameter, the device cache will not be cleared, and on next * connect and discover, the services and characteristics are retrieved from the cache, and do * not necessarily match the ones on the device. This speeds up discovery if you can be sure * that the device did not change. * @param clearCache provide true if the device cache should be cleared, will make sure that * services and characteristics are read from the device and not from the cache * provide false to leave the cached services and characteristics, making * the next connect and discover much faster * @param callback the callback used to report success or failure */ public void close(boolean clearCache, IStatusCallback callback) { BleLog.LOGd(TAG, "closing device ..."); _bleBase.closeDevice(_targetAddress, clearCache, callback); } /** * Discover the available services and characteristics of the connected device. The callbacks * onDiscovery function will be called with service UUID and characteristic UUID for each * discovered characteristic. Once the discovery completes, the onSuccess is called or the onError * if an error occurs * * Note: if you get wrong services and characteristics returned, try to clear the cache by calling * close with parameter clearCache set to true. this makes sure that next discover will really * read the services and characteristics from the device and not the cache * @param callback the callback used to report discovered services and characteristics */ public void discoverServices(final IDiscoveryCallback callback) { BleLog.LOGd(TAG, "discovering services ..."); _detectedCharacteristics.clear(); _bleBase.discoverServices(_targetAddress, new IDiscoveryCallback() { @Override public void onDiscovery(String serviceUuid, String characteristicUuid) { onCharacteristicDiscovered(serviceUuid, characteristicUuid); callback.onDiscovery(serviceUuid, characteristicUuid); } @Override public void onSuccess() { BleLog.LOGd(TAG, "... discovery done"); callback.onSuccess(); } @Override public void onError(int error) { BleLog.LOGe(TAG, "... discovery failed"); callback.onError(error); } }); } /** * Helper function to handle discovered characteristics. the discovered characteristics * are stored in a list, to later make sure that characteristics are only read/ written to * if they were actually discovered (are present on the device) * @param serviceUuid the UUID of the service in which the characteristic was found * @param characteristicUuid the UUID of the characteristic */ private void onCharacteristicDiscovered(String serviceUuid, String characteristicUuid) { // BleLog.LOGd(TAG, "discovered characteristic: %s", characteristicUuid); // todo: might have to store both service and characteristic uuid, because the characteristic // UUID is not unique! _detectedCharacteristics.add(characteristicUuid); } /** * Helper function to check if the connected device has the requested characteristic * @param characteristicUuid the UUID of the characteristic which should be present * @param callback the callback on which the onError is called if the characteristic was not found. * can be null if no error has to be reported, in which case the return value should * be enough * @return true if the device has the characteristic, false otherwise */ public boolean hasCharacteristic(String characteristicUuid, IBaseCallback callback) { if (_detectedCharacteristics.indexOf(characteristicUuid) == -1) { if (callback != null) { BleLog.LOGe(TAG, "characteristic not found"); callback.onError(BleErrors.ERROR_CHARACTERISTIC_NOT_FOUND); } return false; } return true; } /** * Helper function to check if the device has the configuration characteristics. to enable * configuration / settings, the device needs to have the following characteristics: * * CHAR_CONFIG_COTNROL_UUID (to select the configuration to be read or to write a new value * to the configuration) * * CHAR_CONFIG_READ_UUID (to read the value of the configuration previously selected) * if one of the characteristics is missing, configuration is not available * @param callback the callback to be informed about an error * @return true if configuration characteristics are available, false otherwise */ public boolean hasConfigurationCharacteristics(IBaseCallback callback) { return hasCharacteristic(BluenetConfig.CHAR_CONFIG_CONTROL_UUID, callback) && hasCharacteristic(BluenetConfig.CHAR_CONFIG_READ_UUID, callback); } /** * Helper function to check if the device has the state characteristics. to read state variables, * the device needs to have the following characteristics: * * CHAR_STATE_COTNROL_UUID (to select the state to be read) * * CHAR_STATE_READ_UUID (to read the value of the state previously selected) * if one of the characteristics is missing, state variables are not available * @param callback the callback to be informed about an error * @return true if state characteristics are available, false otherwise */ public boolean hasStateCharacteristics(IBaseCallback callback) { return hasCharacteristic(BluenetConfig.CHAR_STATE_CONTROL_UUID, callback) && hasCharacteristic(BluenetConfig.CHAR_STATE_READ_UUID, callback); } /** * Helper function to check if the command control characteristic is avialable * @param callback the callback to be informed about an error * @return true if control characteristic is available, false otherwise */ public boolean hasControlCharacteristic(IBaseCallback callback) { return hasCharacteristic(BluenetConfig.CHAR_CONTROL_UUID, null); } private void handleOnSuccess() { _retries = 0; } /** * Connect to the given device, once connection is established, discover the available * services and characteristics. The connection will be kept open. Need to disconnect and * close manually afterwards. * @param address the MAC address of the device for which we want to discover the services * @param callback the callback which will be notified about discovered services and * characteristics */ public void connectAndDiscover(final String address, final IDiscoveryCallback callback) { connect(address, new IStatusCallback() { @Override public void onSuccess() { handleOnSuccess(); /* [05.01.16] I am sometimes getting the behaviour that the connect first succeeds * and then a couple ms later I receive a disconnect again. In such a case, delaying * the discover leads to the library trying to discover services although a disconnect * was received in between. * If the delay is really necessary, we need to find a better solution */ _handler.postDelayed(new Runnable() { @Override public void run() { discoverServices(callback); } }, 500); // discoverServices(callback); } @Override public void onError(int error) { callback.onError(error); } }); } /** * Disconnect and close the device if disconnect was successful. See @disconnect and @close * functions for details. * @param clearCache set to true if device cache should be cleared on close. see @close for details * @param callback the callback which will be notified about success or failure * @return */ public synchronized boolean disconnectAndClose(boolean clearCache, final IStatusCallback callback) { checkConnectionState(BleDeviceConnectionState.connected, null); // if (!checkConnectionState(BleDeviceConnectionState.connected, callback)) return false; _connectionState = BleDeviceConnectionState.disconnecting; return _bleBase.disconnectAndCloseDevice(_targetAddress, clearCache, new IDataCallback() { @Override public void onData(JSONObject json) { String status = BleCore.getStatus(json); if (status == "closed") { onDisconnect(); // give the bluetooth adapter some time to settle after a close _handler.postDelayed(new Runnable() { @Override public void run() { callback.onSuccess(); } }, 300); } else if (status != "disconnected") { BleLog.LOGe(TAG, "wrong status received: %s", status); } } @Override public void onError(int error) { switch (error) { case BleErrors.ERROR_NEVER_CONNECTED: case BleErrors.ERROR_NOT_CONNECTED: _connectionState = BleDeviceConnectionState.initialized; break; } callback.onError(error); } }); } /** * Request permission to access BLE (locations) on Android > 6.0 * @param activity activity which will be notified about the permission request result. The * activity will need to implement the onRequestPermissionsResult function and * the library function @handlePermissionResult. */ public void requestPermissions(Activity activity) { _bleBase.requestPermissions(activity); } /** * Helper function which will handle the result of the permission request. call this function * from the activity's onActivityResult function * @param requestCode The request code, received in onRequestPermissionResult * @param permissions The requested permissions, received in onRequestPermissionResult * @param grantResults The grant results for the corresponding permissions * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. * @param callback the callback function which will be informed about success or failure of the * permission request */ public boolean handlePermissionResult(int requestCode, String[] permissions, int[] grantResults, IStatusCallback callback) { return _bleBase.handlePermissionResult(requestCode, permissions, grantResults, callback); } public Handler getHandler() { return _handler; } // abstract class CallbackRunnable implements Runnable { // IStatusCallback _callback; // // public void setCallback(IStatusCallback callback) { // _callback = callback; // } // } /** * a runnable with a callback. if the runnable is called it disconnects and * closes the device, then calls the callbacks onSuccess or onError function * used in connectAndXXX functions, which call disconnect after a timeout, but if several * functions are called in succession, only connects once and disconnects automatically * after the last call */ class DelayedDisconnectRunnable implements Runnable { IStatusCallback _callback; public void setCallback(IStatusCallback callback) { _callback = callback; } @Override public synchronized void run() { BleLog.LOGd(TAG, "delayed disconnect timeout"); disconnectAndClose(false, new IStatusCallback() { @Override public void onSuccess() { if (_callback != null) { _callback.onSuccess(); } } @Override public void onError(int error) { if (_callback != null) { _callback.onError(error); } } }); _delayedDisconnect = null; } } // the runnable used for the delayed disconnect. private DelayedDisconnectRunnable _delayedDisconnect = null; /** * Helper function to set a delayed disconnect. this will clear the previous delayed * disconnect, then register a new delayed disconnect with the DELAYED_DISCONNECT_TIME timeout * @param callback the callback which should be notified once the disconnect and close completed */ private void delayedDisconnect(IStatusCallback callback) { BleLog.LOGd(TAG, "delay disconnect"); // remove the previous delayed disconnect clearDelayedDisconnect(); // if a callback is provided (or no delayedDisconnect runnable available) if (callback != null || _delayedDisconnect == null) { // create and register a new delayed disconnect with the new callback _delayedDisconnect = new DelayedDisconnectRunnable(); // _delayedDisconnect.setCallback(callback); } // otherwise post the previous runnable again with the new timeout _handler.postDelayed(_delayedDisconnect, DELAYED_DISCONNECT_TIME); } /** * Clear the delayed disconnect, either when the timer expires, or if the device * disconnects for another reason */ private boolean clearDelayedDisconnect() { if (_delayedDisconnect != null) { BleLog.LOGd(TAG, "delay disconnect remove callbacks"); _handler.removeCallbacks(_delayedDisconnect); return true; } else { return false; } } private int _retries = 0; private boolean retry(int error) { // check if error is retriable ... switch (error) { case 19: case BleErrors.ERROR_CHARACTERISTIC_NOT_FOUND: { return false; } case 133: default: { if (_retries < MAX_RETRIES) { _retries++; BleLog.LOGw(TAG, "retry: %d", _retries); return true; } else { _retries = 0; return false; } } } } // private boolean retry(final String address, final IExecuteCallback function, final IStatusCallback callback) { // // if (_retries < MAX_RETRIES) { // _retries++; // BleLog.LOGw(TAG, "retry: %d", _retries); // connectAndExecute(address, function, callback); // return true; // } else { // _retries = 0; // return false; // } // // } /** * Connects to the given device, discovers the available services, then executes the provided * function, before disconnecting and closing the device again. Once everything completed, the * callbacks onSuccess function is called. * Note: the disconnect and close will be delayed, so consequent calls (within the timeout) to * connectAndExecute functions will keep the connection alive until the last call expires * @param address the MAC address of the device on which the function should be executed * @param function the function to be executed, i.e. the object providing the execute function * which should be executed * @param callback the callback which should be notified once the connectAndExecute function * completed (after closing the device, or if an error occurs) */ public void connectAndExecute(final String address, final IExecuteCallback function, final IStatusCallback callback) { final boolean resumeDelayedDisconnect = clearDelayedDisconnect(); if (checkConnection(address)) { function.execute(new IStatusCallback() { @Override public void onSuccess() { if (resumeDelayedDisconnect) { delayedDisconnect(null); } callback.onSuccess(); handleOnSuccess(); } @Override public void onError(final int error) { if (resumeDelayedDisconnect) { delayedDisconnect(null); } if (error == BleErrors.ERROR_CHARACTERISTIC_NOT_FOUND) { callback.onError(error); } else { if (!retry(error)) { callback.onError(error); } else { connectAndExecute(address, function, callback); } } } }); } else { connectAndDiscover(address, new IDiscoveryCallback() { @Override public void onDiscovery(String serviceUuid, String characteristicUuid) { /* don't care */ } @Override public void onSuccess() { // call execute function function.execute(new IStatusCallback() { @Override public void onSuccess() { delayedDisconnect(null); callback.onSuccess(); handleOnSuccess(); } @Override public void onError(final int error) { delayedDisconnect(null); if (!retry(error)) { callback.onError(error); } else { connectAndExecute(address, function, callback); } } }); } @Override public void onError(final int error) { // todo: do we need to disconnect and close here? disconnectAndClose(true, new IStatusCallback() { @Override public void onSuccess() { if (!retry(error)) { callback.onError(error); } else { connectAndExecute(address, function, callback); } } @Override public void onError(int e) { if (!retry(error)) { callback.onError(error); } else { connectAndExecute(address, function, callback); } } }); } }); } } /** * Check if we are currently connected to a device * @param callback callback to be notified with an error if we are not connected. provide * null if no notification is necessary, in which case the return value * should be enough. * @return true if device is connected, false otherwise */ public boolean isConnected(IBaseCallback callback) { // if (checkConnectionState(BleDeviceConnectionState.connected, callback)) { if (checkConnectionState(BleDeviceConnectionState.connected, null) && _bleBase.isDeviceConnected(_targetAddress)) { return true; } else { if (callback != null) { callback.onError(BleErrors.ERROR_NOT_CONNECTED); } return false; } // } // return false; } /** * Helper function to check if we are already / still connected, and if a delayed disconnect is * active, restart the delay. * @return true if we are connected, false otherwise * @param address */ public synchronized boolean checkConnection(String address) { if (isConnected(null) && _targetAddress.equals(address)) { if (_delayedDisconnect != null) { // delayedDisconnect(null); clearDelayedDisconnect(); } return true; } return false; } /////////////////// // Power service // /////////////////// /** * Function to read the current PWM value from the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPwm(final IIntegerCallback callback) { // getHandler().post(new Runnable() { // @Override // public void run() { if (isConnected(callback)) { BleLog.LOGd(TAG, "Reading current PWM value ..."); if (hasStateCharacteristics(null)) { _bleExtState.getSwitchState(_targetAddress, callback); } else if (hasCharacteristic(BluenetConfig.CHAR_PWM_UUID, callback)) { _bleBase.readPWM(_targetAddress, callback); } } // } // }); } /** * Function to read the current PWM value from the device. Connects to the device if not already * connected, and/or delays the disconnect if necessary. * * @param address the MAC address of the device from which the PWM value should be read * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPwm(final String address, final IIntegerCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading current PWM value ..."); // if (checkConnection(address)) { // readPwm(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readPwm(new IIntegerCallback() { @Override public void onSuccess(int result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to write the given PWM value to the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param value the PWM value to be written to the device * @param callback the callback which will be informed about success or failure */ public void writePwm(final int value, final IStatusCallback callback) { // getHandler().post(new Runnable() { // @Override // public void run() { if (isConnected(callback)) { BleLog.LOGd(TAG, "Set PWM to %d", value); if (hasControlCharacteristic(null)) { BleLog.LOGd(TAG, "use control characteristic"); _bleBase.sendCommand(_targetAddress, new CommandMsg(BluenetConfig.CMD_PWM, 1, new byte[] { (byte) value }), callback); } else if (hasCharacteristic(BluenetConfig.CHAR_PWM_UUID, callback)) { _bleBase.writePWM(_targetAddress, value, callback); } } // } // }); } /** * Function to write the given PWM value to the device. Connects to the device if not already * connected, and/or delays the disconnect if necessary. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param address the MAC address of the device to which the PWM value should be written * @param value the PWM value to be written * @param callback the callback which will be informed about success or failure */ public void writePwm(final String address, final int value, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Set PWM to %d", value); // if (checkConnection(address)) { // writePwm(value, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { writePwm(value, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to read the current relay value from the device. * callback returns true if relay is on, false otherwise * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readRelay(final IBooleanCallback callback) { // getHandler().post(new Runnable() { // @Override // public void run() { if (isConnected(callback)) { BleLog.LOGd(TAG, "Reading current Relay value ..."); if (hasStateCharacteristics(null)) { _bleExtState.getSwitchState(_targetAddress, new IIntegerCallback() { @Override public void onSuccess(int result) { callback.onSuccess(result > 0); } @Override public void onError(int error) { callback.onError(error); } }); } else if (hasCharacteristic(BluenetConfig.CHAR_RELAY_UUID, callback)) { _bleBase.readRelay(_targetAddress, callback); } } // } // }); } /** * Function to read the current relay value from the device. Connects to the device if not already * connected, and/or delays the disconnect if necessary. * callback returns true if relay is on, false otherwise * * @param address the MAC address of the device from which the Relay value should be read * @param callback the callback which will get the read value on success, or an error otherwise */ public void readRelay(final String address, final IBooleanCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading current Relay value ..."); // if (checkConnection(address)) { // readRelay(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readRelay(new IBooleanCallback() { @Override public void onSuccess(boolean result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to write the given Relay value to the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param relayOn true if the relay should be switched on, false otherwise * @param callback the callback which will be informed about success or failure */ public void writeRelay(final boolean relayOn, final IStatusCallback callback) { // getHandler().post(new Runnable() { // @Override // public void run() { if (isConnected(callback)) { BleLog.LOGd(TAG, "Set Relay to %b", relayOn); if (hasControlCharacteristic(null)) { BleLog.LOGd(TAG, "use control characteristic"); int value = relayOn ? BluenetConfig.RELAY_ON : BluenetConfig.RELAY_OFF; _bleBase.sendCommand(_targetAddress, new CommandMsg(BluenetConfig.CMD_RELAY, 1, new byte[] { (byte) value }), callback); } else if (hasCharacteristic(BluenetConfig.CHAR_RELAY_UUID, callback)) { _bleBase.writeRelay(_targetAddress, relayOn, callback); } } // } // }); } /** * Function to write the given Relay value to the device. Connects to the device if not already * connected, and/or delays the disconnect if necessary. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param address the MAC address of the device to which the Relay value should be written * @param relayOn true if the relay should be switched on, false otherwise * @param callback the callback which will be informed about success or failure */ public void writeRelay(final String address, final boolean relayOn, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Set Relay to %b", relayOn); // if (checkConnection(address)) { // writeRelay(relayOn, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { writeRelay(relayOn, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Toggle power between ON (pwm = BluenetConfig.PWM_ON) and OFF (pwm = BluenetConfig.PWM_OFF). * Reads first the current PWM value from the device, then switches the PWM value accordingly. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback callback which will be informed about success or failure */ public void togglePower(final IStatusCallback callback) { readPwm(new IIntegerCallback() { @Override public void onSuccess(int result) { if (result > 0) { powerOff(callback); } else { powerOn(callback); } } @Override public void onError(int error) { callback.onError(error); } }); } /** * Toggle power between ON (pwm = BluenetConfig.PWM_ON) and OFF (pwm = BluenetConfig.PWM_OFF). * Reads first the current PWM value from the device, then switches the PWM value accordingly. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback callback which will be informed about success or failure */ public void togglePower(final String address, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Toggle power ..."); // if (checkConnection(address)) { // togglePower(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { togglePower(new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Helper function to set power ON (sets pwm value to BluenetConfig.PWM_ON) * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void powerOn(IStatusCallback callback) { writePwm(BluenetConfig.PWM_ON, callback); } /** * Helper function to set power ON (sets pwm value to BluenetConfig.PWM_ON) * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param callback the callback which will be informed about success or failure */ public void powerOn(String address, final IStatusCallback callback) { writePwm(address, BluenetConfig.PWM_ON, callback); } /** * Helper function to set power OFF (sets pwm value to BluenetConfig.PWM_OFF) * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void powerOff(IStatusCallback callback) { writePwm(BluenetConfig.PWM_OFF, callback); } /** * Helper function to set power OFF (sets pwm value to BluenetConfig.PWM_OFF) * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param callback the callback which will be informed about success or failure */ public void powerOff(String address, final IStatusCallback callback) { writePwm(address, BluenetConfig.PWM_OFF, callback); } /** * Toggle relay between ON (BluenetConfig.RELAY_ON) and OFF (BluenetConfig.RELAY_OFF). * Reads first the current relay value from the device, then switches the relay value accordingly. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback callback which will be informed about success or failure */ public void toggleRelay(final IStatusCallback callback) { readRelay(new IBooleanCallback() { @Override public void onSuccess(boolean result) { if (result) { relayOff(callback); } else { relayOn(callback); } } @Override public void onError(int error) { callback.onError(error); } }); } /** * Toggle relay between ON (BluenetConfig.RELAY_ON) and OFF (BluenetConfig.RELAY_OFF). * Reads first the current relay value from the device, then switches the relay value accordingly. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback callback which will be informed about success or failure */ public void toggleRelay(final String address, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Toggle relay ..."); // if (checkConnection(address)) { // togglePower(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { toggleRelay(new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Helper function to set relay ON (sets relay value to BluenetConfig.RELAY_ON) * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void relayOn(IStatusCallback callback) { writeRelay(true, callback); } /** * Helper function to set relay ON (sets pwm value to BluenetConfig.RELAY_ON) * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param callback the callback which will be informed about success or failure */ public void relayOn(String address, final IStatusCallback callback) { writeRelay(address, true, callback); } /** * Helper function to set relay OFF (sets pwm value to BluenetConfig.RELAY_OFF) * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void relayOff(IStatusCallback callback) { writeRelay(false, callback); } /** * Helper function to set relay OFF (sets pwm value to BluenetConfig.RELAY_OFF) * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param callback the callback which will be informed about success or failure */ public void relayOff(String address, final IStatusCallback callback) { writeRelay(address, false, callback); } // /** // * If permanently connected to a device, state notifications can be requested. this means that // * as soon as a state variable changes on the device, a notification will be sent and the // * callback will trigger with the new value // * Note: there is no version of this function with address parameter, as this functionality is // * only available if we are permanently connected. so it would make no sense to connect and // * disconnect again afterwards // * @param type type of state variable which should be notified, see @BleStateTypes // * @param len for verification, provide length of the variable, 1 for byte, 2 for short, etc. // * @param callback callback which should be informed about a new value update // */ // public void getStateNotifications(final int type, final int len, final IIntegerCallback callback) { // if (isConnected(callback) && hasStateCharacteristics(callback)) { // _bleBase.getStateNotifications(_targetAddress, type, // new IIntegerCallback() { // @Override // public void onSuccess(int result) { // // } // // @Override // public void onError(int error) { // // } // }, // new IStateCallback() { // @Override // public void onSuccess(StateMsg state) { // if (state.getLength() == len) { // callback.onSuccess(state.getValue()); // } else { // callback.onError(BleErrors.ERROR_WRONG_LENGTH_PARAMETER); // } // } // // @Override // public void onError(int error) { // callback.onError(error); // } // }); // } // } // // /** // * Function to read the value of the given state variable from the device. // * // * Note: needs to be already connected or an error is created! Use overloaded function // * with address otherwise // * @param callback the callback which will get the read value on success, or an error otherwise // */ // private void getState(int type, final int len, final IIntegerCallback callback) { // if (isConnected(callback) && hasStateCharacteristics(callback)) { // _bleBase.getState(_targetAddress, type, new IStateCallback() { // @Override // public void onSuccess(StateMsg state) { // if (state.getLength() == len) { // callback.onSuccess(state.getValue()); // } else { // callback.onError(BleErrors.ERROR_WRONG_LENGTH_PARAMETER); // } // } // // @Override // public void onError(int error) { // callback.onError(error); // } // }); // } // } // // /** // * Function to read the value of the given state variable from the device. // * // * Connects to the device if not already connected, and/or delays the disconnect if necessary. // * @param address the MAC address of the device // * @param callback the callback which will get the read value on success, or an error otherwise // */ // private void getState(String address, final int type, final int len, final IIntegerCallback callback) { // if (checkConnection(address)) { // getState(type, len, callback); // } else { // connectAndExecute(address, new IExecuteCallback() { // @Override // public void execute(final IStatusCallback execCallback) { // getState(type, len, new IIntegerCallback() { // @Override // public void onSuccess(int result) { // callback.onSuccess(result); // execCallback.onSuccess(); // } // // @Override // public void onError(int error) { // execCallback.onError(error); // } // }); // } // }, new IStatusCallback() { // @Override // public void onSuccess() { /* don't care */ } // // @Override // public void onError(int error) { // callback.onError(error); // } // }); // } // } /** * Function to read the current consumption value from the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPowerConsumption(final IIntegerCallback callback) { if (isConnected(callback)) { BleLog.LOGd(TAG, "Reading power consumption value ..."); if (hasStateCharacteristics(null)) { _bleExtState.getPowerUsage(_targetAddress, callback); } else if (hasCharacteristic(BluenetConfig.CHAR_POWER_CONSUMPTION_UUID, callback)) { _bleBase.readPowerConsumption(_targetAddress, callback); } } } /** * Function to read the current consumption value from the device. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPowerConsumption(final String address, final IIntegerCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading power consumption value ..."); // if (checkConnection(address)) { // readPowerConsumption(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readPowerConsumption(new IIntegerCallback() { @Override public void onSuccess(int result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to read the current curve from the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPowerSamples(final IPowerSamplesCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_POWER_SAMPLES_UUID, callback)) { BleLog.LOGd(TAG, "Reading PowerSamples value ..."); _bleBase.readPowerSamples(_targetAddress, callback); } } /** * Function to read the current curve from the device. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will get the read value on success, or an error otherwise */ public void readPowerSamples(final String address, final IPowerSamplesCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading PowerSamples value ..."); // if (checkConnection(address)) { // readPowerSamples(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readPowerSamples(new IPowerSamplesCallback() { @Override public void onData(PowerSamples result) { callback.onData(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } public void subscribePowerSamples(final IPowerSamplesCallback callback) { if (!_subscriberIds.containsKey(BluenetConfig.CHAR_POWER_SAMPLES_UUID)) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_POWER_SAMPLES_UUID, callback)) { BleLog.LOGd(TAG, "Subscribing to PowerSamples ..."); _bleBase.subscribePowerSamples(_targetAddress, new IIntegerCallback() { @Override public void onSuccess(int result) { _subscriberIds.put(BluenetConfig.CHAR_POWER_SAMPLES_UUID, result); } @Override public void onError(int error) { callback.onError(error); } }, callback); } } else { BleLog.LOGd(TAG, "already subscribed"); } } public void unsubscribePowerSamples(IStatusCallback callback) { if (isConnected(null) && _subscriberIds.containsKey(BluenetConfig.CHAR_POWER_SAMPLES_UUID)) { BleLog.LOGd(TAG, "Unsubscribing from PowerSamples ..."); _bleBase.unsubscribePowerSamples(_targetAddress, _subscriberIds.get(BluenetConfig.CHAR_POWER_SAMPLES_UUID), callback); } else { BleLog.LOGd(TAG, "not subscribed or connected"); } } ///////////////////// // General service // ///////////////////// /** * Function to write the given reset value to the device. This will reset the device, and * the behaviour after the reset depends on the given value. see @BluenetTypes for possible * values * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param value the value to be written to the device * @param callback the callback which will be informed about success or failure */ private void writeReset(int value, IStatusCallback callback) { if (isConnected(callback)) { BleLog.LOGd(TAG, "Set Reset to %d", value); if (hasControlCharacteristic(null)) { BleLog.LOGd(TAG, "use control characteristic"); _bleBase.sendCommand(_targetAddress, new CommandMsg(BluenetConfig.CMD_RESET, 1, new byte[] { (byte) value }), callback); } else if (hasCharacteristic(BluenetConfig.CHAR_RESET_UUID, callback)) { _bleBase.writeReset(_targetAddress, value, callback); } } } /** * Function to write the given reset value to the device. This will reset the device, and * the behaviour after the reset depends on the given value. see @BluenetTypes for possible * values * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param value the value to be written * @param callback the callback which will be informed about success or failure */ private void writeReset(final String address, final int value, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Set Reset to %d", value); // if (checkConnection(address)) { // writeReset(value, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { writeReset(value, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to reset / reboot the device * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void resetDevice(IStatusCallback callback) { writeReset(BluenetConfig.RESET_DEFAULT, callback); } /** * Function to reset / reboot the device * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will be informed about success or failure */ public void resetDevice(String address, final IStatusCallback callback) { writeReset(address, BluenetConfig.RESET_DEFAULT, callback); } /** * Function to reset / reboot the device to the bootloader, so that a new device firmware * can be uploaded * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will be informed about success or failure */ public void resetToBootloader(IStatusCallback callback) { writeReset(BluenetConfig.RESET_DFU, callback); } /** * Function to reset / reboot the device to the bootloader, so that a new device firmware * can be uploaded * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will be informed about success or failure */ public void resetToBootloader(String address, final IStatusCallback callback) { writeReset(address, BluenetConfig.RESET_DFU, callback); } /** * Function to read the current temperature value from the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readTemperature(IIntegerCallback callback) { if (isConnected(callback)) { BleLog.LOGd(TAG, "Reading Temperature value ..."); if (hasStateCharacteristics(null)) { _bleExtState.getTemperature(_targetAddress, callback); } else if (hasCharacteristic(BluenetConfig.CHAR_TEMPERATURE_UUID, callback)) { _bleBase.readTemperature(_targetAddress, callback); } } } /** * Function to read the current temperature value from the device. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will get the read value on success, or an error otherwise */ public void readTemperature(final String address, final IIntegerCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading Temperature value ..."); // if (checkConnection(address)) { // readTemperature(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readTemperature(new IIntegerCallback() { @Override public void onSuccess(int result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to write the given mesh message to the device. the mesh message will be * forwarded by the device into the mesh network * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param value the message to be sent to the mesh (through the device) * @param callback the callback which will be informed about success or failure */ public void writeMeshMessage(MeshMsg value, IStatusCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_MESH_CONTROL_UUID, callback)) { BleLog.LOGd(TAG, "Set MeshMessage to %s", value.toString()); _bleBase.writeMeshMessage(_targetAddress, value, callback); } } /** * Function to write the given mesh message to the device. the mesh message will be * forwarded by the device into the mesh network * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param value the message to be sent to the mesh (through the device) * @param callback the callback which will be informed about success or failure */ public void writeMeshMessage(final String address, final MeshMsg value, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Set MeshMessage to %s", value.toString()); // if (checkConnection(address)) { // writeMeshMessage(value, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { writeMeshMessage(value, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } ////////////////////////// // Localization service // ////////////////////////// /** * Function to read the list of tracked devices from the device. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void readTrackedDevices(IByteArrayCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_TRACKED_DEVICES_UUID, callback)) { BleLog.LOGd(TAG, "Reading TrackedDevices value ..."); _bleBase.readTrackedDevices(getTargetAddress(), callback); } } /** * Function to read the list of tracked devices from the device. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will get the read value on success, or an error otherwise */ public void readTrackedDevices(final String address, final IByteArrayCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading TrackedDevices value ..."); // if (checkConnection(address)) { // readTrackedDevices(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readTrackedDevices(new IByteArrayCallback() { @Override public void onSuccess(byte[] result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to add a new device to be tracked * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param value the new device to be tracked * @param callback the callback which will be informed about success or failure */ public void addTrackedDevice(TrackedDeviceMsg value, IStatusCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_TRACK_CONTROL_UUID, callback)) { BleLog.LOGd(TAG, "Set TrackedDevice to %s", value.toString()); _bleBase.addTrackedDevice(getTargetAddress(), value, callback); } } /** * Function to add a new device to be tracked * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param value the new device to be tracked * @param callback the callback which will be informed about success or failure */ public void addTrackedDevice(final String address, final TrackedDeviceMsg value, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Set TrackedDevice to %s", value.toString()); // if (checkConnection(address)) { // addTrackedDevice(value, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { addTrackedDevice(value, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to get the list of scanned BLE devices from the device. Need to call writeScanDevices * first to start and to stop the scan. Consider using @scanForDevices instead * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param callback the callback which will get the read value on success, or an error otherwise */ public void listScannedDevices(IByteArrayCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_SCANNED_DEVICES_UUID, callback)) { BleLog.LOGd(TAG, "List scanned devices ..."); _bleBase.listScannedDevices(getTargetAddress(), callback); } } /** * Function to get the list of scanned BLE devices from the device. Need to call writeScanDevices * first to start and to stop the scan. Consider using @scanForDevices instead * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param address the MAC address of the device * @param callback the callback which will get the read value on success, or an error otherwise */ public void listScannedDevices(final String address, final IByteArrayCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "List scanned devices ..."); // if (checkConnection(address)) { // listScannedDevices(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { listScannedDevices(new IByteArrayCallback() { @Override public void onSuccess(byte[] result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to start / stop a scan for BLE devices. After starting a scan, it will run indefinite * until this function is called again to stop it. * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param value true to start scanning for devices, false to stop the scan * @param callback the callback which will be informed about success or failure */ public void writeScanDevices(boolean value, IStatusCallback callback) { if (isConnected(callback)) { BleLog.LOGd(TAG, "Scan Devices: %b", value); if (hasControlCharacteristic(null)) { BleLog.LOGd(TAG, "use control characteristic"); int scan = (value ? 1 : 0); _bleBase.sendCommand(getTargetAddress(), new CommandMsg(BluenetConfig.CMD_SCAN_DEVICES, 1, new byte[] { (byte) scan }), callback); } else if (hasCharacteristic(BluenetConfig.CHAR_SCAN_CONTROL_UUID, callback)) { _bleBase.scanDevices(getTargetAddress(), value, callback); } } } /** * Function to start / stop a scan for BLE devices. After starting a scan, it will run indefinite * until this function is called again to stop it. * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param value true to start scanning for devices, false to stop the scan * @param callback the callback which will be informed about success or failure */ public void writeScanDevices(final String address, final boolean value, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Scan Devices: %b", value); // if (checkConnection(address)) { // writeScanDevices(value, callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { writeScanDevices(value, new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } /** * Function to start a scan for devices, stop it again after scanDuration expired, then * return the list of devices * * Note: needs to be already connected or an error is created! Use overloaded function * with address otherwise * @param scanDuration the duration (in ms) for which the device should scan for other BLE * devices * @param callback the callback which will return the list of scanned devices */ public void scanForDevices(final int scanDuration, final IByteArrayCallback callback) { writeScanDevices(true, new IStatusCallback() { @Override public void onSuccess() { _handler.postDelayed(new Runnable() { @Override public void run() { writeScanDevices(false, new IStatusCallback() { @Override public void onSuccess() { // delay 500 ms, just wait, don't postdelay, since we are already // inside the handler, and hopefully 500ms delay won't cause havoc SystemClock.sleep(500); listScannedDevices(callback); } @Override public void onError(int error) { callback.onError(error); } }); } }, scanDuration); } @Override public void onError(int error) { callback.onError(error); } }); } /** * Function to start a scan for devices, stop it again after scanDuration expired, then * return the list of devices * * Connects to the device if not already connected, and/or delays the disconnect if necessary. * @param scanDuration the duration (in ms) for which the device should scan for other BLE * devices * @param callback the callback which will return the list of scanned devices */ public void scanForDevices(final String address, final int scanDuration, final IByteArrayCallback callback) { BleLog.LOGd(TAG, "Scan for devices ..."); if (checkConnection(address)) { scanForDevices(scanDuration, callback); } else { // connect and execute ... connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback startExecCallback) { // ... start scanning for devices writeScanDevices(true, new IStatusCallback() { @Override public void onSuccess() { // if successfully started, post the stop scan with scanDuration delay _handler.postDelayed(new Runnable() { @Override public void run() { // once scanDuration delay expired, connect and execute ... connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback stopExecCallback) { // ... stop scanning for devices writeScanDevices(false, new IStatusCallback() { @Override public void onSuccess() { // if successfully stopped, get the list of scanned devices ... // delay 500 ms to give time for list to be written to characteristic // just wait, don't postdelay, since we are already // inside the handler, and hopefully 500ms delay won't cause havoc SystemClock.sleep(500); // get the list ... listScannedDevices(new IByteArrayCallback() { @Override public void onSuccess(byte[] result) { callback.onSuccess(result); // ... and disconnect again once we have it stopExecCallback.onSuccess(); } @Override public void onError(int error) { // callback.onError(error); // also disconnect if an error occurs stopExecCallback.onError(error); } }); } @Override public void onError(int error) { // callback.onError(error); // disconnect if an error occurs stopExecCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); } }, scanDuration); // after posting, disconnect again startExecCallback.onSuccess(); } @Override public void onError(int error) { // callback.onError(error); // disconnect if an error occurs startExecCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); } } public void readAlert(final IAlertCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_NEW_ALERT_UUID, callback)) { BleLog.LOGd(TAG, "Reading Alert value ..."); _bleBase.readAlert(getTargetAddress(), callback); } } public void readAlert(final String address, final IAlertCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reading Alert value ..."); // if (checkConnection(address)) { // readAlert(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { readAlert(new IAlertCallback() { @Override public void onSuccess(AlertState result) { callback.onSuccess(result); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { /* don't care */ } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } public void resetAlert(IStatusCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.CHAR_NEW_ALERT_UUID, callback)) { BleLog.LOGd(TAG, "Reset Alert"); _bleBase.writeAlert(getTargetAddress(), 0, callback); } } public void resetAlert(final String address, final IStatusCallback callback) { getHandler().post(new Runnable() { @Override public void run() { BleLog.LOGd(TAG, "Reset Alert"); // if (checkConnection(address)) { // resetAlert(callback); // } else { connectAndExecute(address, new IExecuteCallback() { @Override public void execute(final IStatusCallback execCallback) { resetAlert(new IStatusCallback() { @Override public void onSuccess() { callback.onSuccess(); execCallback.onSuccess(); } @Override public void onError(int error) { execCallback.onError(error); } }); } }, new IStatusCallback() { @Override public void onSuccess() { } @Override public void onError(int error) { callback.onError(error); } }); // } } }); } public void readMeshData(final IMeshDataCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.MESH_DATA_CHARACTERISTIC_UUID, callback)) { BleLog.LOGd(TAG, "subscribe to mesh data"); _bleBase.readMeshData(getTargetAddress(), callback); } } public void subscribeMeshData(final IMeshDataCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.MESH_DATA_CHARACTERISTIC_UUID, callback)) { BleLog.LOGd(TAG, "subscribe to mesh data"); _bleBase.subscribeMeshData(getTargetAddress(), callback); } } public void unsubscribeMeshData(final IMeshDataCallback callback) { if (isConnected(callback) && hasCharacteristic(BluenetConfig.MESH_DATA_CHARACTERISTIC_UUID, callback)) { BleLog.LOGd(TAG, "unsubscribe from mesh data"); _bleBase.unsubscribeMeshData(getTargetAddress(), callback); } } // public void writeConfiguration(ConfigurationMsg value, IStatusCallback callback) { // if (isConnected(callback) && hasConfigurationCharacteristics(callback)) { // BleCore.LOGd(TAG, "Set Configuration to %s", value.toString()); // _bleBase.writeConfiguration(_targetAddress, value, callback); // } // } // public void writeConfiguration(String address, final ConfigurationMsg value, final IStatusCallback callback) { // if (checkConnection(null)) { // writeConfiguration(value, callback); // } else { // connectAndExecute(address, new IExecuteCallback() { // @Override // public void execute(final IStatusCallback execCallback) { // writeConfiguration(value, new IStatusCallback() { // @Override // public void onDeviceScanned() { // callback.onDeviceScanned(); // execCallback.onDeviceScanned(); // } // // @Override // public void onError(int error) { // execCallback.onDeviceScanned(); // } // }); // } // }, new IStatusCallback() { // @Override // public void onDeviceScanned() { /* don't care */ } // // @Override // public void onError(int error) { // callback.onError(error); // } // }); // } // } // public void readConfiguration(int configurationType, IConfigurationCallback callback) { // if (isConnected(callback) && hasConfigurationCharacteristics(callback)) { // BleCore.LOGd(TAG, "Reading Configuration value ..."); // _bleBase.getConfiguration(_targetAddress, configurationType, callback); // } // } // public void readConfiguration(String address, final int configurationType, final IConfigurationCallback callback) { // if (checkConnection(null)) { // readConfiguration(configurationType, callback); // } else { // connectAndExecute(address, new IExecuteCallback() { // @Override // public void execute(final IStatusCallback execCallback) { // readConfiguration(configurationType, new IConfigurationCallback() { // @Override // public void onDeviceScanned(ConfigurationMsg result) { // callback.onDeviceScanned(result); // execCallback.onDeviceScanned(); // } // // @Override // public void onError(int error) { // execCallback.onDeviceScanned(); // } // }); // } // }, new IStatusCallback() { // @Override // public void onDeviceScanned() { /* don't care */ } // // @Override // public void onError(int error) { // callback.onError(error); // } // }); // } // } }