/******************************************************************************* * Copyright (c) 2011, 2016 Eurotech and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eurotech * Red Hat Inc - minor clean ups *******************************************************************************/ package org.eclipse.kura.linux.bluetooth; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.kura.KuraException; import org.eclipse.kura.bluetooth.BluetoothAdapter; import org.eclipse.kura.bluetooth.BluetoothBeaconCommandListener; import org.eclipse.kura.bluetooth.BluetoothBeaconScanListener; import org.eclipse.kura.bluetooth.BluetoothDevice; import org.eclipse.kura.bluetooth.BluetoothLeScanListener; import org.eclipse.kura.bluetooth.listener.BluetoothAdvertisementScanListener; import org.eclipse.kura.linux.bluetooth.le.BluetoothLeScanner; import org.eclipse.kura.linux.bluetooth.le.beacon.BluetoothAdvertisingData; import org.eclipse.kura.linux.bluetooth.le.beacon.BluetoothConfigurationProcessListener; import org.eclipse.kura.linux.bluetooth.util.BluetoothUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BluetoothAdapterImpl implements BluetoothAdapter { private static final Logger s_logger = LoggerFactory.getLogger(BluetoothAdapterImpl.class); private static List<BluetoothDevice> s_connectedDevices; private final String m_name; private String m_address; private boolean m_leReady; private BluetoothLeScanner m_bls = null; private BluetoothBeaconCommandListener m_bbcl; // See Bluetooth 4.0 Core specifications (https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=229737) private static final String OGF_CONTROLLER_CMD = "0x08"; private static final String OCF_ADVERTISING_PARAM_CMD = "0x0006"; private static final String OCF_ADVERTISING_DATA_CMD = "0x0008"; private static final String OCF_ADVERTISING_ENABLE_CMD = "0x000a"; public BluetoothAdapterImpl(String name) throws KuraException { this.m_name = name; this.m_bbcl = null; buildAdapter(name); } public BluetoothAdapterImpl(String name, BluetoothBeaconCommandListener bbcl) throws KuraException { this.m_name = name; this.m_bbcl = bbcl; buildAdapter(name); } public void setBluetoothBeaconCommandListener(BluetoothBeaconCommandListener bbcl) { this.m_bbcl = bbcl; } // -------------------------------------------------------------------- // // Private methods // // -------------------------------------------------------------------- private void buildAdapter(String name) throws KuraException { s_logger.debug("Creating new Bluetooth adapter: {}", name); Map<String, String> props = new HashMap<String, String>(); props = BluetoothUtil.getConfig(name); this.m_address = props.get("address"); this.m_leReady = Boolean.parseBoolean(props.get("leReady")); } private String[] toStringArray(String string) { // Regex to split a string every 2 characters return string.split("(?<=\\G..)"); } // -------------------------------------------------------------------- // // Static methods // // -------------------------------------------------------------------- public static void addConnectedDevice(BluetoothDevice bd) { if (s_connectedDevices == null) { s_connectedDevices = new ArrayList<BluetoothDevice>(); } s_connectedDevices.add(bd); } public static void removeConnectedDevice(BluetoothDevice bd) { if (s_connectedDevices == null) { return; } s_connectedDevices.remove(bd); } // -------------------------------------------------------------------- // // BluetoothAdapter API // // -------------------------------------------------------------------- @Override public String getAddress() { return this.m_address; } @Override public boolean isEnabled() { return BluetoothUtil.isEnabled(this.m_name); } @Override public void startLeScan(BluetoothLeScanListener listener) { killLeScan(); this.m_bls = new BluetoothLeScanner(); this.m_bls.startScan(this.m_name, listener); } @Override public void startAdvertisementScan(String companyName, BluetoothAdvertisementScanListener listener) { killLeScan(); this.m_bls = new BluetoothLeScanner(); this.m_bls.startAdvertisementScan(this.m_name, companyName, listener); } @Override public void startBeaconScan(String companyName, BluetoothBeaconScanListener listener) { killLeScan(); this.m_bls = new BluetoothLeScanner(); this.m_bls.startBeaconScan(this.m_name, companyName, listener); } @Override public void killLeScan() { if (this.m_bls != null) { this.m_bls.killScan(); this.m_bls = null; } } @Override public boolean isScanning() { if (this.m_bls != null) { return this.m_bls.isScanRunning(); } else { return false; } } @Override public boolean isLeReady() { return this.m_leReady; } @Override public void enable() { BluetoothUtil.hciconfigCmd(this.m_name, "up"); } @Override public void disable() { BluetoothUtil.hciconfigCmd(this.m_name, "down"); } @Override public BluetoothDevice getRemoteDevice(String address) { return new BluetoothDeviceImpl(address, ""); } @Override public void startBeaconAdvertising() { BluetoothConfigurationProcessListener bbl = new BluetoothConfigurationProcessListener(this.m_bbcl); s_logger.debug("Start Advertising : hcitool -i " + this.m_name + " cmd " + OGF_CONTROLLER_CMD + " " + OCF_ADVERTISING_ENABLE_CMD + " 01"); s_logger.info("Start Advertising on interface " + this.m_name); String[] cmd = { "cmd", OGF_CONTROLLER_CMD, OCF_ADVERTISING_ENABLE_CMD, "01" }; BluetoothUtil.hcitoolCmd(this.m_name, cmd, bbl); } @Override public void stopBeaconAdvertising() { BluetoothConfigurationProcessListener bbl = new BluetoothConfigurationProcessListener(this.m_bbcl); s_logger.debug("Stop Advertising : hcitool -i " + this.m_name + " cmd " + OGF_CONTROLLER_CMD + " " + OCF_ADVERTISING_ENABLE_CMD + " 00"); s_logger.info("Stop Advertising on interface " + this.m_name); String[] cmd = { "cmd", OGF_CONTROLLER_CMD, OCF_ADVERTISING_ENABLE_CMD, "00" }; BluetoothUtil.hcitoolCmd(this.m_name, cmd, bbl); } @Override public void setBeaconAdvertisingInterval(Integer min, Integer max) { BluetoothConfigurationProcessListener bbl = new BluetoothConfigurationProcessListener(this.m_bbcl); // See // http://stackoverflow.com/questions/21124993/is-there-a-way-to-increase-ble-advertisement-frequency-in-bluez String[] minHex = toStringArray(BluetoothAdvertisingData.to2BytesHex(min)); String[] maxHex = toStringArray(BluetoothAdvertisingData.to2BytesHex(max)); s_logger.debug("Set Advertising Parameters : hcitool -i " + this.m_name + " cmd " + OGF_CONTROLLER_CMD + " " + OCF_ADVERTISING_PARAM_CMD + " " + minHex[1] + " " + minHex[0] + " " + maxHex[1] + " " + maxHex[0] + " 03 00 00 00 00 00 00 00 00 07 00"); s_logger.info("Set Advertising Parameters on interface " + this.m_name); String[] cmd = { "cmd", OGF_CONTROLLER_CMD, OCF_ADVERTISING_PARAM_CMD, minHex[1], minHex[0], maxHex[1], maxHex[0], "03", "00", "00", "00", "00", "00", "00", "00", "00", "07", "00" }; BluetoothUtil.hcitoolCmd(this.m_name, cmd, bbl); } @Override public void setBeaconAdvertisingData(String uuid, Integer major, Integer minor, String companyCode, Integer txPower, boolean LELimited, boolean LEGeneral, boolean BR_EDRSupported, boolean LE_BRController, boolean LE_BRHost) { BluetoothConfigurationProcessListener bbl = new BluetoothConfigurationProcessListener(this.m_bbcl); String[] dataHex = toStringArray(BluetoothAdvertisingData.getData(uuid, major, minor, companyCode, txPower, LELimited, LEGeneral, BR_EDRSupported, LE_BRController, LE_BRHost)); String[] cmd = new String[3 + dataHex.length]; cmd[0] = "cmd"; cmd[1] = OGF_CONTROLLER_CMD; cmd[2] = OCF_ADVERTISING_DATA_CMD; for (int i = 0; i < dataHex.length; i++) { cmd[i + 3] = dataHex[i]; } s_logger.debug("Set Advertising Data : hcitool -i " + this.m_name + "cmd " + OGF_CONTROLLER_CMD + " " + OCF_ADVERTISING_DATA_CMD + " " + Arrays.toString(dataHex)); s_logger.info("Set Advertising Data on interface " + this.m_name); BluetoothUtil.hcitoolCmd(this.m_name, cmd, bbl); } @Override public void ExecuteCmd(String ogf, String ocf, String parameter) { BluetoothConfigurationProcessListener bbl = new BluetoothConfigurationProcessListener(this.m_bbcl); String[] paramArray = toStringArray(parameter); s_logger.info("Execute custom command : hcitool -i " + this.m_name + "cmd " + ogf + " " + ocf + " " + Arrays.toString(paramArray)); String[] cmd = new String[3 + paramArray.length]; cmd[0] = "cmd"; cmd[1] = ogf; cmd[2] = ocf; for (int i = 0; i < paramArray.length; i++) { cmd[i + 3] = paramArray[i]; } BluetoothUtil.hcitoolCmd(this.m_name, cmd, bbl); } }