<commit_before>/*
 * Copyright (C) 2018 Machine Learning and Data Analytics Lab, Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU).
 * <p>
 * This file 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. If you reuse
 * this code you have to keep or cite this comment.
 */
package de.fau.sensorlib.sensors;

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.content.Context;
import android.util.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;

import de.fau.sensorlib.BleGattAttributes;
import de.fau.sensorlib.BleSensorManager;
import de.fau.sensorlib.SensorDataProcessor;
import de.fau.sensorlib.SensorInfo;
import de.fau.sensorlib.enums.HardwareSensor;
import de.fau.sensorlib.enums.SensorMessage;
import de.fau.sensorlib.enums.SensorState;

import static android.bluetooth.BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;

/**
 * Implementation of a generic BLE sensor device.
 */
public class GenericBleSensor extends AbstractSensor {

    protected final static String TAG = GenericBleSensor.class.getSimpleName();

    /**
     * Sensors available on the BLE device after discovery. Not available on class instantiation.
     */
    protected EnumSet<HardwareSensor> mAvailableSensors = mDeviceClass.getAvailableSensors();

    /**
     * Bluetooth device.
     */
    private BluetoothDevice mBtDevice;

    private enum BluetoothProfileState {
        STATE_DISCONNECTED,
        STATE_CONNECTING,
        STATE_CONNECTED,
        STATE_DISCONNECTING,
        STATE_UNKNOWN;

        public static BluetoothProfileState lookup(int state) {
            return (state >= values().length - 1) ? STATE_UNKNOWN : values()[state];
        }
    }

    private enum BluetoothGattStatus {
        GATT_SUCCESS(0x00),
        GATT_READ_NOT_PERMITTED(0x02),
        GATT_WRITE_NOT_PERMITTED(0x03),
        GATT_INSUFFICIENT_AUTHENTICATION(0x05),
        GATT_REQUEST_NOT_SUPPORTED(0x06),
        GATT_INVALID_OFFSET(0x07),
        GATT_INVALID_ATTRIBUTE_LENGTH(0x0D),
        GATT_INSUFFICIENT_ENCRYPTION(0x0F),
        GATT_FAILURE(0x101);

        private int status;

        BluetoothGattStatus(int status) {
            this.status = status;
        }

        public static BluetoothGattStatus lookup(int status) {
            for (BluetoothGattStatus gattStatus : BluetoothGattStatus.values()) {
                if (gattStatus.status == status) {
                    return gattStatus;
                }
            }
            return GATT_FAILURE;
        }
    }

    protected enum BleConnectionMode {
        MODE_DEFAULT,
        MODE_NILSPOD
    }

    private static final int MAX_MTU_SIZE = 247;


    /**
     * GATT client instance for the BLE connection.
     */
    protected BluetoothGatt mGatt;

    private BleGattAttributes.BodySenorLocation mBodyLocation;

    private BleConnectionMode mStateMachineMode = BleConnectionMode.MODE_DEFAULT;

    private boolean mHasBatteryMeasurement = false;


    @Override
    public boolean hasBatteryMeasurement() {
        // Overrides hardcoded value from KnownSensor
        return mHasBatteryMeasurement;
    }

    /**
     * Returns the Body Sensor Location.
     *
     * @return Body Sensor Location as String
     */
    public String getBodyLocation() {
        return BleGattAttributes.BodySenorLocation.getLocation(mBodyLocation);
    }

    protected BluetoothGattService getService(UUID uuid) {
        for (BluetoothGattService service : mServiceList) {
            if (service.getUuid().equals(uuid)) {
                return service;
            }
        }

        return null;
    }

    /**
     * Since BLE characteristics should be read sequentially, this thread-safe queue stores all the characteristic-read-requests.
     */
    protected ConcurrentLinkedQueue<BluetoothGattCharacteristic> mCharacteristicsReadRequests;

    /**
     * Since BLE descriptors should be written sequentially, this thread-safe queue stores all the descriptor-write-requests.
     */
    private ConcurrentLinkedQueue<BluetoothGattDescriptor> mDescriptorWriteRequests;

    /**
     * Since BLE characteristics should be written sequentially, this thread-safe queue stores all the characteristic-write-requests.
     */
    private ConcurrentLinkedQueue<BluetoothGattCharacteristic> mCharacteristicsWriteRequests;


    private ConcurrentLinkedQueue<BluetoothGattCharacteristic> mNotificationsList;

    /**
     * Store references to the sensor's services
     */
    private ArrayList<BluetoothGattService> mServiceList = new ArrayList<>();

    private boolean mWasDiscovered = false;

    private int mReconnectAttempts = 0;
    private static final int MAX_RECONNECT_ATTEMPTS = 2;

    /**
     * GATT callback instance.
     */
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            Log.d(TAG, "<" + getDeviceName() + "> BleStateChange: " + gatt + " < status: " + BluetoothGattStatus.lookup(status) + " – new state: " + BluetoothProfileState.lookup(newState) + " >");

            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothGatt.STATE_CONNECTED) {
                    // assign the custom name from the device
                    mDeviceName = gatt.getDevice().getName();
                    // discover provided services/sensors for this BLE device
                    mGatt.discoverServices();
                } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                    sendDisconnected();
                    mGatt.close();
                    mGatt = null;
                }
            } else {
                if (getState() == SensorState.CONNECTING && mReconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
                    mReconnectAttempts++;
                    Log.e(TAG, BluetoothGattStatus.lookup(status) + ", attempting to reconnect (" + mReconnectAttempts + "/" + MAX_RECONNECT_ATTEMPTS + ")...");
                    mGatt.connect();
                } else {
                    mGatt.close();
                    mGatt = null;
                    sendConnectionLost();<commit_msg>GenericBleSensor does not send a CONNECTION_LOST message if it is currently in UPGRADING_FIRMWARE state (although it would since the GATT gets closed during firmware upgrade)<commit_after>/*
 * Copyright (C) 2018 Machine Learning and Data Analytics Lab, Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU).
 * <p>
 * This file 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. If you reuse
 * this code you have to keep or cite this comment.
 */
package de.fau.sensorlib.sensors;

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.content.Context;
import android.util.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;

import de.fau.sensorlib.BleGattAttributes;
import de.fau.sensorlib.BleSensorManager;
import de.fau.sensorlib.SensorDataProcessor;
import de.fau.sensorlib.SensorInfo;
import de.fau.sensorlib.enums.HardwareSensor;
import de.fau.sensorlib.enums.SensorMessage;
import de.fau.sensorlib.enums.SensorState;

import static android.bluetooth.BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;

/**
 * Implementation of a generic BLE sensor device.
 */
public class GenericBleSensor extends AbstractSensor {

    protected final static String TAG = GenericBleSensor.class.getSimpleName();

    /**
     * Sensors available on the BLE device after discovery. Not available on class instantiation.
     */
    protected EnumSet<HardwareSensor> mAvailableSensors = mDeviceClass.getAvailableSensors();

    /**
     * Bluetooth device.
     */
    private BluetoothDevice mBtDevice;

    private enum BluetoothProfileState {
        STATE_DISCONNECTED,
        STATE_CONNECTING,
        STATE_CONNECTED,
        STATE_DISCONNECTING,
        STATE_UNKNOWN;

        public static BluetoothProfileState lookup(int state) {
            return (state >= values().length - 1) ? STATE_UNKNOWN : values()[state];
        }
    }

    private enum BluetoothGattStatus {
        GATT_SUCCESS(0x00),
        GATT_READ_NOT_PERMITTED(0x02),
        GATT_WRITE_NOT_PERMITTED(0x03),
        GATT_INSUFFICIENT_AUTHENTICATION(0x05),
        GATT_REQUEST_NOT_SUPPORTED(0x06),
        GATT_INVALID_OFFSET(0x07),
        GATT_INVALID_ATTRIBUTE_LENGTH(0x0D),
        GATT_INSUFFICIENT_ENCRYPTION(0x0F),
        GATT_FAILURE(0x101);

        private int status;

        BluetoothGattStatus(int status) {
            this.status = status;
        }

        public static BluetoothGattStatus lookup(int status) {
            for (BluetoothGattStatus gattStatus : BluetoothGattStatus.values()) {
                if (gattStatus.status == status) {
                    return gattStatus;
                }
            }
            return GATT_FAILURE;
        }
    }

    protected enum BleConnectionMode {
        MODE_DEFAULT,
        MODE_NILSPOD
    }

    private static final int MAX_MTU_SIZE = 247;


    /**
     * GATT client instance for the BLE connection.
     */
    protected BluetoothGatt mGatt;

    private BleGattAttributes.BodySenorLocation mBodyLocation;

    private BleConnectionMode mStateMachineMode = BleConnectionMode.MODE_DEFAULT;

    private boolean mHasBatteryMeasurement = false;


    @Override
    public boolean hasBatteryMeasurement() {
        // Overrides hardcoded value from KnownSensor
        return mHasBatteryMeasurement;
    }

    /**
     * Returns the Body Sensor Location.
     *
     * @return Body Sensor Location as String
     */
    public String getBodyLocation() {
        return BleGattAttributes.BodySenorLocation.getLocation(mBodyLocation);
    }

    protected BluetoothGattService getService(UUID uuid) {
        for (BluetoothGattService service : mServiceList) {
            if (service.getUuid().equals(uuid)) {
                return service;
            }
        }

        return null;
    }

    /**
     * Since BLE characteristics should be read sequentially, this thread-safe queue stores all the characteristic-read-requests.
     */
    protected ConcurrentLinkedQueue<BluetoothGattCharacteristic> mCharacteristicsReadRequests;

    /**
     * Since BLE descriptors should be written sequentially, this thread-safe queue stores all the descriptor-write-requests.
     */
    private ConcurrentLinkedQueue<BluetoothGattDescriptor> mDescriptorWriteRequests;

    /**
     * Since BLE characteristics should be written sequentially, this thread-safe queue stores all the characteristic-write-requests.
     */
    private ConcurrentLinkedQueue<BluetoothGattCharacteristic> mCharacteristicsWriteRequests;


    private ConcurrentLinkedQueue<BluetoothGattCharacteristic> mNotificationsList;

    /**
     * Store references to the sensor's services
     */
    private ArrayList<BluetoothGattService> mServiceList = new ArrayList<>();

    private boolean mWasDiscovered = false;

    private int mReconnectAttempts = 0;
    private static final int MAX_RECONNECT_ATTEMPTS = 2;

    /**
     * GATT callback instance.
     */
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            Log.d(TAG, "<" + getDeviceName() + "> BleStateChange: " + gatt + " < status: " + BluetoothGattStatus.lookup(status) + " – new state: " + BluetoothProfileState.lookup(newState) + " >");

            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothGatt.STATE_CONNECTED) {
                    // assign the custom name from the device
                    mDeviceName = gatt.getDevice().getName();
                    // discover provided services/sensors for this BLE device
                    mGatt.discoverServices();
                } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                    sendDisconnected();
                    mGatt.close();
                    mGatt = null;
                }
            } else {
                if (getState() == SensorState.CONNECTING && mReconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
                    mReconnectAttempts++;
                    Log.e(TAG, BluetoothGattStatus.lookup(status) + ", attempting to reconnect (" + mReconnectAttempts + "/" + MAX_RECONNECT_ATTEMPTS + ")...");
                    mGatt.connect();
                } else {
                    mGatt.close();
                    mGatt = null;
                    if (getState() != SensorState.UPGRADING_FIRMWARE) {
                        sendConnectionLost();
                    }
{'fraction_non_alphanumeric': 0.07752899132083728, 'fraction_numerical': 0.005251258113923128, 'mean_word_length': 2.738276990185387, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '682587', 'n_tokens_mistral': 3859, 'n_tokens_neox': 3664, 'n_words': 882}