Java tutorial
/** * Copyright 2014 MbientLab Inc. All rights reserved. * * IMPORTANT: Your use of this Software is limited to those specific rights * granted under the terms of a software license agreement between the user who * downloaded the software, his/her employer (which must be your employer) and * MbientLab Inc, (the "License"). You may not use this Software unless you * agree to abide by the terms of the License which can be found at * www.mbientlab.com/terms . The License limits your use, and you acknowledge, * that the Software may not be modified, copied or distributed and can be used * solely and exclusively in conjunction with a MbientLab Inc, product. Other * than for the foregoing purpose, you may not use, reproduce, copy, prepare * derivative works of, modify, distribute, perform, display or sell this * Software and/or its documentation for any purpose. * * YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE * PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE, * NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL * MBIENTLAB OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT, NEGLIGENCE, * STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER LEGAL EQUITABLE * THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES INCLUDING BUT NOT LIMITED * TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE OR CONSEQUENTIAL DAMAGES, LOST * PROFITS OR LOST DATA, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY, * SERVICES, OR ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY * DEFENSE THEREOF), OR OTHER SIMILAR COSTS. * * Should you have any questions regarding your right to use this Software, * contact MbientLab Inc, at www.mbientlab.com. */ package com.mbientlab.metawear.api; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map.Entry; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import com.mbientlab.metawear.api.GATT.GATTCharacteristic; import com.mbientlab.metawear.api.GATT.GATTService; import com.mbientlab.metawear.api.MetaWearController.DeviceCallbacks; import com.mbientlab.metawear.api.MetaWearController.DeviceCallbacks.GattOperation; import com.mbientlab.metawear.api.MetaWearController.ModuleCallbacks; import com.mbientlab.metawear.api.characteristic.*; import com.mbientlab.metawear.api.controller.*; import com.mbientlab.metawear.api.controller.Accelerometer.SamplingConfig.OutputDataRate; import com.mbientlab.metawear.api.controller.Logging.Trigger; import com.mbientlab.metawear.api.util.Registers; import android.app.Service; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; /** * Service for maintaining the Bluetooth GATT connection to the MetaWear board * @author Eric Tsai */ public class MetaWearBleService extends Service { /** * Represents the state of MetaWear service * @author Eric Tsai */ private enum DeviceState { /** Service is in the process of enabling notifications */ ENABLING_NOTIFICATIONS, /** Service is reading characteristics */ READING_CHARACTERISTICS, /** Service is writing characteristics */ WRITING_CHARACTERISTICS, /** Service is ready to process a command */ READY; } private class Action { /** An non-zero status was returned in a bt gatt callback function */ public static final String GATT_ERROR = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.GATT_ERROR"; /** Data was received from a MetaWear notification register */ public static final String NOTIFICATION_RECEIVED = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.NOTIFICATION_RECEIVED"; /** Connected to a Bluetooth device */ public static final String DEVICE_CONNECTED = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.DEVICE_CONNECTED"; /** Disconnected from a Bluetooth device */ public static final String DEVICE_DISCONNECTED = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.DEVICE_DISCONNECTED"; /** A Bluetooth characteristic was read */ public static final String CHARACTERISTIC_READ = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.CHARACTERISTIC_READ"; /** Read the RSSI value of the remote device */ public static final String RSSI_READ = "com.mbientlab.com.metawear.api.MetaWearBleService.Action.RSSI_READ"; } private class Extra { public static final String EXPLICIT_CLOSE = "com.mbientlab.metawear.api.MetaWearBleService.Extra.EXPLICIT_CLOSE"; public static final String BLUETOOTH_DEVICE = "com.mbientlab.metawear.api.MetaWearBleService.Extra.BLUETOOTH_DEVICE"; /** Extra intent information identifying the gatt operation */ public static final String GATT_OPERATION = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.GATT_OPERATION"; /** Extra intent information for a status code */ public static final String STATUS = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.STATUS"; /** Extra Intent information for the remote rssi value */ public static final String RSSI = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.RSSI"; /** Extra Intent information for the service UUID */ public static final String SERVICE_UUID = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.SERVICE_UUID"; /** Extra Intent information for the characteristic UUID */ public static final String CHARACTERISTIC_UUID = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.CHARACTERISTIC_UUID"; /** Extra Intent information for the characteristic value */ public static final String CHARACTERISTIC_VALUE = "com.mbientlab.com.metawear.api.MetaWearBleService.Extra.CHARACTERISTIC_VALUE"; } private interface GattAction { public void execAction(); } private interface InternalCallback { public void process(byte[] data); } private interface EventInfo { public byte[] entry(); public byte[] command(); } private class EventTriggerBuilder { private final ArrayList<EventInfo> entryBytes = new ArrayList<>(); private final Register srcReg; private final byte index; public EventTriggerBuilder(Register srcReg, byte index) { this.srcReg = srcReg; this.index = index; } public EventTriggerBuilder withDestRegister(final Register destReg, final byte[] command, final boolean isRead) { entryBytes.add(new EventInfo() { @Override public byte[] entry() { byte destOpcode = destReg.opcode(); if (isRead) { destOpcode |= 0x80; } return new byte[] { srcReg.module().opcode, srcReg.opcode(), index, destReg.module().opcode, destOpcode, (byte) command.length }; } @Override public byte[] command() { return command; } }); return this; } public Collection<EventInfo> getEventInfo() { return entryBytes; } } private final static UUID CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); private class MetaWearState { public MetaWearState(BluetoothDevice mwBoard) { this.mwBoard = mwBoard; } /** * */ public MetaWearState() { // TODO Auto-generated constructor stub } public void resetState() { mwController.clearCallbacks(); shouldNotify.clear(); commandBytes.clear(); readCharUuids.clear(); } public BluetoothDevice mwBoard; public byte thermistorMode = 0; public EventTriggerBuilder etBuilder; public boolean connected, isRecording = false, retainState = true, readyToClose, notifyUser; public MetaWearControllerImpl mwController = null; public BluetoothGatt mwGatt = null; public DeviceState deviceState = null; public final ArrayDeque<BluetoothGattCharacteristic> shouldNotify = new ArrayDeque<>(); public final ConcurrentLinkedQueue<byte[]> commandBytes = new ConcurrentLinkedQueue<>(); public final ConcurrentLinkedQueue<GATTCharacteristic> readCharUuids = new ConcurrentLinkedQueue<>(); public final HashMap<Register, InternalCallback> internalCallbacks = new HashMap<>(); public final HashMap<Byte, ArrayList<ModuleCallbacks>> moduleCallbackMap = new HashMap<>(); public final HashSet<DeviceCallbacks> deviceCallbacks = new HashSet<>(); } /** GATT connection to the ble device */ private static final HashMap<BluetoothDevice, MetaWearState> metaWearStates = new HashMap<>(); private boolean useLocalBroadcastMnger = false; private final AtomicBoolean isExecGattActions = new AtomicBoolean(false); private final ConcurrentLinkedQueue<GattAction> gattActions = new ConcurrentLinkedQueue<>(); private MetaWearState singleMwState = null; private MetaWearControllerImpl singleController = null; /** * Get the IntentFilter for actions broadcasted by the MetaWear service * @return IntentFilter for MetaWear specific actions * @see ModuleCallbacks * @see DeviceCallbacks */ public static IntentFilter getMetaWearIntentFilter() { IntentFilter filter = new IntentFilter(); filter.addAction(Action.NOTIFICATION_RECEIVED); filter.addAction(Action.CHARACTERISTIC_READ); filter.addAction(Action.DEVICE_CONNECTED); filter.addAction(Action.DEVICE_DISCONNECTED); filter.addAction(Action.RSSI_READ); filter.addAction(Action.GATT_ERROR); return filter; } private static BroadcastReceiver mwBroadcastReceiver = null; /** * Get the broadcast receiver for MetaWear intents. An Activity using the MetaWear service * will need to register this receiver to trigger its callback functions. * @return MetaWear specific broadcast receiver */ public static BroadcastReceiver getMetaWearBroadcastReceiver() { if (mwBroadcastReceiver == null) { mwBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getExtras().getParcelable(Extra.BLUETOOTH_DEVICE); if (device != null && metaWearStates.containsKey(device)) { MetaWearState mwState = metaWearStates.get(device); switch (intent.getAction()) { case Action.GATT_ERROR: int gattStatus = intent.getIntExtra(Extra.STATUS, -1); DeviceCallbacks.GattOperation gattOp = (GattOperation) intent.getExtras() .get(Extra.GATT_OPERATION); for (DeviceCallbacks it : mwState.deviceCallbacks) { it.receivedGattError(gattOp, gattStatus); } break; case Action.NOTIFICATION_RECEIVED: final byte[] data = (byte[]) intent.getExtras().get(Extra.CHARACTERISTIC_VALUE); if (data.length > 1) { byte moduleOpcode = data[0], registerOpcode = (byte) (0x7f & data[1]); Collection<ModuleCallbacks> callbacks; if (mwState.moduleCallbackMap.containsKey(moduleOpcode) && (callbacks = mwState.moduleCallbackMap.get(moduleOpcode)) != null) { Module.lookupModule(moduleOpcode).lookupRegister(registerOpcode) .notifyCallbacks(callbacks, data); } } break; case Action.DEVICE_CONNECTED: for (DeviceCallbacks it : mwState.deviceCallbacks) { it.connected(); } break; case Action.DEVICE_DISCONNECTED: for (DeviceCallbacks it : mwState.deviceCallbacks) { it.disconnected(); } if (!mwState.retainState) { mwState.resetState(); metaWearStates.remove(mwState.mwBoard); } break; case Action.CHARACTERISTIC_READ: UUID serviceUuid = (UUID) intent.getExtras().get(Extra.SERVICE_UUID), charUuid = (UUID) intent.getExtras().get(Extra.CHARACTERISTIC_UUID); GATTCharacteristic characteristic = GATTService.lookupGATTService(serviceUuid) .getCharacteristic(charUuid); for (DeviceCallbacks it : mwState.deviceCallbacks) { it.receivedGATTCharacteristic(characteristic, (byte[]) intent.getExtras().get(Extra.CHARACTERISTIC_VALUE)); } break; case Action.RSSI_READ: int rssi = intent.getExtras().getInt(Extra.RSSI); for (DeviceCallbacks it : mwState.deviceCallbacks) { it.receivedRemoteRSSI(rssi); } break; } } } }; } return mwBroadcastReceiver; } private void broadcastIntent(Intent intent) { if (useLocalBroadcastMnger) { LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } else { sendBroadcast(intent); } } private void execGattAction() { if (!gattActions.isEmpty()) { isExecGattActions.set(true); gattActions.poll().execAction(); } else { isExecGattActions.set(false); } } /** * Writes a command to MetaWear via the command register UUID * @see Characteristics.MetaWear#COMMAND */ private void writeCommand(final MetaWearState mwState) { gattActions.add(new GattAction() { @Override public void execAction() { byte[] next = mwState.commandBytes.poll(); if (next != null && mwState.mwGatt != null) { BluetoothGattService service = mwState.mwGatt.getService(GATTService.METAWEAR.uuid()); BluetoothGattCharacteristic command = service.getCharacteristic(MetaWear.COMMAND.uuid()); command.setValue(next); mwState.mwGatt.writeCharacteristic(command); } else { execGattAction(); } } }); } /** * Read a characteristic from MetaWear. * An intent with the action CHARACTERISTIC_READ will be broadcasted. * @see Action.BluetoothLe#ACTION_CHARACTERISTIC_READ */ private void readCharacteristic(final MetaWearState mwState) { gattActions.add(new GattAction() { @Override public void execAction() { GATTCharacteristic charInfo = mwState.readCharUuids.poll(); if (mwState.mwGatt != null) { BluetoothGattService service = mwState.mwGatt.getService(charInfo.gattService().uuid()); BluetoothGattCharacteristic characteristic = service.getCharacteristic(charInfo.uuid()); mwState.mwGatt.readCharacteristic(characteristic); } else { execGattAction(); } } }); } private abstract class MetaWearControllerImpl extends BluetoothGattCallback implements MetaWearController { private final MetaWearState mwState; private final HashMap<Module, ModuleController> modules = new HashMap<>(); public MetaWearControllerImpl(MetaWearState mwState) { this.mwState = mwState; } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { Intent intent; if (status != BluetoothGatt.GATT_SUCCESS) { intent = new Intent(Action.GATT_ERROR); intent.putExtra(Extra.STATUS, status); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.RSSI_READ); } else { intent = new Intent(Action.RSSI_READ); intent.putExtra(Extra.RSSI, rssi); } intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Intent intent = new Intent(); boolean broadcast = true; if (status != BluetoothGatt.GATT_SUCCESS) { intent.setAction(Action.GATT_ERROR); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.CONNECTION_STATE_CHANGE); intent.putExtra(Extra.STATUS, status); } else { switch (newState) { case BluetoothProfile.STATE_CONNECTED: gatt.discoverServices(); intent.setAction(Action.DEVICE_CONNECTED); mwState.connected = true; break; case BluetoothProfile.STATE_DISCONNECTED: intent.setAction(Action.DEVICE_DISCONNECTED); mwState.connected = false; break; default: broadcast = false; break; } } if (broadcast) { intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status != BluetoothGatt.GATT_SUCCESS) { Intent intent = new Intent(Action.GATT_ERROR); intent.putExtra(Extra.STATUS, status); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.DISCOVER_SERVICES); intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); } else { for (BluetoothGattService service : gatt.getServices()) { for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { int charProps = characteristic.getProperties(); if ((charProps & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { mwState.shouldNotify.add(characteristic); } } } mwState.deviceState = DeviceState.ENABLING_NOTIFICATIONS; setupNotification(mwState); } } private void setupNotification(final MetaWearState mwState) { gattActions.add(new GattAction() { @Override public void execAction() { mwState.mwGatt.setCharacteristicNotification(mwState.shouldNotify.peek(), true); BluetoothGattDescriptor descriptor = mwState.shouldNotify.poll() .getDescriptor(CHARACTERISTIC_CONFIG); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mwState.mwGatt.writeDescriptor(descriptor); } }); execGattAction(); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { isExecGattActions.set(!gattActions.isEmpty()); if (status != BluetoothGatt.GATT_SUCCESS) { Intent intent = new Intent(Action.GATT_ERROR); intent.putExtra(Extra.STATUS, status); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.DESCRIPTOR_WRITE); intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); } if (!mwState.shouldNotify.isEmpty()) setupNotification(mwState); else mwState.deviceState = DeviceState.READY; if (mwState.deviceState == DeviceState.READY) { mwState.internalCallbacks.put(Temperature.Register.THERMISTOR_MODE, new InternalCallback() { @Override public void process(byte[] data) { mwState.thermistorMode = data[2]; mwState.internalCallbacks.remove(Temperature.Register.THERMISTOR_MODE); } }); readRegister(mwState, Temperature.Register.THERMISTOR_MODE); if (!mwState.commandBytes.isEmpty()) { mwState.deviceState = DeviceState.WRITING_CHARACTERISTICS; writeCommand(mwState); } else if (!mwState.readCharUuids.isEmpty()) { mwState.deviceState = DeviceState.READING_CHARACTERISTICS; readCharacteristic(mwState); } else if (mwState.readyToClose) { close(mwState.notifyUser); } } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Intent intent; isExecGattActions.set(!gattActions.isEmpty()); if (status != BluetoothGatt.GATT_SUCCESS) { intent = new Intent(Action.GATT_ERROR); intent.putExtra(Extra.STATUS, status); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.CHARACTERISTIC_READ); } else { intent = new Intent(Action.CHARACTERISTIC_READ); intent.putExtra(Extra.SERVICE_UUID, characteristic.getService().getUuid()); intent.putExtra(Extra.CHARACTERISTIC_UUID, characteristic.getUuid()); intent.putExtra(Extra.CHARACTERISTIC_VALUE, characteristic.getValue()); } intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); if (!mwState.readCharUuids.isEmpty()) readCharacteristic(mwState); else mwState.deviceState = DeviceState.READY; if (mwState.deviceState == DeviceState.READY) { if (!mwState.commandBytes.isEmpty()) { mwState.deviceState = DeviceState.WRITING_CHARACTERISTICS; writeCommand(mwState); } else if (!mwState.readCharUuids.isEmpty()) { mwState.deviceState = DeviceState.READING_CHARACTERISTICS; readCharacteristic(mwState); } else if (mwState.readyToClose) { close(mwState.notifyUser); } } execGattAction(); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { isExecGattActions.set(!gattActions.isEmpty()); if (status != BluetoothGatt.GATT_SUCCESS) { Intent intent = new Intent(Action.GATT_ERROR); intent.putExtra(Extra.STATUS, status); intent.putExtra(Extra.GATT_OPERATION, DeviceCallbacks.GattOperation.CHARACTERISTIC_WRITE); intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); } if (!mwState.commandBytes.isEmpty()) writeCommand(mwState); else mwState.deviceState = DeviceState.READY; if (mwState.deviceState == DeviceState.READY) { if (!mwState.readCharUuids.isEmpty()) { mwState.deviceState = DeviceState.READING_CHARACTERISTICS; readCharacteristic(mwState); } else if (mwState.readyToClose) { close(mwState.notifyUser); } } execGattAction(); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (characteristic.getValue().length > 1) { Register mwRegister = Module.lookupModule(characteristic.getValue()[0]) .lookupRegister(characteristic.getValue()[1]); byte[] bleData; if (mwRegister == Temperature.Register.TEMPERATURE) { bleData = Arrays.copyOf(characteristic.getValue(), characteristic.getValue().length + 1); bleData[bleData.length - 1] = mwState.thermistorMode; } else { bleData = characteristic.getValue(); } Intent intent = new Intent(Action.NOTIFICATION_RECEIVED); intent.putExtra(Extra.CHARACTERISTIC_VALUE, bleData); intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); broadcastIntent(intent); if (mwState.internalCallbacks.containsKey(mwRegister)) { mwState.internalCallbacks.get(mwRegister).process(bleData); } } } @Override public MetaWearController addModuleCallback(ModuleCallbacks callback) { byte moduleOpcode = callback.getModule().opcode; if (!mwState.moduleCallbackMap.containsKey(moduleOpcode)) { mwState.moduleCallbackMap.put(moduleOpcode, new ArrayList<ModuleCallbacks>()); } mwState.moduleCallbackMap.get(moduleOpcode).add(callback); return this; } @Override public void removeModuleCallback(ModuleCallbacks callback) { byte callbackOpcode = callback.getModule().opcode; if (mwState.moduleCallbackMap.containsKey(callbackOpcode)) { mwState.moduleCallbackMap.get(callbackOpcode).remove(callback); } } @Override public MetaWearController addDeviceCallback(DeviceCallbacks callback) { mwState.deviceCallbacks.add(callback); return this; } @Override public void removeDeviceCallback(DeviceCallbacks callback) { mwState.deviceCallbacks.remove(callback); } @Override public void clearCallbacks() { mwState.moduleCallbackMap.clear(); mwState.deviceCallbacks.clear(); } @Override public ModuleController getModuleController(Module module) { switch (module) { case ACCELEROMETER: return getAccelerometerModule(); case DEBUG: return getDebugModule(); case GPIO: return getGPIOModule(); case IBEACON: return getIBeaconModule(); case LED: return getLEDDriverModule(); case MECHANICAL_SWITCH: return getMechanicalSwitchModule(); case NEO_PIXEL: return getNeoPixelDriver(); case TEMPERATURE: return getTemperatureModule(); case HAPTIC: return getHapticModule(); case EVENT: return getEventModule(); case LOGGING: return getLoggingModule(); case DATA_PROCESSOR: return getDataProcessorModule(); case TIMER: return getTimerModule(); } return null; } private ModuleController getTimerModule() { if (!modules.containsKey(Module.TIMER)) { modules.put(Module.TIMER, new Timer() { @Override public void addTimer(int period, short repeat, boolean delay) { ByteBuffer buffer = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(period).putShort(repeat).put((byte) (delay ? 1 : 0)); writeRegister(mwState, Register.TIMER_ENTRY, buffer.array()); } @Override public void startTimer(byte timerId) { writeRegister(mwState, Register.START, timerId); } @Override public void stopTimer(byte timerId) { writeRegister(mwState, Register.STOP, timerId); } @Override public void removeTimer(byte timerId) { writeRegister(mwState, Register.REMOVE, timerId); } @Override public void enableNotification(byte timerId) { writeRegister(mwState, Register.TIMER_NOTIFY, (byte) 1); writeRegister(mwState, Register.TIMER_NOTIFY_ENABLE, timerId, (byte) 1); } @Override public void disableNotification(byte timerId) { writeRegister(mwState, Register.TIMER_NOTIFY_ENABLE, timerId, (byte) 0); } }); } return modules.get(Module.TIMER); } private ModuleController getDataProcessorModule() { if (!modules.containsKey(Module.DATA_PROCESSOR)) { modules.put(Module.DATA_PROCESSOR, new DataProcessor() { @Override public void chainFilters(byte srcFilterId, byte srcSize, FilterConfig config) { byte[] attributes = new byte[config.bytes().length + 5]; attributes[0] = Register.FILTER_NOTIFICATION.module().opcode; attributes[1] = Register.FILTER_NOTIFICATION.opcode(); attributes[2] = srcFilterId; attributes[3] = (byte) ((srcSize - 1) << 5); attributes[4] = (byte) (config.type().ordinal() + 1); System.arraycopy(config.bytes(), 0, attributes, 5, config.bytes().length); writeRegister(mwState, Register.FILTER_CREATE, attributes); } @Override public void addFilter(Trigger trigger, FilterConfig config) { byte[] attributes = new byte[config.bytes().length + 5]; attributes[0] = trigger.register().module().opcode; attributes[1] = trigger.register().opcode(); attributes[2] = trigger.index(); attributes[3] = (byte) (trigger.offset() | ((trigger.length() - 1) << 5)); attributes[4] = (byte) (config.type().ordinal() + 1); System.arraycopy(config.bytes(), 0, attributes, 5, config.bytes().length); writeRegister(mwState, Register.FILTER_CREATE, attributes); } @Override public void setFilterConfiguration(byte filterId, FilterConfig config) { byte[] bleData = new byte[config.bytes().length + 2]; bleData[0] = filterId; bleData[1] = (byte) (config.type().ordinal() + 1); System.arraycopy(config.bytes(), 0, bleData, 2, config.bytes().length); writeRegister(mwState, Register.FILTER_CONFIGURATION, bleData); } @Override public void resetFilterState(byte filterId) { writeRegister(mwState, Register.FILTER_STATE, filterId); } @Override public void removeFilter(byte filterId) { writeRegister(mwState, Register.FILTER_REMOVE, filterId); } @Override public void enableFilterNotify(byte filterId) { writeRegister(mwState, Register.FILTER_NOTIFICATION, (byte) 1); writeRegister(mwState, Register.FILTER_NOTIFY_ENABLE, filterId, (byte) 1); } @Override public void disableFilterNotify(byte filterId) { writeRegister(mwState, Register.FILTER_NOTIFY_ENABLE, filterId, (byte) 0); } @Override public void enableModule() { writeRegister(mwState, Register.ENABLE, (byte) 1); } @Override public void disableModule() { writeRegister(mwState, Register.ENABLE, (byte) 0); } @Override public void filterIdToObject(byte filterId) { readRegister(mwState, Register.FILTER_CREATE, (byte) 1); } @Override public void removeAllFilters() { for (byte filterId = 0; filterId < 16; filterId++) { writeRegister(mwState, Register.FILTER_REMOVE, filterId); } } }); } return modules.get(Module.DATA_PROCESSOR); } private ModuleController getEventModule() { if (!modules.containsKey(Module.EVENT)) { modules.put(Module.EVENT, new Event() { @Override public void recordMacro(com.mbientlab.metawear.api.Register srcReg) { recordMacro(srcReg, (byte) -1); } @Override public void recordMacro(com.mbientlab.metawear.api.Register srcReg, byte index) { mwState.isRecording = true; mwState.etBuilder = new EventTriggerBuilder(srcReg, index); } @Override public byte stopRecord() { mwState.isRecording = false; for (EventInfo info : mwState.etBuilder.getEventInfo()) { writeRegister(mwState, Register.ADD_ENTRY, info.entry()); writeRegister(mwState, Register.EVENT_COMMAND, info.command()); } return (byte) mwState.etBuilder.getEventInfo().size(); } @Override public void removeMacros() { for (byte commandId = 0; commandId < 8; commandId++) { writeRegister(mwState, Register.REMOVE_ENTRY, commandId); } } @Override public void commandIdToObject(byte commandId) { readRegister(mwState, Register.ADD_ENTRY, (byte) 1); } @Override public void readCommandBytes(byte commandId) { readRegister(mwState, Register.EVENT_COMMAND, (byte) 1); } @Override public void enableModule() { writeRegister(mwState, Event.Register.EVENT_ENABLE, (byte) 1); } @Override public void disableModule() { writeRegister(mwState, Event.Register.EVENT_ENABLE, (byte) 0); } @Override public void removeCommand(byte commandId) { writeRegister(mwState, Register.REMOVE_ENTRY, commandId); } }); } return modules.get(Module.EVENT); } private ModuleController getLoggingModule() { if (!modules.containsKey(Module.LOGGING)) { modules.put(Module.LOGGING, new Logging() { @Override public void startLogging() { writeRegister(mwState, Register.ENABLE, (byte) 1); } @Override public void stopLogging() { writeRegister(mwState, Register.ENABLE, (byte) 0); } @Override public void addTrigger(Trigger triggerObj) { writeRegister(mwState, Register.ADD_TRIGGER, triggerObj.register().module().opcode, triggerObj.register().opcode(), triggerObj.index(), (byte) (triggerObj.offset() | ((triggerObj.length() - 1) << 5))); } @Override public void triggerIdToObject(byte triggerId) { readRegister(mwState, Register.ADD_TRIGGER, triggerId); } @Override public void removeTrigger(byte triggerId) { writeRegister(mwState, Register.REMOVE_TRIGGER, triggerId); } @Override public void readReferenceTick() { readRegister(mwState, Register.TIME, (byte) 0); } @Override public void readTotalEntryCount() { readRegister(mwState, Register.LENGTH, (byte) 0); } @Override public void downloadLog(int nEntries, int notifyIncrement) { ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) (nEntries & 0xffff)).putShort((short) (notifyIncrement & 0xffff)); writeRegister(mwState, Register.READOUT_NOTIFY, (byte) 1); writeRegister(mwState, Register.READOUT_PROGRESS, (byte) 1); writeRegister(mwState, Register.READOUT, buffer.array()); } @Override public void removeAllTriggers() { for (byte triggerId = 0; triggerId < 8; triggerId++) { writeRegister(mwState, Register.REMOVE_TRIGGER, triggerId); } } }); } return modules.get(Module.LOGGING); } private ModuleController getAccelerometerModule() { if (!modules.containsKey(Module.ACCELEROMETER)) { modules.put(Module.ACCELEROMETER, new Accelerometer() { private static final float MMA8452Q_G_PER_STEP = (float) 0.063; private final HashSet<Component> activeComponents = new HashSet<>(); private final HashSet<Component> activeNotifications = new HashSet<>(); private final HashMap<Component, byte[]> configurations = new HashMap<>(); private OutputDataRate accelOdr = OutputDataRate.ODR_100_HZ;; private byte[] globalConfig = new byte[] { 0, 0, 0x18, 0, 0 }; @Override public void enableComponent(Component component, boolean notify) { if (notify) { enableNotification(component); } else { writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 0); writeRegister(mwState, component.enable, (byte) 1); writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 1); } } @Override public void disableComponent(Component component) { disableNotification(component); } @Override public void enableNotification(Component component) { writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 0); writeRegister(mwState, component.enable, (byte) 1); writeRegister(mwState, component.status, (byte) 1); writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 1); } @Override public void disableNotification(Component component) { writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 0); writeRegister(mwState, component.enable, (byte) 0); writeRegister(mwState, component.status, (byte) 0); writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 1); } @Override public void readComponentConfiguration(Component component) { readRegister(mwState, component.config, (byte) 0); } @Override public void setComponentConfiguration(Component component, byte[] data) { writeRegister(mwState, component.config, data); } @Override public void disableDetection(Component component, boolean saveConfig) { activeComponents.remove(component); activeNotifications.remove(component); if (!saveConfig) { if (component == Component.DATA) { globalConfig = new byte[] { 0, 0, 0x18, 0, 0 }; } else { configurations.remove(component); } } } @Override public void disableAllDetection(boolean saveConfig) { activeComponents.clear(); activeNotifications.clear(); if (!saveConfig) { configurations.clear(); globalConfig = new byte[] { 0, 0, 0x18, 0, 0 }; } } @Override public ThresholdConfig enableTapDetection(TapType type, Axis axis) { byte[] tapConfig; if (!configurations.containsKey(Component.PULSE)) { tapConfig = new byte[] { 0x40, 0, 0x40, 0x40, 0x50, 0x18, 0x14, 0x3c }; configurations.put(Component.PULSE, tapConfig); } else { tapConfig = configurations.get(Component.PULSE); tapConfig[0] &= 0xc0; } switch (type) { case SINGLE_TAP: tapConfig[0] |= 1 << (2 * axis.ordinal()); break; case DOUBLE_TAP: tapConfig[0] |= 1 << (1 + 2 * axis.ordinal()); break; } configurations.put(Component.PULSE, tapConfig); activeComponents.add(Component.PULSE); activeNotifications.add(Component.PULSE); return new ThresholdConfig() { @Override public ThresholdConfig withThreshold(float gravity) { byte nSteps = (byte) (gravity / MMA8452Q_G_PER_STEP); byte[] config = configurations.get(Component.PULSE); config[2] |= nSteps; config[3] |= nSteps; config[4] |= nSteps; return this; } @Override public AccelerometerConfig withSilentMode() { activeNotifications.remove(Component.PULSE); return this; } @Override public byte[] getBytes() { return configurations.get(Component.PULSE); } }; } @Override public ThresholdConfig enableShakeDetection(Axis axis) { byte[] shakeConfig; if (!configurations.containsKey(Component.TRANSIENT)) { shakeConfig = new byte[] { 0x10, 0, 0x8, 0x5 }; configurations.put(Component.TRANSIENT, shakeConfig); } else { shakeConfig = configurations.get(Component.TRANSIENT); shakeConfig[0] &= 0xf1; } shakeConfig[0] |= 2 << axis.ordinal(); activeComponents.add(Component.TRANSIENT); activeNotifications.add(Component.TRANSIENT); return new ThresholdConfig() { @Override public ThresholdConfig withThreshold(float gravity) { configurations.get(Component.TRANSIENT)[2] = (byte) (gravity / MMA8452Q_G_PER_STEP); return this; } @Override public AccelerometerConfig withSilentMode() { activeNotifications.remove(Component.TRANSIENT); return this; } @Override public byte[] getBytes() { return configurations.get(Component.TRANSIENT); } }; } @Override public AccelerometerConfig enableOrientationDetection() { if (!configurations.containsKey(Component.ORIENTATION)) { configurations.put(Component.ORIENTATION, new byte[] { 0, (byte) 0xc0, 0xa, 0, 0 }); } activeComponents.add(Component.ORIENTATION); activeNotifications.add(Component.ORIENTATION); return new AccelerometerConfig() { @Override public AccelerometerConfig withSilentMode() { activeNotifications.remove(Component.ORIENTATION); return this; } @Override public byte[] getBytes() { return configurations.get(Component.ORIENTATION); } }; } class FF_Motion_Config implements ThresholdConfig { @Override public ThresholdConfig withThreshold(float gravity) { configurations.get(Component.FREE_FALL)[2] = (byte) (gravity / MMA8452Q_G_PER_STEP); return this; } @Override public AccelerometerConfig withSilentMode() { activeNotifications.remove(Component.FREE_FALL); return this; } @Override public byte[] getBytes() { return configurations.get(Component.FREE_FALL); } } @Override public ThresholdConfig enableFreeFallDetection() { if (!configurations.containsKey(Component.FREE_FALL)) { configurations.put(Component.FREE_FALL, new byte[] { (byte) 0xb8, 0, 0x3, 0xa }); } else { byte[] ffConfig = configurations.get(Component.FREE_FALL); ffConfig[0] = (byte) 0xb8; ffConfig[2] = 0x3; } activeComponents.add(Component.FREE_FALL); activeNotifications.add(Component.FREE_FALL); return new FF_Motion_Config(); } public ThresholdConfig enableMotionDetection(Axis... axes) { byte[] motionConfig; if (!configurations.containsKey(Component.FREE_FALL)) { motionConfig = new byte[] { (byte) 0xc0, 0, 0x20, 0xa }; configurations.put(Component.FREE_FALL, motionConfig); } else { motionConfig = configurations.get(Component.FREE_FALL); motionConfig[0] = (byte) 0xc0; motionConfig[2] = 0x20; } byte mask = 0; for (Axis axis : axes) { mask |= 1 << (axis.ordinal() + 3); } motionConfig[0] |= mask; activeComponents.add(Component.FREE_FALL); activeNotifications.add(Component.FREE_FALL); return new FF_Motion_Config(); } public void startComponents() { float multiplier = (float) Math.pow(2, accelOdr.ordinal() - OutputDataRate.ODR_100_HZ.ordinal()); for (Component active : activeComponents) { if (configurations.containsKey(active)) { byte[] config = configurations.get(active); if (accelOdr != OutputDataRate.ODR_100_HZ) { switch (active) { case FREE_FALL: config[3] = (byte) (Math.max(100 / (10 * multiplier), 20)); break; case ORIENTATION: config[2] = (byte) (Math.max(100 / (10 * multiplier), 20)); break; case PULSE: config[5] = (byte) (Math.min(Math.max(60 / (2.5 * multiplier), 5), 0.625)); config[6] = (byte) (Math.min(Math.max(200 / (5 * multiplier), 10), 1.25)); config[7] = (byte) (Math.min(Math.max(300 / (5 * multiplier), 10), 1.25)); break; case TRANSIENT: config[3] = (byte) (Math.max(50 / (10 * multiplier), 20)); break; default: break; } } setComponentConfiguration(active, config); } writeRegister(mwState, active.enable, (byte) 1); if (activeNotifications.contains(active)) { writeRegister(mwState, active.status, (byte) 1); } } setComponentConfiguration(Component.DATA, globalConfig); writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 1); } public void stopComponents() { writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 0); for (Component active : activeComponents) { writeRegister(mwState, active.enable, (byte) 0); writeRegister(mwState, active.status, (byte) 0); } } public void resetAll() { disableAllDetection(false); writeRegister(mwState, Register.GLOBAL_ENABLE, (byte) 0); for (Component it : Component.values()) { writeRegister(mwState, it.enable, (byte) 0); writeRegister(mwState, it.status, (byte) 0); } } @Override public SamplingConfig enableXYZSampling() { activeComponents.add(Component.DATA); activeNotifications.add(Component.DATA); return new SamplingConfig() { @Override public SamplingConfig withFullScaleRange(FullScaleRange range) { globalConfig[0] &= 0xfc; globalConfig[0] |= range.ordinal(); return this; } @Override public AccelerometerConfig withSilentMode() { activeNotifications.remove(Component.DATA); return this; } @Override public SamplingConfig withOutputDataRate(OutputDataRate rate) { globalConfig[2] &= 0xc7; globalConfig[2] |= (rate.ordinal() << 3); accelOdr = rate; return this; } @Override public byte[] getBytes() { return globalConfig; } @Override public SamplingConfig withHighPassFilter(byte cutoff) { globalConfig[0] |= 0x10; globalConfig[1] |= cutoff; return this; } @Override public SamplingConfig withHighPassFilter() { globalConfig[0] |= 0x10; return this; } @Override public SamplingConfig withoutHighPassFilter() { globalConfig[0] &= 0xef; return this; } }; } }); } return modules.get(Module.ACCELEROMETER); } private ModuleController getDebugModule() { if (!modules.containsKey(Module.DEBUG)) { modules.put(Module.DEBUG, new Debug() { @Override public void resetDevice() { writeRegister(mwState, Register.RESET_DEVICE); } @Override public void jumpToBootloader() { writeRegister(mwState, Register.JUMP_TO_BOOTLOADER); } }); } return modules.get(Module.DEBUG); } private ModuleController getGPIOModule() { if (!modules.containsKey(Module.GPIO)) { modules.put(Module.GPIO, new GPIO() { @Override public void readAnalogInput(byte pin, AnalogMode mode) { readRegister(mwState, mode.register, pin); } @Override public void readDigitalInput(byte pin) { readRegister(mwState, Register.READ_DIGITAL_INPUT, pin); } @Override public void setDigitalOutput(byte pin) { writeRegister(mwState, Register.SET_DIGITAL_OUTPUT, pin); } @Override public void clearDigitalOutput(byte pin) { writeRegister(mwState, Register.CLEAR_DIGITAL_OUTPUT, pin); } @Override public void setDigitalInput(byte pin, PullMode mode) { writeRegister(mwState, mode.register, pin); } @Override public void setPinChangeType(byte pin, ChangeType type) { writeRegister(mwState, Register.SET_PIN_CHANGE, pin, (byte) type.ordinal()); } @Override public void enablePinChangeNotification(byte pin) { writeRegister(mwState, Register.PIN_CHANGE_NOTIFY, (byte) 1); writeRegister(mwState, Register.PIN_CHANGE_NOTIFY_ENABLE, pin, (byte) 1); } @Override public void disablePinChangeNotification(byte pin) { writeRegister(mwState, Register.PIN_CHANGE_NOTIFY_ENABLE, pin, (byte) 0); } }); } return modules.get(Module.GPIO); } private ModuleController getIBeaconModule() { if (!modules.containsKey(Module.IBEACON)) { modules.put(Module.IBEACON, new IBeacon() { @Override public void enableIBeacon() { writeRegister(mwState, Register.ENABLE, (byte) 1); } @Override public void disableIBecon() { writeRegister(mwState, Register.ENABLE, (byte) 0); } @Override public IBeacon setUUID(UUID uuid) { byte[] uuidBytes = ByteBuffer.wrap(new byte[16]).order(ByteOrder.LITTLE_ENDIAN) .putLong(uuid.getLeastSignificantBits()).putLong(uuid.getMostSignificantBits()) .array(); writeRegister(mwState, Register.ADVERTISEMENT_UUID, uuidBytes); return this; } @Override public void readSetting(Register register) { readRegister(mwState, register); } @Override public IBeacon setMajor(short major) { writeRegister(mwState, Register.MAJOR, (byte) (major & 0xff), (byte) ((major >> 8) & 0xff)); return this; } @Override public IBeacon setMinor(short minor) { writeRegister(mwState, Register.MINOR, (byte) (minor & 0xff), (byte) ((minor >> 8) & 0xff)); return this; } @Override public IBeacon setCalibratedRXPower(byte power) { writeRegister(mwState, Register.RX_POWER, power); return this; } @Override public IBeacon setTXPower(byte power) { writeRegister(mwState, Register.TX_POWER, power); return this; } @Override public IBeacon setAdvertisingPeriod(short freq) { writeRegister(mwState, Register.ADVERTISEMENT_PERIOD, (byte) (freq & 0xff), (byte) ((freq >> 8) & 0xff)); return this; } }); } return modules.get(Module.IBEACON); } private ModuleController getLEDDriverModule() { if (!modules.containsKey(Module.LED)) { modules.put(Module.LED, new LED() { public void play(boolean autoplay) { writeRegister(mwState, Register.PLAY, (byte) (autoplay ? 2 : 1)); } public void pause() { writeRegister(mwState, Register.PLAY, (byte) 0); } public void stop(boolean resetChannels) { writeRegister(mwState, Register.STOP, (byte) (resetChannels ? 1 : 0)); } public ChannelDataWriter setColorChannel(final ColorChannel color) { return new ChannelDataWriter() { private byte[] channelData = new byte[15]; @Override public ColorChannel getChannel() { return color; } @Override public ChannelDataWriter withHighIntensity(byte intensity) { channelData[2] = intensity; return this; } @Override public ChannelDataWriter withLowIntensity(byte intensity) { channelData[3] = intensity; return this; } @Override public ChannelDataWriter withRiseTime(short time) { channelData[5] = (byte) ((time >> 8) & 0xff); channelData[4] = (byte) (time & 0xff); return this; } @Override public ChannelDataWriter withHighTime(short time) { channelData[7] = (byte) ((time >> 8) & 0xff); channelData[6] = (byte) (time & 0xff); return this; } @Override public ChannelDataWriter withFallTime(short time) { channelData[9] = (byte) ((time >> 8) & 0xff); channelData[8] = (byte) (time & 0xff); return this; } @Override public ChannelDataWriter withPulseDuration(short period) { channelData[11] = (byte) ((period >> 8) & 0xff); channelData[10] = (byte) (period & 0xff); return this; } @Override public ChannelDataWriter withPulseOffset(short offset) { channelData[13] = (byte) ((offset >> 8) & 0xff); channelData[12] = (byte) (offset & 0xff); return this; } @Override public ChannelDataWriter withRepeatCount(byte count) { channelData[14] = count; ; return this; } @Override public void commit() { channelData[0] = (byte) (color.ordinal()); channelData[1] = 0x2; ///< Keep it set to flash for now writeRegister(mwState, Register.MODE, channelData); } }; } }); } return modules.get(Module.LED); } private ModuleController getMechanicalSwitchModule() { if (!modules.containsKey(Module.MECHANICAL_SWITCH)) { modules.put(Module.MECHANICAL_SWITCH, new MechanicalSwitch() { @Override public void enableNotification() { writeRegister(mwState, Register.SWITCH_STATE, (byte) 1); } @Override public void disableNotification() { writeRegister(mwState, Register.SWITCH_STATE, (byte) 0); } }); } return modules.get(Module.MECHANICAL_SWITCH); } private ModuleController getNeoPixelDriver() { if (!modules.containsKey(Module.NEO_PIXEL)) { modules.put(Module.NEO_PIXEL, new NeoPixel() { @Override public void readStrandState(byte strand) { readRegister(mwState, Register.INITIALIZE, strand); } @Override public void readHoldState(byte strand) { readRegister(mwState, Register.HOLD, strand); } @Override public void readPixelState(byte strand, byte pixel) { readRegister(mwState, Register.PIXEL, strand, pixel); } @Override public void readRotationState(byte strand) { readRegister(mwState, Register.ROTATE, strand); } @Override public void initializeStrand(byte strand, ColorOrdering ordering, StrandSpeed speed, byte ioPin, byte length) { writeRegister(mwState, Register.INITIALIZE, strand, (byte) (speed.ordinal() << 2 | ordering.ordinal()), ioPin, length); } @Override public void holdStrand(byte strand, byte holdState) { writeRegister(mwState, Register.HOLD, strand, holdState); } @Override public void clearStrand(byte strand, byte start, byte end) { writeRegister(mwState, Register.CLEAR, strand, start, end); } @Override public void setPixel(byte strand, byte pixel, byte red, byte green, byte blue) { writeRegister(mwState, Register.PIXEL, strand, pixel, red, green, blue); } @Override public void rotateStrand(byte strand, RotationDirection direction, byte repetitions, short delay) { writeRegister(mwState, Register.ROTATE, strand, (byte) direction.ordinal(), repetitions, (byte) (delay & 0xff), (byte) (delay >> 8 & 0xff)); } @Override public void deinitializeStrand(byte strand) { writeRegister(mwState, Register.DEINITIALIZE, strand); } }); } return modules.get(Module.NEO_PIXEL); } private ModuleController getTemperatureModule() { if (!modules.containsKey(Module.TEMPERATURE)) { modules.put(Module.TEMPERATURE, new Temperature() { @Override public void readTemperature() { readRegister(mwState, Register.TEMPERATURE, (byte) 0); } @Override public SamplingConfigBuilder enableSampling() { return new SamplingConfigBuilder() { private final byte[] samplingConfig = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; private boolean silent = false; @Override public SamplingConfigBuilder withSilentMode() { silent = true; return this; } @Override public SamplingConfigBuilder withSampingPeriod(int period) { short shortPeriod = (short) (period & 0xffff); samplingConfig[1] = (byte) ((shortPeriod >> 8) & 0xff); samplingConfig[0] = (byte) (shortPeriod & 0xff); return this; } @Override public SamplingConfigBuilder withTemperatureDelta(float delta) { short tempTicks = (short) (delta * 4); samplingConfig[3] = (byte) ((tempTicks >> 8) & 0xff); samplingConfig[2] = (byte) (tempTicks & 0xff); return this; } @Override public SamplingConfigBuilder withTemperatureBoundary(float lower, float upper) { short lowerTicks = (short) (lower * 4), upperTicks = (short) (upper * 4); samplingConfig[5] = (byte) ((lowerTicks >> 8) & 0xff); samplingConfig[4] = (byte) (lowerTicks & 0xff); samplingConfig[7] = (byte) ((upperTicks >> 8) & 0xff); samplingConfig[6] = (byte) (upperTicks & 0xff); return this; } @Override public void commit() { writeRegister(mwState, Register.MODE, samplingConfig); if (!silent) { writeRegister(mwState, Register.TEMPERATURE, (byte) 1); writeRegister(mwState, Register.DELTA_TEMP, (byte) 1); writeRegister(mwState, Register.THRESHOLD_DETECT, (byte) 1); } } }; } @Override public void disableSampling() { writeRegister(mwState, Register.MODE, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); writeRegister(mwState, Register.TEMPERATURE, (byte) 0); writeRegister(mwState, Register.DELTA_TEMP, (byte) 0); writeRegister(mwState, Register.THRESHOLD_DETECT, (byte) 0); } @Override public void enableThermistorMode(byte analogReadPin, byte pulldownPin) { mwState.thermistorMode = (byte) 1; writeRegister(mwState, Register.THERMISTOR_MODE, (byte) 1, analogReadPin, pulldownPin); } @Override public void disableThermistorMode() { mwState.thermistorMode = (byte) 0; writeRegister(mwState, Register.THERMISTOR_MODE, (byte) 0); } }); } return modules.get(Module.TEMPERATURE); } private ModuleController getHapticModule() { if (!modules.containsKey(Module.HAPTIC)) { modules.put(Module.HAPTIC, new Haptic() { private final static float DEFAULT_DUTY_CYCLE = 100.f; @Override public void startMotor(short pulseWidth) { startMotor(DEFAULT_DUTY_CYCLE, pulseWidth); } @Override public void startBuzzer(short pulseWidth) { writeRegister(mwState, Register.PULSE, (byte) 127, (byte) (pulseWidth & 0xff), (byte) ((pulseWidth >> 8) & 0xff), (byte) 1); } @Override public void startMotor(float dutyCycle, short pulseWidth) { short converted = (short) ((dutyCycle / 100.f) * 248); writeRegister(mwState, Register.PULSE, (byte) (converted & 0xff), (byte) (pulseWidth & 0xff), (byte) ((pulseWidth >> 8) & 0xff), (byte) 0); } }); } return modules.get(Module.HAPTIC); } @Override public void readDeviceInformation() { for (GATTCharacteristic it : DeviceInformation.values()) { mwState.readCharUuids.add(it); } if (mwState.deviceState == DeviceState.READY) { mwState.deviceState = DeviceState.READING_CHARACTERISTICS; if (!isExecGattActions.get()) { readCharacteristic(mwState); execGattAction(); } else { readCharacteristic(mwState); } } } @Override public void readBatteryLevel() { mwState.readCharUuids.add(Battery.BATTERY_LEVEL); if (mwState.deviceState == DeviceState.READY) { mwState.deviceState = DeviceState.READING_CHARACTERISTICS; if (!isExecGattActions.get()) { readCharacteristic(mwState); execGattAction(); } else { readCharacteristic(mwState); } } } @Override public void readRemoteRSSI() { if (mwState.mwGatt != null) { mwState.mwGatt.readRemoteRssi(); } } @Override public boolean isConnected() { return mwState.connected; } @Override public void setRetainState(boolean retain) { mwState.retainState = retain; } }; /** * Gets a controller for the MetaWear board, in single MetaWear mode. The MetaWearController returned * does not support the {@link MetaWearController#connect()}, * {@link MetaWearController#close(boolean)}, and {@link MetaWearController#reconnect(boolean)} functions. * You will need to use the deprecated variants of those functions to modify the connection state. * @return MetaWear controller, in single MetaWear mode, to interact with the board * @deprecated As of v1.3. Use {@link #getMetaWearController(BluetoothDevice)} instead */ @Deprecated public MetaWearController getMetaWearController() { if (singleMwState == null) { singleMwState = new MetaWearState(); } if (singleController == null) { singleController = new MetaWearControllerImpl(singleMwState) { @Override public void connect() { throw new UnsupportedOperationException( "This function is not supported in single metawear mode"); } @Override public void reconnect(boolean notify) { throw new UnsupportedOperationException( "This function is not supported in single metawear mode"); } @Override public void close(boolean notify) { throw new UnsupportedOperationException( "This function is not supported in single metawear mode"); } @Override public void close(boolean notify, boolean wait) { throw new UnsupportedOperationException( "This function is not supported in single metawear mode"); } }; } return singleController; } /** * Gets a controller for a specific MetaWear board. Modifying connection state must be done with * the {@link MetaWearController#connect()}, {@link MetaWearController#close(boolean)}, and * {@link MetaWearController#reconnect(boolean)} functions rather * than their deprecated variants * @param mwBoard MetaWear board to interact with * @return Controller attached to the specific board */ public MetaWearController getMetaWearController(final BluetoothDevice mwBoard) { if (!metaWearStates.containsKey(mwBoard)) { metaWearStates.put(mwBoard, new MetaWearState(mwBoard)); } final MetaWearState mwState = metaWearStates.get(mwBoard); if (mwState.mwController == null) { mwState.mwController = new MetaWearControllerImpl(metaWearStates.get(mwBoard)) { @Override public void connect() { if (!isConnected()) { if (!metaWearStates.containsKey(mwBoard)) { metaWearStates.put(mwBoard, mwState); } MetaWearBleService.this.close(mwState); mwState.notifyUser = false; mwState.readyToClose = false; mwState.deviceState = null; mwState.mwGatt = mwState.mwBoard.connectGatt(MetaWearBleService.this, false, this); } } @Override public void reconnect(boolean notify) { close(notify); connect(); } @Override public void close(boolean notify) { MetaWearBleService.this.close(mwState); if (notify) { Intent intent = new Intent(Action.DEVICE_DISCONNECTED); intent.putExtra(Extra.BLUETOOTH_DEVICE, mwState.mwBoard); intent.putExtra(Extra.EXPLICIT_CLOSE, true); broadcastIntent(intent); } else { if (!mwState.retainState) { mwState.resetState(); metaWearStates.remove(mwState.mwBoard); } } } @Override public void close(boolean notify, boolean wait) { if (wait) { mwState.notifyUser = notify; mwState.readyToClose = true; } else { close(notify); } } }; } return mwState.mwController; } /** * Set how intents are broadcasted from the service. Default behaviour is to broadcast * to all receivers * @param useFlag True if {@link android.support.v4.content.LocalBroadcastManager} should * be used to broadcast intents */ public void useLocalBroadcasterManager(boolean useFlag) { useLocalBroadcastMnger = useFlag; } /** * Connect to the GATT service on the MetaWear device. This version of the function is for the old * single MetaWear mode. * @param metaWearBoard MetaWear board to connect to * @deprecated As of v1.3. Use {@link MetaWearController#connect()} and retrieve a MetaWearController * with {@link #getMetaWearController(BluetoothDevice)} */ @Deprecated public void connect(BluetoothDevice metaWearBoard) { if (singleMwState == null) { singleMwState = new MetaWearState(metaWearBoard); } else { singleMwState.mwBoard = metaWearBoard; } if (singleMwState.mwController == null) { singleMwState.mwController = (MetaWearControllerImpl) getMetaWearController(); } if (!metaWearStates.containsKey(metaWearBoard)) { close(singleMwState); metaWearStates.clear(); metaWearStates.put(metaWearBoard, singleMwState); singleMwState.mwGatt = metaWearBoard.connectGatt(this, false, singleMwState.mwController); } else { reconnect(); } } /** * Restarts the connection to a board. This version of the function is for the old * single MetaWear mode. * @deprecated As of v1.3. Use {@link MetaWearController#reconnect(boolean)} and retrieve a MetaWearController * with {@link #getMetaWearController(BluetoothDevice)} */ @Deprecated public void reconnect() { if (singleMwState != null && singleMwState.mwBoard != null) { if (singleMwState.mwGatt != null) { singleMwState.mwGatt.close(); singleMwState.mwGatt = null; } singleMwState.connected = false; singleMwState.deviceState = null; singleMwState.mwGatt = singleMwState.mwBoard.connectGatt(this, false, singleMwState.mwController); } } /** * Disconnects from the board. This version of the function is for the old * single MetaWear mode. * @deprecated As of v1.3. Use {@link MetaWearController#close(boolean)} and * retrieve a MetaWearController with {@link #getMetaWearController(BluetoothDevice)} */ @Deprecated public void disconnect() { if (singleMwState != null && singleMwState.mwGatt != null) { singleMwState.mwGatt.disconnect(); } } /** * Close the GATT service and free up resources. This version of the function is for * single MetaWear mode. * @param notify True if the {@link MetaWearController.DeviceCallbacks#disconnected()} * function should be called * @deprecated As of v1.3. Use {@link MetaWearController#close(boolean)} and retrieve * a MetaWearController with {@link #getMetaWearController(BluetoothDevice)} */ @Deprecated public void close(boolean notify) { if (singleMwState != null && singleMwState.mwGatt != null) { singleMwState.mwGatt.close(); singleMwState.mwGatt = null; singleMwState.connected = false; if (notify) { Intent intent = new Intent(Action.DEVICE_DISCONNECTED); intent.putExtra(Extra.BLUETOOTH_DEVICE, singleMwState.mwBoard); intent.putExtra(Extra.EXPLICIT_CLOSE, true); broadcastIntent(intent); } else { if (!singleMwState.retainState) { singleMwState.resetState(); metaWearStates.remove(singleMwState.mwBoard); } } } } private void close(MetaWearState mwState) { if (mwState != null) { if (mwState.mwGatt != null) { mwState.mwGatt.close(); mwState.mwGatt = null; } mwState.deviceState = null; mwState.connected = false; } } /** * Close the GATT service and free up resources. This version of the function is for * single MetaWear mode. * @deprecated As of v1.3. Use {@link MetaWearController#close(boolean)} and retrieve * a MetaWearController with {@link #getMetaWearController(BluetoothDevice)} */ public void close() { close(false); } /** Binding between the Intent and this service */ private final Binder serviceBinder = new LocalBinder(); /** Dummy class for getting the MetaWear BLE service from its binder */ public class LocalBinder extends Binder { /** * Get the MetaWearBLEService object * @return MetaWearBLEService object */ public MetaWearBleService getService() { return MetaWearBleService.this; } } @Override public IBinder onBind(Intent intent) { return serviceBinder; } @Override public void onDestroy() { for (Entry<BluetoothDevice, MetaWearState> it : metaWearStates.entrySet()) { close(it.getValue()); } close(singleMwState); metaWearStates.clear(); super.onDestroy(); } private void readRegister(MetaWearState mwState, com.mbientlab.metawear.api.Register register, byte... parameters) { if (!mwState.readyToClose) { byte[] bleData = Registers.buildReadCommand(register, parameters); if (mwState.isRecording) { mwState.etBuilder.withDestRegister(register, Arrays.copyOfRange(bleData, 2, bleData.length), true); } else { queueCommand(mwState, bleData); } } } private void writeRegister(MetaWearState mwState, com.mbientlab.metawear.api.Register register, byte... data) { if (!mwState.readyToClose) { byte[] bleData = Registers.buildWriteCommand(register, data); if (mwState.isRecording) { mwState.etBuilder.withDestRegister(register, Arrays.copyOfRange(bleData, 2, bleData.length), false); } else { queueCommand(mwState, bleData); } } } /** * @param module * @param registerOpcode * @param data */ private void queueCommand(MetaWearState mwState, byte[] command) { mwState.commandBytes.add(command); if (mwState.deviceState == DeviceState.READY) { mwState.deviceState = DeviceState.WRITING_CHARACTERISTICS; if (!isExecGattActions.get()) { writeCommand(mwState); execGattAction(); } else { writeCommand(mwState); } } } }