diff options
author | Sven Gothel <[email protected]> | 2020-10-25 10:33:12 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-10-25 10:33:12 +0100 |
commit | 5f6fdee65c34337b1ade27de59192dbfe613317d (patch) | |
tree | a4e674e07e9d516ef3cea2bf6d67dcb93bf87bd6 | |
parent | 530db7b53364c7ba3460d2db8bde53bf2a20ef3f (diff) |
Add ChangedAdapterSetCallback (C++), ChangedAdapterSetListener (Java) for [DBT|Bluetooth]Manager, enabling fully event driven workflow w/ adapter add/remove
-rw-r--r-- | api/direct_bt/DBTAdapter.hpp | 4 | ||||
-rw-r--r-- | api/direct_bt/DBTManager.hpp | 127 | ||||
-rw-r--r-- | examples/direct_bt_scanner10/dbt_scanner10.cpp | 111 | ||||
-rw-r--r-- | examples/java/DBTScanner10.java | 115 | ||||
-rw-r--r-- | java/direct_bt/tinyb/DBTManager.java | 54 | ||||
-rw-r--r-- | java/org/tinyb/BluetoothManager.java | 41 | ||||
-rw-r--r-- | java/tinyb/dbus/DBusManager.java | 7 | ||||
-rw-r--r-- | scripts/run-dbt_scanner10.sh | 4 | ||||
-rw-r--r-- | src/direct_bt/DBTManager.cpp | 39 |
9 files changed, 418 insertions, 84 deletions
diff --git a/api/direct_bt/DBTAdapter.hpp b/api/direct_bt/DBTAdapter.hpp index 532f9e2d..86fcbc16 100644 --- a/api/direct_bt/DBTAdapter.hpp +++ b/api/direct_bt/DBTAdapter.hpp @@ -280,7 +280,7 @@ namespace direct_bt { * Using the default adapter device * <p> * The default adapter is either the first POWERED adapter, - * or the one with index == dev_id == 0. + * or none - in which case this instance !isValid() * </p> */ DBTAdapter() noexcept; @@ -297,7 +297,7 @@ namespace direct_bt { * or the default adapter device if dev_id < 0. * <p> * The default adapter is either the first POWERED adapter, - * or the one with index == dev_id == 0. + * or none - in which case this instance !isValid(). * </p> * * @param[in] dev_id an already identified HCI device id diff --git a/api/direct_bt/DBTManager.hpp b/api/direct_bt/DBTManager.hpp index c1b351d2..7d28344f 100644 --- a/api/direct_bt/DBTManager.hpp +++ b/api/direct_bt/DBTManager.hpp @@ -136,6 +136,57 @@ namespace direct_bt { }; /** + * Callback function to receive change events regarding the system's adapter set, + * e.g. about a removed or added adapter due to user interaction or 'cold reset'. + * <p> + * When a new callback is added, all available adapter's will be reported as added, + * this allows a fully event driven workflow. + * </p> + * <p> + * Note that the 'adapter added' callback, i.e. 'added==true', + * is performed on a dedicated thread allowing the user to perform complex operations. + * </p> + * <p> + * User shall not perform complex operations on the 'adapter removed' callback, + * i.e. 'added==false'. + * </p> + * + * @param added true if adapter was newly added, otherwise removed from system + * @param adapterInfo the adapter's AdapterInfo, inclusive the dev_id + * @return ignored + * @see ChangedAdapterSetCallback + * @see DBTManager::addChangedAdapterSetCallback() + * @see DBTManager::removeChangedAdapterSetCallback() + */ + typedef bool (*ChangedAdapterSetFunc)(bool added, const AdapterInfo& adapterInfo); + + /** + * Callback jau::FunctionDef to receive change events regarding the system's adapter set, + * e.g. about a removed or added adapter due to user interaction or 'cold reset'. + * <p> + * When a new callback is added, all available adapter's will be reported as added, + * this allows a fully event driven workflow. + * </p> + * <p> + * Note that the 'adapter added' callback, i.e. 'added==true', + * is performed on a dedicated thread allowing the user to perform complex operations. + * </p> + * <p> + * User shall not perform complex operations on the 'adapter removed' callback, + * i.e. 'added==false'. + * </p> + * + * @param added true if adapter was newly added, otherwise removed from system + * @param adapterInfo the adapter's AdapterInfo, inclusive the dev_id + * @return ignored + * @see ChangedAdapterSetFunc + * @see DBTManager::addChangedAdapterSetCallback() + * @see DBTManager::removeChangedAdapterSetCallback() + */ + typedef jau::FunctionDef<bool, bool, const AdapterInfo&> ChangedAdapterSetCallback; + typedef jau::cow_vector<ChangedAdapterSetCallback> ChangedAdapterSetCallbackList; + + /** * A thread safe singleton handler of the Linux Kernel's BlueZ manager control channel. * <p> * Implementation utilizes a lock free ringbuffer receiving data within its separate thread. @@ -187,6 +238,8 @@ namespace direct_bt { return static_cast<uint16_t>(opc) < mgmtAdapterEventCallbackLists.size(); } + ChangedAdapterSetCallbackList mgmtChangedAdapterSetCallbackList; + jau::cow_vector<std::shared_ptr<AdapterInfo>> adapterInfos; void mgmtReaderThreadImpl() noexcept; @@ -226,6 +279,18 @@ namespace direct_bt { bool mgmtEvPinCodeRequestCB(std::shared_ptr<MgmtEvent> e) noexcept; bool mgmtEvUserPasskeyRequestCB(std::shared_ptr<MgmtEvent> e) noexcept; + /** + * Adds the given AdapterInfo if representing a new dev_id. + * @return true if newly added dev_id, otherwise false if dev_id already exists. + */ + bool addAdapterInfo(std::shared_ptr<AdapterInfo> ai) noexcept; + + /** + * Removes the AdapterInfo with the given dev_id + * @return the removed instance or nullptr if not found. + */ + std::shared_ptr<AdapterInfo> removeAdapterInfo(const uint16_t dev_id) noexcept; + public: /** * Retrieves the singleton instance. @@ -296,18 +361,6 @@ namespace direct_bt { std::shared_ptr<AdapterInfo> getAdapterInfo(const uint16_t dev_id) const noexcept; /** - * Adds the given AdapterInfo if representing a new dev_id. - * @return true if newly added dev_id, otherwise false if dev_id already exists. - */ - bool addAdapterInfo(std::shared_ptr<AdapterInfo> ai) noexcept; - - /** - * Removes the AdapterInfo with the given dev_id - * @return the removed instance or nullptr if not found. - */ - std::shared_ptr<AdapterInfo> removeAdapterInfo(const uint16_t dev_id) noexcept; - - /** * Returns the current BTMode of given adapter dev_idx or BTMode::NONE if dev_id adapter is not available. */ BTMode getCurrentBTMode(uint16_t dev_id) const noexcept; @@ -410,6 +463,56 @@ namespace direct_bt { /** Manually send a MgmtEvent to all of its listeners. */ void sendMgmtEvent(std::shared_ptr<MgmtEvent> event) noexcept; + + /** ChangedAdapterSetCallback handling */ + + /** + * Adds the given ChangedAdapterSetCallback to this manager. + * <p> + * When a new callback is added, all available adapter's will be reported as added, + * this allows a fully event driven workflow. + * </p> + * <p> + * Note that the 'adapter added' callback, i.e. 'added==true', + * is performed on a dedicated thread allowing the user to perform complex operations. + * </p> + * <p> + * User shall not perform complex operations on the 'adapter removed' callback, + * i.e. 'added==false'. + * </p> + */ + void addChangedAdapterSetCallback(const ChangedAdapterSetCallback & l); + + /** + * Remove the given ChangedAdapterSetCallback from this manager. + * @param l the to be removed element + * @return the number of removed elements + */ + int removeChangedAdapterSetCallback(const ChangedAdapterSetCallback & l); + + /** + * Adds the given ChangedAdapterSetFunc to this manager. + * <p> + * When a new callback is added, all available adapter's will be reported as added, + * this allows a fully event driven workflow. + * </p> + * <p> + * Note that the 'adapter added' callback, i.e. 'added==true', + * is performed on a dedicated thread allowing the user to perform complex operations. + * </p> + * <p> + * User shall not perform complex operations on the 'adapter removed' callback, + * i.e. 'added==false'. + * </p> + */ + void addChangedAdapterSetCallback(ChangedAdapterSetFunc f); + + /** + * Remove the given ChangedAdapterSetFunc from this manager. + * @param l the to be removed element + * @return the number of removed elements + */ + int removeChangedAdapterSetCallback(ChangedAdapterSetFunc f); }; } // namespace direct_bt diff --git a/examples/direct_bt_scanner10/dbt_scanner10.cpp b/examples/direct_bt_scanner10/dbt_scanner10.cpp index 9a9d162b..4c95f823 100644 --- a/examples/direct_bt_scanner10/dbt_scanner10.cpp +++ b/examples/direct_bt_scanner10/dbt_scanner10.cpp @@ -506,16 +506,20 @@ static bool startDiscovery(DBTAdapter *a, std::string msg) { return HCIStatusCode::SUCCESS == status; } -static std::shared_ptr<DBTAdapter> createAdapter(const int dev_id0) { +static std::shared_ptr<DBTAdapter> createAdapter(const int dev_id0, const bool validate_dev_id) { // pre-validate dev_id availability int dev_id; - DBTManager & mngr = DBTManager::get(BTMode::LE); - if( 0 > dev_id0 ) { - dev_id = mngr.getDefaultAdapterDevID(); - } else if( nullptr != mngr.getAdapterInfo(dev_id0) ) { - dev_id = dev_id0; + if( validate_dev_id ) { + DBTManager & mngr = DBTManager::get(BTMode::LE); + if( 0 > dev_id0 ) { + dev_id = mngr.getDefaultAdapterDevID(); + } else if( nullptr != mngr.getAdapterInfo(dev_id0) ) { + dev_id = dev_id0; + } else { + dev_id = -1; + } } else { - dev_id = -1; + dev_id = dev_id0; } if( 0 > dev_id ) { fprintf(stderr, "Adapter not available (1): Request %d, deduced %d\n", dev_id0, dev_id); @@ -531,7 +535,6 @@ static std::shared_ptr<DBTAdapter> createAdapter(const int dev_id0) { fprintf(stderr, "Adapter not powered (2): %s\n", adapter->toString().c_str()); return nullptr; } - fprintf(stderr, "Using adapter: %s\n", adapter->toString().c_str()); adapter->addStatusListener(std::shared_ptr<AdapterStatusListener>(new MyAdapterStatusListener())); @@ -548,12 +551,68 @@ static std::shared_ptr<DBTAdapter> createAdapter(const int dev_id0) { return adapter; } -void test(int dev_id) { +static jau::cow_vector<std::shared_ptr<DBTAdapter>> adapterList; + +static std::shared_ptr<DBTAdapter> getAdapter(const uint16_t dev_id) { + std::shared_ptr<std::vector<std::shared_ptr<DBTAdapter>>> snapshot = adapterList.get_snapshot(); + auto begin = snapshot->begin(); + auto it = std::find_if(begin, snapshot->end(), [&](std::shared_ptr<DBTAdapter> const& p) -> bool { + return p->dev_id == dev_id; + }); + if ( it == std::end(*snapshot) ) { + return nullptr; + } else { + return *it; + } +} +static int removeAdapter(const uint16_t dev_id) { + const std::lock_guard<std::recursive_mutex> lock(adapterList.get_write_mutex()); + std::shared_ptr<std::vector<std::shared_ptr<DBTAdapter>>> store = adapterList.copy_store(); + int count = 0; + for(auto it = store->begin(); it != store->end(); ) { + if ( (*it)->dev_id == dev_id ) { + it = store->erase(it); + count++; + } else { + ++it; + } + } + if( 0 < count ) { + adapterList.set_store(std::move(store)); + } + return count; +} + +static bool myChangedAdapterSetFunc(const bool added, const AdapterInfo& adapterInfo) { + if( added ) { + std::shared_ptr<DBTAdapter> pre = getAdapter(adapterInfo.dev_id); + if( nullptr != pre ) { + fprintf(stderr, "****** Adapter ADDED__: Not new %s\n", pre->toString().c_str()); + } else { + if( adapterInfo.isCurrentSettingBitSet(AdapterSetting::POWERED) ) { + std::shared_ptr<DBTAdapter> adapter = createAdapter(adapterInfo.dev_id, false /* validate_dev_id */); + if( nullptr != adapter ) { + adapterList.push_back(adapter); + fprintf(stderr, "****** Adapter ADDED__: Created %s\n", adapter->toString().c_str()); + } + } else { + fprintf(stderr, "****** Adapter ADDED__: Ignored %s\n", adapterInfo.toString().c_str()); + } + } + } else { + const int count = removeAdapter(adapterInfo.dev_id); + fprintf(stderr, "****** Adapter REMOVED: count %d, %s\n", count, adapterInfo.toString().c_str()); + } + return true; +} + +void test() { bool done = false; timestamp_t0 = getCurrentMilliseconds(); - std::shared_ptr<DBTAdapter> adapter = createAdapter(dev_id); + DBTManager & mngr = DBTManager::get(BTMode::LE); + mngr.addChangedAdapterSetCallback(myChangedAdapterSetFunc); while( !done ) { if( 0 == MULTI_MEASUREMENTS || @@ -566,28 +625,22 @@ void test(int dev_id) { printDevicesProcessed("****** DevicesProcessed "); done = true; } else { - // validate existing adapter - if( nullptr != adapter ) { - if( !adapter->isValid() /* || !adapter->isPowered() */ ) { - // In case of removed adapter, not just powered-off (soft-reset) - // We could also close adapter on !isPowered() here, but its not required - adapter operational. - adapter->close(); - adapter = nullptr; // purge - } - } - // re-create adapter if required - if( nullptr == adapter ) { - adapter = createAdapter(dev_id); - } std::this_thread::sleep_for(std::chrono::milliseconds(2000)); } } - fprintf(stderr, "****** EOL Adapter's Devices - pre close: %s\n", adapter->toString().c_str()); - if( nullptr != adapter ) { + + jau::for_each_cow(adapterList, [](std::shared_ptr<DBTAdapter>& adapter) { + fprintf(stderr, "****** EOL Adapter's Devices - pre close: %s\n", adapter->toString().c_str()); adapter->printSharedPtrListOfDevices(); adapter->close(); fprintf(stderr, "****** EOL Adapter's Devices - post close\n"); adapter->printSharedPtrListOfDevices(); + }); + fprintf(stderr, "****** EOL Adapter's Devices - post close all\n"); + + { + int count = mngr.removeChangedAdapterSetCallback(myChangedAdapterSetFunc); + fprintf(stderr, "****** EOL Removed ChangedAdapterSetCallback %d\n", count); } } @@ -595,7 +648,6 @@ void test(int dev_id) { int main(int argc, char *argv[]) { - int dev_id = -1; // default BTMode btMode = BTMode::NONE; bool waitForEnter=false; @@ -612,8 +664,6 @@ int main(int argc, char *argv[]) setenv("direct_bt.hci", argv[++i], 1 /* overwrite */); } else if( !strcmp("-dbt_mgmt", argv[i]) && argc > (i+1) ) { setenv("direct_bt.mgmt", argv[++i], 1 /* overwrite */); - } else if( !strcmp("-dev_id", argv[i]) && argc > (i+1) ) { - dev_id = atoi(argv[++i]); } else if( !strcmp("-btmode", argv[i]) && argc > (i+1) ) { btMode = getBTMode(argv[++i]); if( BTMode::NONE != btMode ) { @@ -650,7 +700,7 @@ int main(int argc, char *argv[]) } fprintf(stderr, "pid %d\n", getpid()); - fprintf(stderr, "Run with '[-dev_id <adapter-index>] [-btmode LE|BREDR|DUAL] " + fprintf(stderr, "Run with '[-btmode LE|BREDR|DUAL] " "[-disconnect] [-enableGATTPing] [-count <number>] [-single] [-show_update_events] [-quiet] " "[-resetEachCon connectionCount] " "(-mac <device_address>)* (-wl <device_address>)* " @@ -670,7 +720,6 @@ int main(int argc, char *argv[]) fprintf(stderr, "USE_WHITELIST %d\n", USE_WHITELIST); fprintf(stderr, "SHOW_UPDATE_EVENTS %d\n", SHOW_UPDATE_EVENTS); fprintf(stderr, "QUIET %d\n", QUIET); - fprintf(stderr, "dev_id %d\n", dev_id); fprintf(stderr, "btmode %s\n", getBTModeString(btMode).c_str()); printList( "waitForDevice: ", waitForDevices); @@ -680,7 +729,7 @@ int main(int argc, char *argv[]) getchar(); } fprintf(stderr, "****** TEST start\n"); - test(dev_id); + test(); fprintf(stderr, "****** TEST end\n"); if( true ) { // Just for testing purpose, i.e. triggering DBTManager::close() within the test controlled app, diff --git a/examples/java/DBTScanner10.java b/examples/java/DBTScanner10.java index 7c490f24..78283bc9 100644 --- a/examples/java/DBTScanner10.java +++ b/examples/java/DBTScanner10.java @@ -30,7 +30,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; import org.tinyb.AdapterSettings; import org.tinyb.BluetoothAdapter; @@ -54,6 +57,7 @@ import org.tinyb.HCIStatusCode; import org.tinyb.HCIWhitelistConnectType; import org.tinyb.PairingMode; import org.tinyb.ScanType; +import org.tinyb.BluetoothManager.ChangedAdapterSetListener; import direct_bt.tinyb.DBTManager; @@ -87,8 +91,6 @@ public class DBTScanner10 { boolean SHOW_UPDATE_EVENTS = false; boolean QUIET = false; - int dev_id = -1; // use default - int shutdownTest = 0; static void printf(final String format, final Object... args) { @@ -525,25 +527,10 @@ public class DBTScanner10 { return HCIStatusCode.SUCCESS == status; } - private BluetoothAdapter createAdapter(final BluetoothManager mngr, final int dev_id0) { - BluetoothAdapter adapter; - if( 0 > dev_id0 ) { - adapter = mngr.getDefaultAdapter(); - } else { - adapter = mngr.getAdapter(dev_id0); - if( !adapter.isPowered() ) { - adapter = mngr.getDefaultAdapter(); - } - } - final int dev_id = null != adapter ? adapter.getDevID() : -1; - if( 0 > dev_id ) { - println("Adapter not available (1): Request "+dev_id0+", deduced "+dev_id); - return null; - } - + private boolean initAdapter(final BluetoothAdapter adapter) { if( !adapter.isPowered() ) { // should have been covered above println("Adapter not powered (2): "+adapter.toString()); - return null; + return false; } println("Using adapter: "+adapter.toString()); @@ -564,18 +551,71 @@ public class DBTScanner10 { } } else { if( !startDiscovery(adapter, "kick-off") ) { + return false; } } - return adapter; + return true; } - public void runTest(final BluetoothManager manager) { - BluetoothAdapter adapter = createAdapter(manager, dev_id); + private final List<BluetoothAdapter> adapters = new CopyOnWriteArrayList<BluetoothAdapter>(); + + @SuppressWarnings("unused") + private BluetoothAdapter getAdapter(final int dev_id) { + for( int i=0; i<adapters.size(); i++) { + final BluetoothAdapter a = adapters.get(i); + if( dev_id == a.getDevID() ) { + return a; + } + } + return null; + } + + private int removeAdapter(final int dev_id) { + // Using removeIf allows performing test on all object and remove within one write-lock cycle + final int count[] = { 0 }; + adapters.removeIf(new Predicate<BluetoothAdapter>() { + @Override + public boolean test(final BluetoothAdapter t) { + if( dev_id == t.getDevID() ) { + count[0]++; + return true; + } + return false; + } + }); + return count[0]; + } + private final BluetoothManager.ChangedAdapterSetListener myChangedAdapterSetListener = + new BluetoothManager.ChangedAdapterSetListener() { + @Override + public void adapterAdded(final BluetoothAdapter adapter) { + if( adapter.isPowered() ) { + if( initAdapter( adapter ) ) { + adapters.add(adapter); + println("****** Adapter ADDED__: NewInit " + adapter); + } else { + println("****** Adapter ADDED__: Failed_ " + adapter); + } + } else { + println("****** Adapter ADDED__: Ignored " + adapter); + } + } + + @Override + public void adapterRemoved(final BluetoothAdapter adapter) { + final int count = removeAdapter(adapter.getDevID()); + println("****** Adapter REMOVED: count "+count+", " + adapter); + } + }; + + public void runTest(final BluetoothManager manager) { timestamp_t0 = BluetoothUtils.currentTimeMillis(); boolean done = false; + manager.addChangedAdapterSetListener(myChangedAdapterSetListener); + while( !done ) { if( 0 == MULTI_MEASUREMENTS.get() || ( -1 == MULTI_MEASUREMENTS.get() && !waitForDevices.isEmpty() && devicesProcessed.containsAll(waitForDevices) ) @@ -587,19 +627,6 @@ public class DBTScanner10 { println("****** DevicesProcessed "+Arrays.toString(devicesProcessed.toArray())); done = true; } else { - // validate existing adapter - if( null != adapter ) { - if( !adapter.isValid() /* || !adapter.isPowered() */ ) { - // In case of removed adapter, not just powered-off (soft-reset) - // We could also close adapter on !isPowered() here, but its not required - adapter operational. - adapter.close(); - adapter = null; // purge - } - } - // re-create adapter if required - if( null == adapter ) { - adapter = createAdapter(manager, dev_id); - } try { Thread.sleep(2000); } catch (final InterruptedException e) { @@ -608,6 +635,19 @@ public class DBTScanner10 { } } + adapters.forEach(new Consumer<BluetoothAdapter>() { + @Override + public void accept(final BluetoothAdapter a) { + println("****** EOL Adapter's Devices - pre_ close: " + a); + a.close(); + println("****** EOL Adapter's Devices - post close: " + a); + } } ); + + { + final int count = manager.removeChangedAdapterSetListener(myChangedAdapterSetListener); + println("****** EOL Removed ChangedAdapterSetCallback " + count); + } + // All implicit via destructor or shutdown hook! // manager.shutdown(); /* implies: adapter.close(); */ } @@ -663,8 +703,6 @@ public class DBTScanner10 { test.SHOW_UPDATE_EVENTS = true; } else if( arg.equals("-quiet") ) { test.QUIET = true; - } else if( arg.equals("-dev_id") && args.length > (i+1) ) { - test.dev_id = Integer.valueOf(args[++i]).intValue(); } else if( arg.equals("-shutdown") && args.length > (i+1) ) { test.shutdownTest = Integer.valueOf(args[++i]).intValue(); } else if( arg.equals("-mac") && args.length > (i+1) ) { @@ -690,7 +728,7 @@ public class DBTScanner10 { test.RESET_ADAPTER_EACH_CONN = Integer.valueOf(args[++i]).intValue(); } } - println("Run with '[-default_dev_id <adapter-index>] [-dev_id <adapter-index>] [-btmode LE|BREDR|DUAL] "+ + println("Run with '[-btmode LE|BREDR|DUAL] "+ "[-bluetoothManager <BluetoothManager-Implementation-Class-Name>] "+ "[-disconnect] [-enableGATTPing] [-count <number>] [-single] (-char <uuid>)* [-show_update_events] [-quiet] "+ "[-resetEachCon connectionCount] "+ @@ -714,7 +752,6 @@ public class DBTScanner10 { println("SHOW_UPDATE_EVENTS "+test.SHOW_UPDATE_EVENTS); println("QUIET "+test.QUIET); - println("dev_id "+test.dev_id); println("waitForDevice: "+Arrays.toString(test.waitForDevices.toArray())); println("characteristicList: "+Arrays.toString(test.charIdentifierList.toArray())); diff --git a/java/direct_bt/tinyb/DBTManager.java b/java/direct_bt/tinyb/DBTManager.java index 5f663fcd..b7f05fcd 100644 --- a/java/direct_bt/tinyb/DBTManager.java +++ b/java/direct_bt/tinyb/DBTManager.java @@ -31,6 +31,8 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Predicate; import org.tinyb.BluetoothAdapter; import org.tinyb.BluetoothDevice; @@ -43,6 +45,7 @@ import org.tinyb.BluetoothObject; import org.tinyb.BluetoothManager; import org.tinyb.BluetoothType; import org.tinyb.HCIStatusCode; +import org.tinyb.BluetoothManager.ChangedAdapterSetListener; public class DBTManager implements BluetoothManager { @@ -151,6 +154,8 @@ public class DBTManager implements BluetoothManager private long nativeInstance; private final List<BluetoothAdapter> adapters = new CopyOnWriteArrayList<BluetoothAdapter>(); + private final List<ChangedAdapterSetListener> changedAdapterSetListenerList = new CopyOnWriteArrayList<ChangedAdapterSetListener>(); + private final Settings settings; @Override @@ -277,6 +282,39 @@ public class DBTManager implements BluetoothManager @Override public boolean getDiscovering() throws BluetoothException { return getDefaultAdapter().getDiscovering(); } + @Override + public final void addChangedAdapterSetListener(final ChangedAdapterSetListener l) { + changedAdapterSetListenerList.add(l); + + adapters.forEach(new Consumer<BluetoothAdapter>() { + @Override + public void accept(final BluetoothAdapter adapter) { + changedAdapterSetListenerList.forEach(new Consumer<ChangedAdapterSetListener>() { + @Override + public void accept(final ChangedAdapterSetListener t) { + t.adapterAdded(adapter); + } } ); + } + }); + } + + @Override + public final int removeChangedAdapterSetListener(final ChangedAdapterSetListener l) { + // Using removeIf allows performing test on all object and remove within one write-lock cycle + final int count[] = { 0 }; + changedAdapterSetListenerList.removeIf(new Predicate<ChangedAdapterSetListener>() { + @Override + public boolean test(final ChangedAdapterSetListener t) { + if( t.equals(l) ) { + count[0]++; + return true; + } + return false; + } + }); + return count[0]; + } + private native List<BluetoothAdapter> getAdapterListImpl(); private native BluetoothAdapter getAdapterImpl(int dev_id); @@ -290,6 +328,14 @@ public class DBTManager implements BluetoothManager System.err.println("DBTManager.removeAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+ "]: removed "+a.toString()+", size "+adapters.size()); } + if( 0x0005 == opc_reason ) { + // MgmtEvent::Opcode::INDEX_REMOVED = 0x0005 + changedAdapterSetListenerList.forEach(new Consumer<ChangedAdapterSetListener>() { + @Override + public void accept(final ChangedAdapterSetListener t) { + t.adapterRemoved(a); + } } ); + } return; } } @@ -321,6 +367,14 @@ public class DBTManager implements BluetoothManager System.err.println("DBTManager.updatedAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+ "]: added "+added+": new "+newInstance.toString()+", size "+adapters.size()); } + if( added && 0x0004 == opc_reason ) { + // MgmtEvent::Opcode::INDEX_ADDED = 0x0004 + changedAdapterSetListenerList.forEach(new Consumer<ChangedAdapterSetListener>() { + @Override + public void accept(final ChangedAdapterSetListener t) { + t.adapterAdded(newInstance); + } } ); + } } private native void initImpl(final boolean unifyUUID128Bit, final int btMode) throws BluetoothException; diff --git a/java/org/tinyb/BluetoothManager.java b/java/org/tinyb/BluetoothManager.java index b8754303..2a558805 100644 --- a/java/org/tinyb/BluetoothManager.java +++ b/java/org/tinyb/BluetoothManager.java @@ -75,6 +75,27 @@ public interface BluetoothManager String toString(); } + + /** + * Event listener to receive change events regarding the system's {@link BluetoothAdapter} set, + * e.g. about a removed or added {@link BluetoothAdapter} due to user interaction or 'cold reset'. + * @since 2.0.0 + * @implNote Not implemented on tinyb.dbus + */ + public static interface ChangedAdapterSetListener { + /** + * {@link BluetoothAdapter} was added to the system. + * @param adapter the newly added {@link BluetoothAdapter} to the system + */ + void adapterAdded(final BluetoothAdapter adapter); + + /** + * {@link BluetoothAdapter} was removed from the system. + * @param adapter the removed {@link BluetoothAdapter} from the system + */ + void adapterRemoved(final BluetoothAdapter adapter); + } + /** * Returns this implmentation's {@link Settings}. */ @@ -269,6 +290,26 @@ public interface BluetoothManager public boolean getDiscovering() throws BluetoothException; /** + * Add the given {@link ChangedAdapterSetListener} to this manager. + * <p> + * When a new callback is added, all available adapter's will be reported as added, + * this allows a fully event driven workflow. + * </p> + * @since 2.0.0 + * @implNote Not implemented on tinyb.dbus + */ + void addChangedAdapterSetListener(final ChangedAdapterSetListener l); + + /** + * Remove the given {@link ChangedAdapterSetListener} from this manager. + * @param l the to be removed element + * @return the number of removed elements + * @since 2.0.0 + * @implNote Not implemented on tinyb.dbus + */ + int removeChangedAdapterSetListener(final ChangedAdapterSetListener l); + + /** * Release the native memory associated with this object and all related Bluetooth resources. * The object should not be used following a call to close * <p> diff --git a/java/tinyb/dbus/DBusManager.java b/java/tinyb/dbus/DBusManager.java index 0c87ba49..db7e87f7 100644 --- a/java/tinyb/dbus/DBusManager.java +++ b/java/tinyb/dbus/DBusManager.java @@ -38,6 +38,7 @@ import org.tinyb.BluetoothObject; import org.tinyb.BluetoothManager; import org.tinyb.BluetoothType; import org.tinyb.HCIStatusCode; +import org.tinyb.BluetoothManager.ChangedAdapterSetListener; public class DBusManager implements BluetoothManager { @@ -173,4 +174,10 @@ public class DBusManager implements BluetoothManager public void shutdown() { delete(); } + + @Override + public final void addChangedAdapterSetListener(final ChangedAdapterSetListener l) {} // FIXME + + @Override + public final int removeChangedAdapterSetListener(final ChangedAdapterSetListener l) { return 0; } // FIXME } diff --git a/scripts/run-dbt_scanner10.sh b/scripts/run-dbt_scanner10.sh index b7e1ba51..decd767a 100644 --- a/scripts/run-dbt_scanner10.sh +++ b/scripts/run-dbt_scanner10.sh @@ -11,6 +11,10 @@ # ../scripts/run-dbt_scanner10.sh -wait -wl C0:26:DA:01:DA:B1 2>&1 | tee ~/scanner-h01-dbt10.log # ../scripts/run-dbt_scanner10.sh -wait 2>&1 | tee ~/scanner-h01-dbt10.log # +# To do a BT adapter removal/add via software, assuming the device is '1-4' (Bus 1.Port 4): +# echo '1-4' > /sys/bus/usb/drivers/usb/unbind +# echo '1-4' > /sys/bus/usb/drivers/usb/bind + sdir=`dirname $(readlink -f $0)` rootdir=`dirname $sdir` diff --git a/src/direct_bt/DBTManager.cpp b/src/direct_bt/DBTManager.cpp index 85a6e1ee..e84262e9 100644 --- a/src/direct_bt/DBTManager.cpp +++ b/src/direct_bt/DBTManager.cpp @@ -886,6 +886,7 @@ void DBTManager::clearAllMgmtEventCallbacks() noexcept { for(size_t i=0; i<mgmtAdapterEventCallbackLists.size(); i++) { mgmtAdapterEventCallbackLists[i].clear(); } + mgmtChangedAdapterSetCallbackList.clear(); } void DBTManager::processAdapterAdded(std::shared_ptr<MgmtEvent> e) noexcept { @@ -895,6 +896,9 @@ void DBTManager::processAdapterAdded(std::shared_ptr<MgmtEvent> e) noexcept { const bool added = addAdapterInfo(ai); DBG_PRINT("DBTManager::Adapter[%d] Added %d: %s", dev_id, added, ai->toString().c_str()); sendMgmtEvent(e); + jau::for_each_cow(mgmtChangedAdapterSetCallbackList, [&](ChangedAdapterSetCallback &cb) { + cb.invoke(true /* added */, *ai); + }); } else { DBG_PRINT("DBTManager::Adapter[%d] Added 0: Init failed", dev_id); } @@ -902,6 +906,9 @@ void DBTManager::processAdapterAdded(std::shared_ptr<MgmtEvent> e) noexcept { bool DBTManager::mgmtEvAdapterRemovedCB(std::shared_ptr<MgmtEvent> e) noexcept { DBG_PRINT("DBTManager:mgmt:AdapterRemoved: Start %s", e->toString().c_str()); std::shared_ptr<AdapterInfo> ai = removeAdapterInfo(e->getDevID()); + jau::for_each_cow(mgmtChangedAdapterSetCallbackList, [&](ChangedAdapterSetCallback &cb) { + cb.invoke(false /* added */, *ai); + }); DBG_PRINT("DBTManager:mgmt:AdapterRemoved: End: Removed %s", (nullptr != ai ? ai->toString().c_str() : "none")); return true; } @@ -1008,3 +1015,35 @@ bool DBTManager::mgmtEvUserPasskeyRequestCB(std::shared_ptr<MgmtEvent> e) noexce (void)event; return true; } + +/** + * ChangedAdapterSetCallback handling + */ + +static ChangedAdapterSetCallbackList::equal_comparator _changedAdapterSetCallbackEqComp = + [](const ChangedAdapterSetCallback& a, const ChangedAdapterSetCallback& b) -> bool { return a == b; }; + + +void DBTManager::addChangedAdapterSetCallback(const ChangedAdapterSetCallback & l) { + mgmtChangedAdapterSetCallbackList.push_back(l); +} +int DBTManager::removeChangedAdapterSetCallback(const ChangedAdapterSetCallback & l) { + return mgmtChangedAdapterSetCallbackList.erase_matching(l, true /* all_matching */, _changedAdapterSetCallbackEqComp); +} + +void DBTManager::addChangedAdapterSetCallback(ChangedAdapterSetFunc f) { + addChangedAdapterSetCallback( + ChangedAdapterSetCallback( + jau::bindPlainFunc<bool, bool, const AdapterInfo&>(f) + ) ); + + jau::for_each_cow(adapterInfos, [&](std::shared_ptr<AdapterInfo>& ai) { + jau::for_each_cow(mgmtChangedAdapterSetCallbackList, [&](ChangedAdapterSetCallback &cb) { + cb.invoke(true /* added */, *ai); + }); + }); +} +int DBTManager::removeChangedAdapterSetCallback(ChangedAdapterSetFunc f) { + ChangedAdapterSetCallback l( jau::bindPlainFunc<bool, bool, const AdapterInfo&>(f) ); + return mgmtChangedAdapterSetCallbackList.erase_matching(l, true /* all_matching */, _changedAdapterSetCallbackEqComp); +} |