/******************************************************************************* * Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved. * * The information contained herein is property of Nordic Semiconductor ASA. * Terms and conditions of usage are described in detail in NORDIC SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. * Licensees are granted free, non-transferable use of the information. NO WARRANTY of ANY KIND is provided. * This heading must NOT be removed from the file. ******************************************************************************/ package no.nordicsemi.android.mcp.ble.parser; import no.nordicsemi.android.mcp.adapter.Device; import no.nordicsemi.android.mcp.adapter.Device.DataUnion; import no.nordicsemi.android.mcp.ble.parser.gap.AppearanceParser; import no.nordicsemi.android.mcp.ble.parser.gap.CompleteLocalNameParser; import no.nordicsemi.android.mcp.ble.parser.gap.FlagsParser; import no.nordicsemi.android.mcp.ble.parser.gap.ServicesParser; import no.nordicsemi.android.mcp.ble.parser.gap.ShortenedLocalNameParser; import no.nordicsemi.android.mcp.ble.parser.gap.SlaveConnectionIntervalRangeParser; import no.nordicsemi.android.mcp.ble.parser.gap.TxPowerLevelParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.BatteryParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.ByteParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.CharacterParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.FloatParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.IntParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.ServiceDataParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.ShortParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.StringParser; import no.nordicsemi.android.mcp.ble.parser.gap.servicedata.TemperatureParser; import no.nordicsemi.android.mcp.ble.parser.utils.ParserUtils; import android.bluetooth.BluetoothDevice; import android.util.Log; /** * Advertisement Package Parser */ public class GenericAccessProfileParser { private static final String TAG = "GenericAccessProfileParser"; // For GAP details check Vol 3, Part C in Bluetooth Core Specification 4.0) private static final int FLAGS = 0x01; private static final int SERVICES_MORE_AVAILABLE_16_BIT = 0x02; private static final int SERVICES_COMPLETE_LIST_16_BIT = 0x03; private static final int SERVICES_MORE_AVAILABLE_32_BIT = 0x04; private static final int SERVICES_COMPLETE_LIST_32_BIT = 0x05; private static final int SERVICES_MORE_AVAILABLE_128_BIT = 0x06; private static final int SERVICES_COMPLETE_LIST_128_BIT = 0x07; private static final int SHORTENED_LOCAL_NAME = 0x08; private static final int COMPLETE_LOCAL_NAME = 0x09; private static final int TX_POWER_LEVEL_NAME = 0x0A; private static final int SLAVE_CONNECTION_INTERVAL_RANGE = 0x12; private static final int SERVICES_SOLICITATION_16_BIT = 0x14; private static final int SERVICES_SOLICITATION_128_BIT = 0x15; private static final int SERVICE_DATA = 0x16; private static final int APPEARANCE_DATA = 0x19; private static final int MANUFACTURER_SPECIFIC_DATA = 0xFF; // Nordic thermometer sensor private static final short TEMPERATURE_SERVICE_UUID = 0x1809; private static final short BATTERY_SERVICE_UUID = 0x180F; // private static final short DEVICE_INFORMATION_SERVICE_UUID = 0x180A; // this is unused public static void parse(final Device device, final BluetoothDevice bluetoothDevice, final byte[] data) { int dataIndex = 0; device.getNewInfo(dataIndex++).addData("Type", ParserUtils.deviceTypeTyString(bluetoothDevice.getType())); for (int i = 0; i < data.length;) { final int pkgLength = data[i]; // is this the end? if (pkgLength == 0) break; final int dataType = unsignedByteToInt(data[++i]); switch (dataType) { case FLAGS: FlagsParser.parse(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); device.setConnectable(FlagsParser.icConnectable(data, i + 1, pkgLength - 1)); break; case SERVICES_MORE_AVAILABLE_16_BIT: ServicesParser.parseMoreAvailable16UUID(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_COMPLETE_LIST_16_BIT: ServicesParser.parseCompleteList16UUID(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_MORE_AVAILABLE_32_BIT: ServicesParser.parseMoreAvailable32UUID(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_COMPLETE_LIST_32_BIT: ServicesParser.parseCompleteList32UUID(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_MORE_AVAILABLE_128_BIT: ServicesParser.parseMoreAvailable128UUID(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_COMPLETE_LIST_128_BIT: ServicesParser.parseCompleteList128UUID(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SHORTENED_LOCAL_NAME: ShortenedLocalNameParser.parse(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case COMPLETE_LOCAL_NAME: CompleteLocalNameParser.parse(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case TX_POWER_LEVEL_NAME: TxPowerLevelParser.parse(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SLAVE_CONNECTION_INTERVAL_RANGE: SlaveConnectionIntervalRangeParser.parse(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_SOLICITATION_16_BIT: ServicesParser.parseSolicitation16UUID(device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICES_SOLICITATION_128_BIT: ServicesParser.parseSolicitation128UUID(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case SERVICE_DATA: { /* * First 2 bytes of service data are the 16-bit Service UUID in reverse order. The rest is service specific data. */ final short serviceUUID = ServiceDataParser.decodeServiceUUID(data, i + 1); final Device.DataUnion union = device.getNewInfo(dataIndex++); ServiceDataParser.parse(union, serviceUUID, data, i + 3, pkgLength - 3); parseServiceData(union, serviceUUID, data, i + 3, pkgLength - 3); parseData(union, data, i + 3, pkgLength - 3); break; } case APPEARANCE_DATA: AppearanceParser.parse(device, device.getNewInfo(dataIndex++), data, i + 1, pkgLength - 1); break; case MANUFACTURER_SPECIFIC_DATA: { final Device.DataUnion union = device.getNewInfo(dataIndex++); union.addData("Manufacturer data", ParserUtils.bytesToHex(data, i + 1, pkgLength - 1)); parseData(union, data, i + 1, pkgLength - 1); break; } default: // TODO move it to separate class? device.getNewInfo(dataIndex++).addData(String.format("GAP (0x%02X)", dataType), ParserUtils.bytesToHex(data, i + 1, pkgLength - 1)); break; } i += pkgLength; for (int w = 0; w < device.getInfoSize(); ++w) { DataUnion union = device.getInfo(w); if (union.size() == 0) { Log.d(TAG, "Empty union"); } } } } private static void parseServiceData(final Device.DataUnion union, final short serviceUUID, final byte[] data, int start, final int length) { switch (serviceUUID) { case BATTERY_SERVICE_UUID: BatteryParser.parse(union, data, start, length); break; case TEMPERATURE_SERVICE_UUID: TemperatureParser.parse(union, data, start, length); break; default: break; } } private static void parseData(final Device.DataUnion union, final byte[] data, final int start, final int length) { switch (length) { case 1: ByteParser.parse(union, data, start, length); CharacterParser.parse(union, data, start, length); break; case 2: FloatParser.parse(union, data, start, length); ShortParser.parse(union, data, start, length); break; case 4: FloatParser.parse(union, data, start, length); IntParser.parse(union, data, start, length); break; } StringParser.parse(union, data, start, length); } /** * Convert a signed byte to an unsigned int. */ private static int unsignedByteToInt(byte b) { return b & 0xFF; } }