Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
DBTClient01.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25package trial.org.direct_bt;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Iterator;
30import java.util.List;
31import java.util.concurrent.atomic.AtomicInteger;
32
33import org.direct_bt.AdapterSettings;
34import org.direct_bt.AdapterStatusListener;
35import org.direct_bt.BTMode;
36import org.direct_bt.BTSecurityLevel;
37import org.direct_bt.BTAdapter;
38import org.direct_bt.BTDevice;
39import org.direct_bt.BTDeviceRegistry;
40import org.direct_bt.BTGattChar;
41import org.direct_bt.BTGattCharListener;
42import org.direct_bt.BTGattCmd;
43import org.direct_bt.BTGattDesc;
44import org.direct_bt.BTGattService;
45import org.direct_bt.BTSecurityRegistry;
46import org.direct_bt.BTUtils;
47import org.direct_bt.DiscoveryPolicy;
48import org.direct_bt.EIRDataTypeSet;
49import org.direct_bt.EInfoReport;
50import org.direct_bt.GattCharPropertySet;
51import org.direct_bt.HCIStatusCode;
52import org.direct_bt.LE_Features;
53import org.direct_bt.LE_PHYs;
54import org.direct_bt.PairingMode;
55import org.direct_bt.SMPIOCapability;
56import org.direct_bt.SMPKeyBin;
57import org.direct_bt.SMPPairingState;
58import org.direct_bt.ScanType;
59import org.jau.io.PrintUtil;
60import org.jau.net.EUI48;
61import org.jau.sys.Clock;
62import org.jau.util.BasicTypes;
63import org.junit.Assert;
64
65/**
66 * This central BTRole::Master participant works with DBTServer00.
67 */
68public class DBTClient01 implements DBTClientTest {
69 private final boolean GATT_VERBOSE = false;
70
71 private final long timestamp_t0 = Clock.startupTimeMillis();
72
73 private final String adapterShortName = "TDev2Clt";
74
75 private final MyAdapterStatusListener myAdapterStatusListener = new MyAdapterStatusListener();
76
77 private final byte cmd_arg = (byte)0x44;
78
79 private String adapterName = "TestDev2_Clt";
80 private EUI48 useAdapter = EUI48.ALL_DEVICE;
81 private BTMode btMode = BTMode.DUAL;
82 private BTAdapter clientAdapter = null;
83
84 private boolean do_disconnect = true;
85
86 private boolean do_remove_device = false;
87
88 private DiscoveryPolicy discoveryPolicy = DiscoveryPolicy.PAUSE_CONNECTED_UNTIL_READY; // default value
89
90 private final AtomicInteger disconnectCount = new AtomicInteger(0);
91 private final AtomicInteger measurementsLeft = new AtomicInteger(1);
92 private final AtomicInteger deviceReadyCount = new AtomicInteger(0);
93 private final AtomicInteger notificationsReceived = new AtomicInteger(0);
94 private final AtomicInteger indicationsReceived = new AtomicInteger(0);
95 private final AtomicInteger completedGATTCommands = new AtomicInteger(0);
96 private final AtomicInteger completedMeasurementsTotal = new AtomicInteger(0);
97 private final AtomicInteger completedMeasurementsSuccess = new AtomicInteger(0);
98
99 private final boolean do_disconnect_randomly;
100
101 public DBTClient01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean do_disconnect_randomly) {
102 this.adapterName = adapterName;
103 this.useAdapter = useAdapter;
104 this.btMode = btMode;
105 this.do_disconnect_randomly = do_disconnect_randomly;
106 }
107 public DBTClient01(final String adapterName, final EUI48 useAdapter, final BTMode btMode) {
108 this(adapterName, useAdapter, btMode, false /* do_disconnect_randomly */);
109 }
110
111 @Override
112 public String getName() { return adapterName; }
113
114 @Override
115 public void setAdapter(final BTAdapter clientAdapter) {
116 this.clientAdapter = clientAdapter;
117 }
118 @Override
119 public BTAdapter getAdapter() { return clientAdapter; }
120
121 @Override
122 public void setProtocolSessionsLeft(final int v) {
123 measurementsLeft.set(v);
124 }
125 @Override
127 return measurementsLeft.get();
128 }
129 @Override
131 return completedMeasurementsTotal.get();
132 }
133 @Override
135 return completedMeasurementsSuccess.get();
136 }
137 @Override
138 public int getDisconnectCount() {
139 return this.disconnectCount.get();
140 }
141
142 @Override
143 public void setDiscoveryPolicy(final DiscoveryPolicy v) {
144 discoveryPolicy = v;
145 }
146 @Override
147 public void setDisconnectDeviceed(final boolean v) {
148 do_disconnect = v;
149 }
150 @Override
151 public void setRemoveDevice(final boolean v) {
152 do_remove_device = v;
153 }
154
155 static void executeOffThread(final Runnable runobj, final String threadName, final boolean detach) {
156 final Thread t = new Thread( runobj, threadName );
157 if( detach ) {
158 t.setDaemon(true); // detach thread
159 }
160 t.start();
161 }
162 static void execute(final Runnable task, final boolean offThread) {
163 if( offThread ) {
164 final Thread t = new Thread(task);
165 t.setDaemon(true);
166 t.start();
167 } else {
168 task.run();
169 }
170 }
171
172 class MyAdapterStatusListener extends AdapterStatusListener {
173 @Override
174 public void adapterSettingsChanged(final BTAdapter adapter, final AdapterSettings oldmask,
175 final AdapterSettings newmask, final AdapterSettings changedmask, final long timestamp) {
176 final boolean initialSetting = oldmask.isEmpty();
177 if( initialSetting ) {
178 PrintUtil.println(System.err, "****** Client SETTINGS_INITIAL: "+oldmask+" -> "+newmask+", changed "+changedmask);
179 } else {
180 PrintUtil.println(System.err, "****** Client SETTINGS_CHANGED: "+oldmask+" -> "+newmask+", changed "+changedmask);
181
182 final boolean justPoweredOn = changedmask.isSet(AdapterSettings.SettingType.POWERED) &&
183 newmask.isSet(AdapterSettings.SettingType.POWERED);
184 if( justPoweredOn && DiscoveryPolicy.AUTO_OFF != discoveryPolicy && clientAdapter.equals(adapter) ) {
185 executeOffThread( () -> { startDiscovery("powered_on"); },
186 "Client DBT-StartDiscovery-"+adapter.getAddressAndType(), true /* detach */);
187 }
188 }
189 PrintUtil.println(System.err, "Client Status Adapter:");
190 PrintUtil.println(System.err, adapter.toString());
191 }
192
193 @Override
194 public void discoveringChanged(final BTAdapter adapter, final ScanType currentMeta, final ScanType changedType, final boolean changedEnabled, final DiscoveryPolicy policy, final long timestamp) {
195 PrintUtil.println(System.err, "****** Client DISCOVERING: meta "+currentMeta+", changed["+changedType+", enabled "+changedEnabled+", policy "+policy+"] on "+adapter);
196 }
197
198 @Override
199 public boolean deviceFound(final BTDevice device, final long timestamp) {
200 if( BTDeviceRegistry.isWaitingForDevice(device.getAddressAndType().address, device.getName()) &&
201 0 < measurementsLeft.get()
202 )
203 {
204 PrintUtil.println(System.err, "****** Client FOUND__-0: Connecting "+device.toString());
205 {
206 final long td = Clock.currentTimeMillis() - timestamp_t0; // adapter-init -> now
207 PrintUtil.println(System.err, "PERF: adapter-init -> FOUND__-0 " + td + " ms");
208 }
209 executeOffThread( () -> { connectDiscoveredDevice(device); },
210 "Client DBT-Connect-"+device.getAddressAndType(), true /* detach */);
211 return true;
212 } else {
213 PrintUtil.println(System.err, "****** Client FOUND__-1: NOP "+device.toString());
214 return false;
215 }
216 }
217
218 @Override
219 public void deviceUpdated(final BTDevice device, final EIRDataTypeSet updateMask, final long timestamp) {
220 }
221
222 @Override
223 public void deviceConnected(final BTDevice device, final boolean discovered, final long timestamp) {
224 PrintUtil.println(System.err, "****** Client CONNECTED (discovered "+discovered+"): "+device.toString());
225 }
226
227 @Override
228 public void devicePairingState(final BTDevice device, final SMPPairingState state, final PairingMode mode, final long timestamp) {
229 PrintUtil.println(System.err, "****** Client PAIRING_STATE: state "+state+", mode "+mode+": "+device);
230 switch( state ) {
231 case NONE:
232 // next: deviceReady(..)
233 break;
234 case FAILED: {
235 final boolean res = SMPKeyBin.remove(DBTConstants.CLIENT_KEY_PATH, device);
236 PrintUtil.println(System.err, "****** Client PAIRING_STATE: state "+state+"; Remove key file "+SMPKeyBin.getFilename(DBTConstants.CLIENT_KEY_PATH, device)+", res "+res);
237 // next: deviceReady() or deviceDisconnected(..)
238 } break;
239 case REQUESTED_BY_RESPONDER:
240 // next: FEATURE_EXCHANGE_STARTED
241 break;
242 case FEATURE_EXCHANGE_STARTED:
243 // next: FEATURE_EXCHANGE_COMPLETED
244 break;
245 case FEATURE_EXCHANGE_COMPLETED:
246 // next: PASSKEY_EXPECTED... or KEY_DISTRIBUTION
247 break;
248 case PASSKEY_EXPECTED: {
249 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
250 if( null != sec && sec.getPairingPasskey() != BTSecurityRegistry.NO_PASSKEY ) {
251 executeOffThread( () -> { device.setPairingPasskey( sec.getPairingPasskey() ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
252 } else {
253 executeOffThread( () -> { device.setPairingPasskey( 0 ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
254 // 3s disconnect: executeOffThread( () -> { device.setPairingPasskeyNegative(); }, "DBT-SetPasskeyNegative-"+device.getAddressAndType(), true /* detach */);
255 }
256 // next: KEY_DISTRIBUTION or FAILED
257 } break;
258 case NUMERIC_COMPARE_EXPECTED: {
259 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
260 if( null != sec ) {
261 executeOffThread( () -> { device.setPairingNumericComparison( sec.getPairingNumericComparison() ); }, "DBT-SetNumericComp-"+device.getAddressAndType(), true /* detach */);
262 } else {
263 executeOffThread( () -> { device.setPairingNumericComparison( false ); }, "DBT-SetNumericCompFalse-"+device.getAddressAndType(), true /* detach */);
264 }
265 // next: KEY_DISTRIBUTION or FAILED
266 } break;
267 case OOB_EXPECTED:
268 // FIXME: ABORT
269 break;
270 case KEY_DISTRIBUTION:
271 // next: COMPLETED or FAILED
272 break;
273 case COMPLETED:
274 // next: deviceReady(..)
275 break;
276 default: // nop
277 break;
278 }
279 }
280
281 private void disconnectDeviceRandomly(final BTDevice device) {
282 // sleep range: 100 - 1500 ms
283 final int sleep_min = 100;
284 final int sleep_max = 1500;
285 final int sleep_dur = (int) ( Math.random() * ( sleep_max - sleep_min + 1 ) + sleep_min );
286 try {
287 Thread.sleep(sleep_dur); // wait a little (FIXME: Fast restart of advertising error)
288 } catch (final InterruptedException e) { }
289
290 PrintUtil.fprintf_td(System.err, "****** Client i470 disconnectDevice(delayed %d ms): client %s\n", sleep_dur, device.toString());
291 device.disconnect();
292 }
293
294 @Override
295 public void deviceReady(final BTDevice device, final long timestamp) {
296 {
297 deviceReadyCount.incrementAndGet();
298 PrintUtil.println(System.err, "****** Client READY-0: Processing["+deviceReadyCount.get()+"] "+device.toString());
299 {
300 final long td = Clock.currentTimeMillis() - timestamp_t0; // adapter-init -> now
301 PrintUtil.println(System.err, "PERF: adapter-init -> READY-0 " + td + " ms");
302 }
303
304 // Be nice to Test* case, allowing to reach its own listener.deviceReady() added later
305 executeOffThread( () -> { processReadyDevice(device); },
306 "DBT-Process1-"+device.getAddressAndType(), true /* detach */);
307 // processReadyDevice(device); // AdapterStatusListener::deviceReady() explicitly allows prolonged and complex code execution!
308
309 if( do_disconnect_randomly ) {
310 executeOffThread( () -> { disconnectDeviceRandomly(device); },
311 "DBT-Disconnect-"+device.getAddressAndType(), true /* detach */);
312 }
313 }
314 }
315
316 @Override
317 public void deviceDisconnected(final BTDevice device, final HCIStatusCode reason, final short handle, final long timestamp) {
318 PrintUtil.println(System.err, "****** Client DISCONNECTED: Reason "+reason+", old handle 0x"+Integer.toHexString(handle)+": "+device+" on "+device.getAdapter());
319
320 disconnectCount.addAndGet(1);
321
322 executeOffThread( () -> { removeDevice(device); }, "Client DBT-Remove-"+device.getAddressAndType(), true /* detach */);
323 }
324
325 @Override
326 public String toString() {
327 return "Client AdapterStatusListener[user, per-adapter]";
328 }
329 };
330
331 class MyGATTEventListener extends BTGattCharListener {
332 @Override
333 public void notificationReceived(final BTGattChar charDecl,
334 final byte[] value, final long timestamp) {
335 if( GATT_VERBOSE ) {
336 final long tR = Clock.currentTimeMillis();
337 PrintUtil.fprintf_td(System.err, "** Characteristic-Notify: UUID %s, td %d ******\n",
338 charDecl.getUUID(), (tR-timestamp));
339 PrintUtil.fprintf_td(System.err, "** Characteristic: %s ******\n", charDecl.toString());
340 PrintUtil.fprintf_td(System.err, "** Value R: size %d, ro: %s ******\n", value.length, BasicTypes.bytesHexString(value, 0, -1, true));
341 PrintUtil.fprintf_td(System.err, "** Value S: %s ******\n", BTUtils.decodeUTF8String(value, 0, value.length));
342 }
343 notificationsReceived.incrementAndGet();
344 }
345
346 @Override
347 public void indicationReceived(final BTGattChar charDecl,
348 final byte[] value, final long timestamp, final boolean confirmationSent) {
349 if( GATT_VERBOSE ) {
350 final long tR = Clock.currentTimeMillis();
351 PrintUtil.fprintf_td(System.err, "** Characteristic-Indication: UUID %s, td %d, confirmed %b ******\n",
352 charDecl.getUUID(), (tR-timestamp), confirmationSent);
353 PrintUtil.fprintf_td(System.err, "** Characteristic: %s ******\n", charDecl.toString());
354 PrintUtil.fprintf_td(System.err, "** Value R: size %d, ro: %s ******\n", value.length, BasicTypes.bytesHexString(value, 0, -1, true));
355 PrintUtil.fprintf_td(System.err, "** Value S: %s ******\n", BTUtils.decodeUTF8String(value, 0, value.length));
356 }
357 indicationsReceived.incrementAndGet();
358 }
359 }
360
361 private void resetLastProcessingStats() {
362 completedGATTCommands.set(0);
363 notificationsReceived.set(0);
364 indicationsReceived.set(0);
365 }
366
367 private void connectDiscoveredDevice(final BTDevice device) {
368 PrintUtil.println(System.err, "****** Client Connecting Device: Start " + device.toString());
369
370 resetLastProcessingStats();
371
372 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, device.getName());
373 if( null != sec ) {
374 PrintUtil.println(System.err, "****** Client Connecting Device: Found SecurityDetail "+sec.toString()+" for "+device.toString());
375 } else {
376 PrintUtil.println(System.err, "****** Client Connecting Device: No SecurityDetail for "+device.toString());
377 }
378 final BTSecurityLevel req_sec_level = null != sec ? sec.getSecLevel() : BTSecurityLevel.UNSET;
379 HCIStatusCode res = device.uploadKeys(DBTConstants.CLIENT_KEY_PATH, req_sec_level, true /* verbose_ */);
380 PrintUtil.fprintf_td(System.err, "****** Client Connecting Device: BTDevice::uploadKeys(...) result %s\n", res.toString());
381 if( HCIStatusCode.SUCCESS != res ) {
382 if( null != sec ) {
383 if( sec.isSecurityAutoEnabled() ) {
384 final boolean r = device.setConnSecurityAuto( sec.getSecurityAutoIOCap() );
385 PrintUtil.println(System.err, "****** Client Connecting Device: Using SecurityDetail.SEC AUTO "+sec+" -> set OK "+r);
386 } else if( sec.isSecLevelOrIOCapSet() ) {
387 final boolean r = device.setConnSecurity(sec.getSecLevel(), sec.getIOCap());
388 PrintUtil.println(System.err, "****** Client Connecting Device: Using SecurityDetail.Level+IOCap "+sec+" -> set OK "+r);
389 } else {
390 final boolean r = device.setConnSecurityAuto( SMPIOCapability.KEYBOARD_ONLY );
391 PrintUtil.println(System.err, "****** Client Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY ("+sec+") -> set OK "+r);
392 }
393 } else {
394 final boolean r = device.setConnSecurityAuto( SMPIOCapability.KEYBOARD_ONLY );
395 PrintUtil.println(System.err, "****** Client Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY -> set OK "+r);
396 }
397 }
398 final EInfoReport eir = device.getEIR();
399 PrintUtil.println(System.err, "Client EIR-1 "+device.getEIRInd().toString());
400 PrintUtil.println(System.err, "Client EIR-2 "+device.getEIRScanRsp().toString());
401 PrintUtil.println(System.err, "Client EIR-+ "+eir.toString());
402
403 short conn_interval_min = (short)8; // 10ms
404 short conn_interval_max = (short)12; // 15ms
405 final short conn_latency = (short)0;
406 if( eir.isSet(EIRDataTypeSet.DataType.CONN_IVAL) ) {
407 final short[] minmax = new short[2];
408 eir.getConnInterval(minmax);
409 conn_interval_min = minmax[0];
410 conn_interval_max = minmax[1];
411 }
412 final short supervision_timeout = BTUtils.getHCIConnSupervisorTimeout(conn_latency, (int) ( conn_interval_max * 1.25 ) /* ms */);
413 res = device.connectLE(le_scan_interval, le_scan_window, conn_interval_min, conn_interval_max, conn_latency, supervision_timeout);
414 // res = device.connectDefault();
415 PrintUtil.println(System.err, "****** Client Connecting Device Command, res "+res+": End result "+res+" of " + device.toString());
416 }
417
418 private void processReadyDevice(final BTDevice device) {
419 PrintUtil.println(System.err, "****** Client Processing Ready Device: Start " + device.toString());
420 final long t1 = Clock.currentTimeMillis();
421
422 SMPKeyBin.createAndWrite(device, DBTConstants.CLIENT_KEY_PATH, true /* verbose */);
423 final long t2 = Clock.currentTimeMillis();
424
425 boolean success = false;
426
427 {
428 final LE_PHYs resTx[] = { new LE_PHYs() };
429 final LE_PHYs resRx[] = { new LE_PHYs() };
430 final HCIStatusCode res = device.getConnectedLE_PHY(resTx, resRx);
431 PrintUtil.fprintf_td(System.err, "****** Client Got Connected LE PHY: status %s: Tx %s, Rx %s\n",
432 res.toString(), resTx[0].toString(), resRx[0].toString());
433 }
434 final long t3 = Clock.currentTimeMillis();
435
436 //
437 // GATT Service Processing
438 //
439 try {
440 final List<BTGattService> primServices = device.getGattServices();
441 if( null == primServices || 0 == primServices.size() ) {
442 // Cheating the flow, but avoiding: goto, do-while-false and lastly unreadable intendations
443 // And it is an error case nonetheless ;-)
444 throw new RuntimeException("Processing Ready Device: getServices() failed " + device.toString());
445 }
446 final long t5 = Clock.currentTimeMillis();
447 {
448 final long td00 = device.getLastDiscoveryTimestamp() - timestamp_t0; // adapter-init to discovered
449 final long td01 = t1 - timestamp_t0; // adapter-init to processing-start
450 final long td05 = t5 - timestamp_t0; // adapter-init -> gatt-complete
451 final long tdc1 = t1 - device.getLastDiscoveryTimestamp(); // discovered to processing-start
452 final long tdc5 = t5 - device.getLastDiscoveryTimestamp(); // discovered to gatt-complete
453 final long td12 = t2 - t1; // SMPKeyBin
454 final long td23 = t3 - t2; // LE_PHY
455 final long td13 = t3 - t1; // SMPKeyBin + LE_PHY
456 final long td35 = t5 - t3; // get-gatt-services
457 PrintUtil.println(System.err, System.lineSeparator()+System.lineSeparator());
458 PrintUtil.println(System.err, "PERF: GATT primary-services completed"+System.lineSeparator()+
459 "PERF: adapter-init to discovered " + td00 + " ms,"+System.lineSeparator()+
460 "PERF: adapter-init to processing-start " + td01 + " ms,"+System.lineSeparator()+
461 "PERF: adapter-init to gatt-complete " + td05 + " ms,"+System.lineSeparator()+
462 "PERF: discovered to processing-start " + tdc1 + " ms,"+System.lineSeparator()+
463 "PERF: discovered to gatt-complete " + tdc5 + " ms,"+System.lineSeparator()+
464 "PERF: SMPKeyBin + LE_PHY " + td13 + " ms (SMPKeyBin "+td12+"ms, LE_PHY "+td23+"ms), "+System.lineSeparator()+
465 "PERF: get-gatt-services " + td35 + " ms,"+System.lineSeparator());
466 }
467
468 {
469 final BTGattCmd cmd = new BTGattCmd(device, "TestCmd", null /* service_uuid */, DBTConstants.CommandUUID, DBTConstants.ResponseUUID);
470 cmd.setVerbose(true);
471 final boolean cmd_resolved = cmd.isResolved();
472 PrintUtil.println(System.err, "Client Command test: "+cmd.toString()+", resolved "+cmd_resolved);
473 final byte[] cmd_data = { cmd_arg };
474 final HCIStatusCode cmd_res = cmd.send(true /* prefNoAck */, cmd_data, 3000 /* timeoutMS */);
475 if( HCIStatusCode.SUCCESS == cmd_res ) {
476 final byte[] resp = cmd.getResponse();
477 if( 1 == resp.length && resp[0] == cmd_arg ) {
478 PrintUtil.fprintf_td(System.err, "Client Success: %s -> %s (echo response)\n", cmd.toString(), BasicTypes.bytesHexString(resp, 0, resp.length, true /* lsb */));
479 completedGATTCommands.incrementAndGet();
480 } else {
481 PrintUtil.fprintf_td(System.err, "Client Failure: %s -> %s (different response)\n", cmd.toString(), BasicTypes.bytesHexString(resp, 0, resp.length, true /* lsb */));
482 }
483 } else {
484 PrintUtil.fprintf_td(System.err, "Client Failure: %s -> %s\n", cmd.toString(), cmd_res.toString());
485 }
486 cmd.close();
487 }
488
489 boolean gattListenerError = false;
490 final List<BTGattCharListener> gattListener = new ArrayList<BTGattCharListener>();
491 int loop = 0;
492 do {
493 try {
494 int i=0;
495 for(final Iterator<BTGattService> srvIter = primServices.iterator(); srvIter.hasNext(); i++) {
496 final BTGattService primService = srvIter.next();
497 if( GATT_VERBOSE ) {
498 PrintUtil.fprintf_td(System.err, " [%02d] Service UUID %s\n", i, primService.getUUID());
499 PrintUtil.fprintf_td(System.err, " [%02d] %s\n", i, primService.toString());
500 }
501 int j=0;
502 final List<BTGattChar> serviceCharacteristics = primService.getChars();
503 for(final Iterator<BTGattChar> charIter = serviceCharacteristics.iterator(); charIter.hasNext(); j++) {
504 final BTGattChar serviceChar = charIter.next();
505 if( GATT_VERBOSE ) {
506 PrintUtil.fprintf_td(System.err, " [%02d.%02d] Characteristic: UUID %s\n", i, j, serviceChar.getUUID());
507 PrintUtil.fprintf_td(System.err, " [%02d.%02d] %s\n", i, j, serviceChar.toString());
508 }
509 final GattCharPropertySet properties = serviceChar.getProperties();
510 if( properties.isSet(GattCharPropertySet.Type.Read) ) {
511 final byte[] value = serviceChar.readValue();
512 final String svalue = BTUtils.decodeUTF8String(value, 0, value.length);
513 if( GATT_VERBOSE ) {
514 PrintUtil.fprintf_td(System.err, " [%02d.%02d] value: %s ('%s')\n", i, j, BasicTypes.bytesHexString(value, 0, -1, true), svalue);
515 }
516 }
517 int k=0;
518 final List<BTGattDesc> charDescList = serviceChar.getDescriptors();
519 for(final Iterator<BTGattDesc> descIter = charDescList.iterator(); descIter.hasNext(); k++) {
520 final BTGattDesc charDesc = descIter.next();
521 if( GATT_VERBOSE ) {
522 PrintUtil.fprintf_td(System.err, " [%02d.%02d.%02d] Descriptor: UUID %s\n", i, j, k, charDesc.getUUID());
523 PrintUtil.fprintf_td(System.err, " [%02d.%02d.%02d] %s\n", i, j, k, charDesc.toString());
524 }
525 }
526 if( 0 == loop ) {
527 final boolean cccdEnableResult[] = { false, false };
528 if( serviceChar.enableNotificationOrIndication( cccdEnableResult ) ) {
529 // ClientCharConfigDescriptor (CCD) is available
530 final MyGATTEventListener gattEventListener = new MyGATTEventListener();
531 final boolean clAdded = serviceChar.addCharListener( gattEventListener );
532 if( clAdded ) {
533 gattListener.add(gattEventListener);
534 } else {
535 gattListenerError = true;
536 PrintUtil.fprintf_td(System.err, "Client Error: Failed to add GattListener: %s @ %s, gattListener %d\n",
537 gattEventListener.toString(), serviceChar.toString(), gattListener.size());
538 }
539 if( GATT_VERBOSE ) {
540 PrintUtil.fprintf_td(System.err, " [%02d.%02d] Characteristic-Listener: Notification(%b), Indication(%b): Added %b\n",
541 i, j, cccdEnableResult[0], cccdEnableResult[1], clAdded);
542 PrintUtil.fprintf_td(System.err, "\n");
543 }
544 }
545 }
546 }
547 if( GATT_VERBOSE ) {
548 PrintUtil.fprintf_td(System.err, "\n");
549 }
550 }
551 success = notificationsReceived.get() >= 2 || indicationsReceived.get() >= 2;
552 ++loop;
553 } catch( final Exception ex) {
554 PrintUtil.println(System.err, "****** Client Processing Ready Device: Exception.2 caught for " + device.toString() + ": "+ex.getMessage());
555 ex.printStackTrace();
556 }
557 } while( !success && device.getConnected() && !gattListenerError );
558
559 success = success && completedGATTCommands.get() >= 1;
560
561 if( gattListenerError ) {
562 success = false;
563 }
564 {
565 int i = 0;
566 for(final BTGattCharListener gcl : gattListener) {
567 if( !device.removeCharListener(gcl) ) {
568 PrintUtil.fprintf_td(System.err, "Client Error: Failed to remove GattListener[%d/%d]: %s @ %s\n",
569 i, gattListener.size(), gcl.toString(), device.toString());
570 success = false;
571 }
572 ++i;
573 }
574 }
575
576 if( device.getConnected() ) {
577 // Tell server we have successfully completed the test.
578 final BTGattCmd cmd = new BTGattCmd(device, "FinalHandshake", null /* service_uuid */, DBTConstants.CommandUUID, DBTConstants.ResponseUUID);
579 cmd.setVerbose(true);
580 final boolean cmd_resolved = cmd.isResolved();
581 PrintUtil.println(System.err, "Client FinalCommand test: "+cmd.toString()+", resolved "+cmd_resolved);
582 final byte[] cmd_data = success ? DBTConstants.SuccessHandshakeCommandData : DBTConstants.FailHandshakeCommandData;
583 final HCIStatusCode cmd_res = cmd.send(true /* prefNoAck */, cmd_data, 3000 /* timeoutMS */);
584 if( HCIStatusCode.SUCCESS == cmd_res ) {
585 final byte[] resp = cmd.getResponse();
586 if( Arrays.equals(cmd_data, resp) ) {
587 PrintUtil.fprintf_td(System.err, "Client Success: %s -> %s (echo response)\n", cmd.toString(), BasicTypes.bytesHexString(resp, 0, resp.length, true /* lsb */));
588 } else {
589 PrintUtil.fprintf_td(System.err, "Client Failure: %s -> %s (different response)\n", cmd.toString(), BasicTypes.bytesHexString(resp, 0, resp.length, true /* lsb */));
590 success = false;
591 }
592 } else {
593 PrintUtil.fprintf_td(System.err, "Client Failure: %s -> %s\n", cmd.toString(), cmd_res.toString());
594 success = false;
595 }
596 cmd.close();
597 }
598 } catch (final Throwable t ) {
599 PrintUtil.println(System.err, "****** Client Processing Ready Device: Exception.2 caught for " + device.toString() + ": "+t.getMessage());
600 t.printStackTrace();
601 }
602
603 PrintUtil.println(System.err, "****** Client Processing Ready Device: End-1: Success " + success + " on " + device.toString());
604
605 if( DiscoveryPolicy.PAUSE_CONNECTED_UNTIL_DISCONNECTED == discoveryPolicy ) {
606 device.getAdapter().removeDevicePausingDiscovery(device);
607 }
608
609 PrintUtil.println(System.err, "****** Client Processing Ready Device: End-2: Success " + success + " on " + device.toString());
610 device.removeAllCharListener();
611
612 if( do_disconnect ) {
613 if( do_remove_device ) {
614 device.remove();
615 } else {
616 device.disconnect();
617 }
618 }
619
620 completedMeasurementsTotal.addAndGet(1);
621 if( success ) {
622 completedMeasurementsSuccess.addAndGet(1);
623 if( 0 < measurementsLeft.get() ) {
624 measurementsLeft.decrementAndGet();
625 }
626 }
627 PrintUtil.println(System.err, "****** Client Processing Ready Device: Success "+success+
628 "; Measurements completed "+completedMeasurementsSuccess.get()+
629 ", left "+measurementsLeft.get()+
630 "; Received notitifications "+notificationsReceived.get()+", indications "+indicationsReceived.get()+
631 "; Completed GATT commands "+completedGATTCommands.get()+
632 ": "+device.getAddressAndType());
633 }
634
635 private void removeDevice(final BTDevice device) {
636 PrintUtil.println(System.err, "****** Client Remove Device: removing: "+device.getAddressAndType());
637
638 if( do_remove_device ) {
639 device.remove();
640 }
641 }
642
643 static final boolean le_scan_active = true; // default value
644 static final short le_scan_interval = (short)24; // 15ms, default value
645 static final short le_scan_window = (short)24; // 15ms, default value
646 static final byte filter_policy = (byte)0; // default value
647 static final boolean filter_dup = true; // default value
648
649 @Override
650 public HCIStatusCode startDiscovery(final String msg) {
651 resetLastProcessingStats();
652
653 final HCIStatusCode status = clientAdapter.startDiscovery(null, discoveryPolicy, le_scan_active, le_scan_interval, le_scan_window, filter_policy, filter_dup );
654 PrintUtil.println(System.err, "****** Client Start discovery ("+msg+") result: "+status);
655 return status;
656 }
657
658 @Override
659 public HCIStatusCode stopDiscovery(final String msg) {
660 final HCIStatusCode status = clientAdapter.stopDiscovery();
661 PrintUtil.println(System.err, "****** Client Stop discovery ("+msg+") result: "+status);
662 return status;
663 }
664
665 @Override
666 public void close(final String msg) {
667 PrintUtil.println(System.err, "****** Client Close: "+msg);
668 clientAdapter.stopDiscovery();
669 Assert.assertTrue( clientAdapter.removeStatusListener( myAdapterStatusListener ) );
670 }
671
672 @Override
673 public boolean initAdapter(final BTAdapter adapter) {
674 if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
675 PrintUtil.fprintf_td(System.err, "initClientAdapter: Adapter not selected: %s\n", adapter.toString());
676 return false;
677 }
678 adapterName = adapterName + "-" + adapter.getAddressAndType().address.toString().replace(":", "");
679
680 // Initialize with defaults and power-on
681 if( !adapter.isInitialized() ) {
682 final HCIStatusCode status = adapter.initialize( btMode, false );
683 if( HCIStatusCode.SUCCESS != status ) {
684 PrintUtil.fprintf_td(System.err, "initClientAdapter: Adapter initialization failed: %s: %s\n",
685 status.toString(), adapter.toString());
686 return false;
687 }
688 } else if( !adapter.setPowered( false ) ) {
689 PrintUtil.fprintf_td(System.err, "initClientAdapter: Already initialized adapter power-off failed:: %s\n", adapter.toString());
690 return false;
691 }
692 // adapter is powered-off
693 PrintUtil.fprintf_td(System.err, "initClientAdapter.1: %s\n", adapter.toString());
694
695 {
696 final HCIStatusCode status = adapter.setName(adapterName, adapterShortName);
697 if( HCIStatusCode.SUCCESS == status ) {
698 PrintUtil.fprintf_td(System.err, "initClientAdapter: setLocalName OK: %s\n", adapter.toString());
699 } else {
700 PrintUtil.fprintf_td(System.err, "initClientAdapter: setLocalName failed: %s\n", adapter.toString());
701 return false;
702 }
703 if( !adapter.setPowered( true ) ) {
704 PrintUtil.fprintf_td(System.err, "initClientAdapter: setPower.2 on failed: %s\n", adapter.toString());
705 return false;
706 }
707 }
708 // adapter is powered-on
709 PrintUtil.println(System.err, "initClientAdapter.2: "+adapter.toString());
710
711 {
712 final LE_Features le_feats = adapter.getLEFeatures();
713 PrintUtil.fprintf_td(System.err, "initClientAdapter: LE_Features %s\n", le_feats.toString());
714 }
715 if( adapter.getBTMajorVersion() > 4 ) {
716 // BT5 specific
717 final LE_PHYs Tx = new LE_PHYs(LE_PHYs.PHY.LE_2M);
718 final LE_PHYs Rx = new LE_PHYs(LE_PHYs.PHY.LE_2M);
719
720 final HCIStatusCode res = adapter.setDefaultLE_PHY(Tx, Rx);
721 PrintUtil.fprintf_td(System.err, "initClientAdapter: Set Default LE PHY: status %s: Tx %s, Rx %s\n",
722 res.toString(), Tx.toString(), Rx.toString());
723 }
724 Assert.assertTrue( adapter.addStatusListener( myAdapterStatusListener ) );
725
726 return true;
727 }
728}
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2021 Gothel Software e.K.
static final String CLIENT_KEY_PATH
LE Link Layer Feature Set (bitmask)
LE Transport PHY bit values (bitmask)
Definition: LE_PHYs.java:36
This central BTRole::Master participant works with DBTServer00.
String getName()
Return name of this endpoint, which becomes the adapter's name.
void setRemoveDevice(final boolean v)
Set remove device when disconnecting.
void setAdapter(final BTAdapter clientAdapter)
Set the server adapter for this endpoint.
DBTClient01(final String adapterName, final EUI48 useAdapter, final BTMode btMode)
void setDisconnectDeviceed(final boolean v)
Set disconnect after processing.
void setDiscoveryPolicy(final DiscoveryPolicy v)
Set DiscoveryPolicy.
boolean initAdapter(final BTAdapter adapter)
Initialize the given adapter for this endpoint.
HCIStatusCode stopDiscovery(final String msg)
HCIStatusCode startDiscovery(final String msg)
void setProtocolSessionsLeft(final int v)
void close(final String msg)
BTAdapter getAdapter()
Return the adapter for this endpoint.
DBTClient01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean do_disconnect_randomly)
Bluetooth adapter operating mode.
Definition: BTMode.java:34
DUAL
Dual Bluetooth mode, i.e.
Definition: BTMode.java:38
Discovery policy defines the BTAdapter discovery mode after connecting a remote BTDevice:
PAUSE_CONNECTED_UNTIL_READY
Pause discovery until all connected BTDevice reach readiness inclusive optional SMP pairing (~120ms) ...
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Each enum represents a 'LE Transport PHY' bit value.
Definition: LE_PHYs.java:43
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.java:48
HCIStatusCode stopDiscovery()
Turns off device discovery if it is enabled.
HCIStatusCode startDiscovery()
Starts discovery using all default arguments, see startDiscovery(DiscoveryPolicy, boolean,...
boolean isInitialized()
Returns true, if initialize(BTMode) has already been called for this adapter, otherwise false.
LE_Features getLEFeatures()
Return LE_Features for this controller.
boolean setPowered(final boolean power_on)
Sets the power state the adapter.
boolean equals(final Object obj)
If both types are of BTAdapter, it compares their BDAddressAndType, see getAddressAndType().
boolean addStatusListener(final AdapterStatusListener listener)
Add the given AdapterStatusListener to the list if not already present.
HCIStatusCode setDefaultLE_PHY(final LE_PHYs Tx, final LE_PHYs Rx)
Sets default preference of LE_PHYs.
BDAddressAndType getAddressAndType()
Returns the adapter's public BDAddressAndType.
HCIStatusCode setName(String name, String short_name)
Sets the name and short-name.
boolean removeStatusListener(final AdapterStatusListener l)
Remove the given AdapterStatusListener from the list.
int getBTMajorVersion()
Returns the Bluetooth major version of this adapter.
HCIStatusCode initialize(final BTMode btMode, boolean powerOn)
Initialize the adapter with default values, including power-on.