/
jswrap_bluetooth.c
4596 lines (4152 loc) · 150 KB
/
jswrap_bluetooth.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2014 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.
*
*/
#include "jswrap_bluetooth.h"
#include "jsinteractive.h"
#include "jsdevices.h"
#include "jswrap_promise.h"
#include "jswrap_interactive.h"
#include "jswrap_string.h"
#include "jsnative.h"
#include "bluetooth_utils.h"
#include "bluetooth.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#ifdef NRF5X
#include "nrf5x_utils.h"
#include "nordic_common.h"
#include "nrf.h"
#include "ble_gap.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_conn_params.h"
#include "app_timer.h"
#include "ble_nus.h"
#include "app_util_platform.h"
#if NRF_SD_BLE_API_VERSION<5
#include "softdevice_handler.h"
#endif
#ifdef USE_NFC
#include "nfc_uri_msg.h"
#include "nfc_ble_pair_msg.h"
#include "nfc_launchapp_msg.h"
#endif
#if ESPR_BLUETOOTH_ANCS
#include "bluetooth_ancs.h"
#endif
#endif
#ifdef ESP32
#include "BLE/esp32_gap_func.h"
#include "BLE/esp32_gatts_func.h"
#include "BLE/esp32_gattc_func.h"
#define BLE_CONN_HANDLE_INVALID -1
#endif
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
#if ESPR_NO_PROMISES!=1
JsVar *blePromise = 0;
#endif
JsVar *bleTaskInfo = 0;
JsVar *bleTaskInfo2 = 0;
BleTask bleTask = BLETASK_NONE;
/// Get the string value of the given task
const char *bleGetTaskString(BleTask task) {
#ifndef SAVE_ON_FLASH_EXTREME
const char *str = BLETASK_STRINGS; // 0 separated, with two 0s at the end
while (task && *str) {
if (!str) return "?";
str += strlen(str)+1;
task--;
}
if (!*str) return "?";
return str;
#else
return "?";
#endif
}
bool bleInTask(BleTask task) {
return bleTask==task;
}
BleTask bleGetCurrentTask() {
return bleTask;
}
bool bleNewTask(BleTask task, JsVar *taskInfo) {
if (bleTask) {
jsExceptionHere(JSET_ERROR, "BLE task %s is already in progress", bleGetTaskString(bleTask));
return false;
}
/* if (blePromise) {
jsiConsolePrintf("Existing blePromise!\n");
jsvTrace(blePromise,2);
}
if (bleTaskInfo) {
jsiConsolePrintf("Existing bleTaskInfo!\n");
jsvTrace(bleTaskInfo,2);
}*/
#if ESPR_NO_PROMISES!=1
assert(!blePromise && !bleTaskInfo && !bleTaskInfo2);
blePromise = jspromise_create();
#endif
bleTask = task;
bleTaskInfo = jsvLockAgainSafe(taskInfo);
bleTaskInfo2 = NULL;
return true;
}
void bleCompleteTask(BleTask task, bool ok, JsVar *data) {
//jsiConsolePrintf(ok?"RES %d %v\n":"REJ %d %q\n", task, data);
if (task != bleTask) {
jsExceptionHere(JSET_INTERNALERROR, "BLE task completed that wasn't scheduled (%s/%s)", bleGetTaskString(task), bleGetTaskString(bleTask));
return;
}
bleTask = BLETASK_NONE;
#if ESPR_NO_PROMISES!=1
if (blePromise) {
if (ok) jspromise_resolve(blePromise, data);
else jspromise_reject(blePromise, data);
jsvUnLock(blePromise);
blePromise = 0;
}
#endif
jsvUnLock(bleTaskInfo);
bleTaskInfo = 0;
jsvUnLock(bleTaskInfo2);
bleTaskInfo2 = 0;
jshHadEvent();
}
void bleCompleteTaskSuccess(BleTask task, JsVar *data) {
bleCompleteTask(task, true, data);
}
void bleCompleteTaskSuccessAndUnLock(BleTask task, JsVar *data) {
bleCompleteTask(task, true, data);
jsvUnLock(data);
}
void bleCompleteTaskFail(BleTask task, JsVar *data) {
bleCompleteTask(task, false, data);
}
void bleCompleteTaskFailAndUnLock(BleTask task, JsVar *data) {
bleCompleteTask(task, false, data);
jsvUnLock(data);
}
void bleSwitchTask(BleTask task) {
bleTask = task;
}
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
#if CENTRAL_LINK_COUNT>0
void bleSetActiveBluetoothGattServer(int idx, JsVar *var) {
assert(idx >=0 && idx < CENTRAL_LINK_COUNT);
if (idx<0) return;
char name[BLE_NAME_GATT_SERVER_LEN] = BLE_NAME_GATT_SERVER;
name[BLE_NAME_GATT_SERVER_LEN-2] = '0'+idx;
jsvObjectSetChild(execInfo.hiddenRoot, name, var);
}
JsVar *bleGetActiveBluetoothGattServer(int idx) {
assert(idx < CENTRAL_LINK_COUNT);
if (idx<0) return 0;
char name[BLE_NAME_GATT_SERVER_LEN] = BLE_NAME_GATT_SERVER;
name[BLE_NAME_GATT_SERVER_LEN-2] = '0'+idx;
return jsvObjectGetChildIfExists(execInfo.hiddenRoot, name);
}
uint16_t jswrap_ble_BluetoothRemoteGATTServer_getHandle(JsVar *parent) {
JsVar *handle = jsvObjectGetChildIfExists(parent, "handle");
if (!jsvIsInt(handle)) return BLE_CONN_HANDLE_INVALID;
return jsvGetIntegerAndUnLock(handle);
}
uint16_t jswrap_ble_BluetoothDevice_getHandle(JsVar *parent) {
JsVar *gatt = jswrap_BluetoothDevice_gatt(parent);
uint16_t handle = BLE_CONN_HANDLE_INVALID;
if (gatt) handle = jswrap_ble_BluetoothRemoteGATTServer_getHandle(gatt);
return handle;
}
uint16_t jswrap_ble_BluetoothRemoteGATTService_getHandle(JsVar *parent) {
JsVar *device = jsvObjectGetChildIfExists(parent, "device");
uint16_t handle = BLE_CONN_HANDLE_INVALID;
if (device) handle = jswrap_ble_BluetoothDevice_getHandle(device);
return handle;
}
uint16_t jswrap_ble_BluetoothRemoteGATTCharacteristic_getHandle(JsVar *parent) {
JsVar *service = jsvObjectGetChildIfExists(parent, "service");
uint16_t handle = BLE_CONN_HANDLE_INVALID;
if (service) handle = jswrap_ble_BluetoothRemoteGATTService_getHandle(service);
return handle;
}
#endif
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
/*JSON{
"type" : "init",
"generate" : "jswrap_ble_init"
}*/
void jswrap_ble_init() {
// Turn off sleeping if it was on before
jsiStatus &= ~BLE_IS_SLEEPING;
if (jsiStatus & JSIS_COMPLETELY_RESET) {
#if defined(USE_NFC) && defined(NFC_DEFAULT_URL)
// By default Puck.js's NFC will send you to the PuckJS website
// address is included so Web Bluetooth can connect to the correct one
JsVar *addr = jswrap_ble_getAddress();
JsVar *uri = jsvVarPrintf(NFC_DEFAULT_URL"?a=%v", addr);
jsvUnLock(addr);
jswrap_nfc_URL(uri);
jsvUnLock(uri);
#endif
} else {
#ifdef USE_NFC
// start NFC, if it had been set
JsVar *flatStr = jsvObjectGetChildIfExists(execInfo.hiddenRoot, "NfcEnabled");
if (flatStr) {
uint8_t *flatStrPtr = (uint8_t*)jsvGetFlatStringPointer(flatStr);
if (flatStrPtr) jsble_nfc_start(flatStrPtr, jsvGetLength(flatStr));
jsvUnLock(flatStr);
}
#endif
}
// Set advertising interval back to default
bleAdvertisingInterval = MSEC_TO_UNITS(BLUETOOTH_ADVERTISING_INTERVAL, UNIT_0_625_MS); /**< The advertising interval (in units of 0.625 ms). */
// Now set up whatever advertising we were doing before
jswrap_ble_reconfigure_softdevice();
}
/** Reconfigure the softdevice (on init or after restart) to have all the services/advertising we need */
void jswrap_ble_reconfigure_softdevice() {
JsVar *v,*o;
// restart various
v = jsvObjectGetChildIfExists(execInfo.root, BLE_SCAN_EVENT);
if (v) jsble_set_scanning(true, NULL);
jsvUnLock(v);
v = jsvObjectGetChildIfExists(execInfo.root, BLE_RSSI_EVENT);
if (v) jsble_set_rssi_scan(true);
jsvUnLock(v);
// advertising
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_ADVERTISE_DATA);
o = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_ADVERTISE_OPTIONS);
jswrap_ble_setAdvertising(v, o);
jsvUnLock2(v,o);
// services
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_SERVICE_DATA);
jsble_set_services(v);
jsvUnLock(v);
// If we had scan response data set, update it
JsVar *scanData = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_SCAN_RESPONSE_DATA);
if (scanData) jswrap_ble_setScanResponse(scanData);
jsvUnLock(scanData);
// Set up security related stuff
jsble_update_security();
}
/*JSON{
"type" : "idle",
"generate" : "jswrap_ble_idle"
}*/
bool jswrap_ble_idle() {
return false;
}
/*JSON{
"type" : "kill",
"generate" : "jswrap_ble_kill"
}*/
void jswrap_ble_kill() {
#ifdef USE_NFC
// stop NFC emulation
jsble_nfc_stop(); // not a problem to call this if NFC isn't started
#endif
// stop any BLE tasks
bleTask = BLETASK_NONE;
#if ESPR_NO_PROMISES!=1
if (blePromise) jsvUnLock(blePromise);
blePromise = 0;
#endif
if (bleTaskInfo) jsvUnLock(bleTaskInfo);
bleTaskInfo = 0;
if (bleTaskInfo2) jsvUnLock(bleTaskInfo2);
bleTaskInfo2 = 0;
// if we were scanning, make sure we stop
jsble_set_scanning(false, NULL);
jsble_set_rssi_scan(false);
#if CENTRAL_LINK_COUNT>0
// if we were connected to something, disconnect
for (int i=0;i<CENTRAL_LINK_COUNT;i++)
if (m_central_conn_handles[i] != BLE_CONN_HANDLE_INVALID)
jsble_disconnect(m_central_conn_handles[i]);
#endif
}
void jswrap_ble_dumpBluetoothInitialisation(vcbprintf_callback user_callback, void *user_data) {
JsVar *v,*o;
v = jsvObjectGetChildIfExists(execInfo.root, BLE_SCAN_EVENT);
if (v) {
user_callback("NRF.setScan(", user_data);
jsiDumpJSON(user_callback, user_data, v, 0);
user_callback(");\n", user_data);
}
jsvUnLock(v);
v = jsvObjectGetChildIfExists(execInfo.root, BLE_RSSI_EVENT);
if (v) {
user_callback("NRF.setRSSIHandler(", user_data);
jsiDumpJSON(user_callback, user_data, v, 0);
user_callback(");\n", user_data);
}
jsvUnLock(v);
// advertising
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_ADVERTISE_DATA);
o = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_ADVERTISE_OPTIONS);
if (v || o)
cbprintf(user_callback, user_data, "NRF.setAdvertising(%j, %j);\n",v,o);
jsvUnLock2(v,o);
// services
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_SERVICE_DATA);
o = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_SERVICE_OPTIONS);
if (v || o)
cbprintf(user_callback, user_data, "NRF.setServices(%j, %j);\n",v,o);
jsvUnLock2(v,o);
// security
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_SECURITY);
if (v)
cbprintf(user_callback, user_data, "NRF.setSecurity(%j);\n",v);
jsvUnLock(v);
// mac address
v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_MAC_ADDRESS);
if (v)
cbprintf(user_callback, user_data, "NRF.setAddress(%j);\n",v);
jsvUnLock(v);
}
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
/*JSON{
"type" : "class",
"class" : "NRF"
}
The NRF class is for controlling functionality of the Nordic nRF51/nRF52 chips.
Most functionality is related to Bluetooth Low Energy, however there are also
some functions related to NFC that apply to NRF52-based devices.
*/
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "connect",
"params" : [
["addr","JsVar","The address of the device that has connected"]
]
}
Called when a host device connects to Espruino. The first argument contains the
address.
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "disconnect",
"params" : [
["reason","int","The reason code reported back by the BLE stack - see Nordic's [`ble_hci.h` file](https://github.com/espruino/Espruino/blob/master/targetlibs/nrf5x_12/components/softdevice/s132/headers/ble_hci.h#L71) for more information"]
]
}
Called when a host device disconnects from Espruino.
The most common reason is:
* 19 - `REMOTE_USER_TERMINATED_CONNECTION`
* 22 - `LOCAL_HOST_TERMINATED_CONNECTION`
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "error",
"#if" : "defined(NRF52_SERIES)",
"params" : [
["msg","JsVar","The error string"]
]
}
Called when the Nordic Bluetooth stack (softdevice) generates an error. In pretty
much all cases an Exception will also have been thrown.
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "passkey",
"ifdef" : "NRF52_SERIES",
"params" : [
["passkey","JsVar","A 6 character numeric String to be displayed"]
]
}
(Added in 2v19) Called when a central device connects to Espruino, pairs, and sends a passkey that Espruino should display.
For this to be used, you'll have to specify that your device has a display using `NRF.setSecurity({mitm:1, display:1});`
For instance:
```
NRF.setSecurity({mitm:1, display:1});
NRF.on("passkey", key => print("Enter PIN: ",passkey));
```
It is also possible to specify a static passkey with `NRF.setSecurity({passkey:"123456", mitm:1, display:1});`
in which case no `passkey` event handler is needed (this method works on Espruino 2v02 and later)
**Note:** A similar event, [`BluetoothDevice.on("passkey", ...)`](http://www.espruino.com/Reference#l_BluetoothDevice_passkey) is available
for when Espruino is connecting *to* another device (central mode).
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "security",
"params" : [
["status","JsVar","An object containing `{auth_status,bonded,lv4,kdist_own,kdist_peer}"]
]
}
Contains updates on the security of the current Bluetooth link.
See Nordic's `ble_gap_evt_auth_status_t` structure for more information.
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "advertising",
"#if" : "defined(NRF52_SERIES)",
"params" : [
["isAdvertising","bool","Whether we are advertising or not"]
]
}
Called when Bluetooth advertising starts or stops on Espruino
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "bond",
"#if" : "defined(NRF52_SERIES)",
"params" : [
["status","JsVar","One of `'request'/'start'/'success'/'fail'`"]
]
}
Called during the bonding process to update on status
`status` is one of:
* `"request"` - Bonding has been requested in code via `NRF.startBonding`
* `"start"` - The bonding procedure has started
* `"success"` - The bonding procedure has succeeded (`NRF.startBonding`'s promise resolves)
* `"fail"` - The bonding procedure has failed (`NRF.startBonding`'s promise rejects)
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "HID",
"#if" : "defined(NRF52_SERIES)"
}
Called with a single byte value when Espruino is set up as a HID device and the
computer it is connected to sends a HID report back to Espruino. This is usually
used for handling indications such as the Caps Lock LED.
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "servicesDiscover",
"#if" : "defined(NRF52_SERIES) || defined(ESP32)"
}
Called with discovered services when discovery is finished
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "characteristicsDiscover",
"#if" : "defined(NRF52_SERIES) || defined(ESP32)"
}
Called with discovered characteristics when discovery is finished
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "NFCon",
"#if" : "defined(NRF52_SERIES) && defined(USE_NFC)"
}
Called when an NFC field is detected
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "NFCoff",
"#if" : "defined(NRF52_SERIES) && defined(USE_NFC)"
}
Called when an NFC field is no longer detected
*/
/*JSON{
"type" : "event",
"class" : "NRF",
"name" : "NFCrx",
"params" : [
["arr","JsVar","An ArrayBuffer containign the received data"]
],
"#if" : "defined(NRF52_SERIES) && defined(USE_NFC)"
}
When NFC is started with `NRF.nfcStart`, this is fired when NFC data is
received. It doesn't get called if NFC is started with `NRF.nfcURL` or
`NRF.nfcRaw`
*/
/*JSON{
"type" : "event",
"class" : "BluetoothDevice",
"name" : "gattserverdisconnected",
"params" : [
["reason","int","The reason code reported back by the BLE stack - see Nordic's `ble_hci.h` file for more information"]
],
"ifdef" : "NRF52_SERIES"
}
Called when the device gets disconnected.
To connect and then print `Disconnected` when the device is disconnected, just
do the following:
```
var gatt;
NRF.connect("aa:bb:cc:dd:ee:ff").then(function(gatt) {
gatt.device.on('gattserverdisconnected', function(reason) {
console.log("Disconnected ",reason);
});
});
```
Or:
```
var gatt;
NRF.requestDevice(...).then(function(device) {
device.on('gattserverdisconnected', function(reason) {
console.log("Disconnected ",reason);
});
});
```
*/
/*JSON{
"type" : "event",
"class" : "BluetoothRemoteGATTCharacteristic",
"name" : "characteristicvaluechanged",
"ifdef" : "BLUETOOTH"
}
Called when a characteristic's value changes, *after*
`BluetoothRemoteGATTCharacteristic.startNotifications` has been called.
```
...
return service.getCharacteristic("characteristic_uuid");
}).then(function(c) {
c.on('characteristicvaluechanged', function(event) {
console.log("-> "+event.target.value);
});
return c.startNotifications();
}).then(...
```
The first argument is of the form `{target :
BluetoothRemoteGATTCharacteristic}`, and
`BluetoothRemoteGATTCharacteristic.value` will then contain the new value (as a
DataView).
*/
/*JSON{
"type" : "object",
"name" : "Bluetooth",
"instanceof" : "Serial",
"ifdef" : "BLUETOOTH"
}
The Bluetooth Serial port - used when data is sent or received over Bluetooth
Smart on nRF51/nRF52 chips.
*/
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "disconnect",
"generate" : "jswrap_ble_disconnect"
}
If a device is connected to Espruino, disconnect from it.
*/
void jswrap_ble_disconnect() {
uint32_t err_code;
if (jsble_has_peripheral_connection()) {
err_code = jsble_disconnect(m_peripheral_conn_handle);
jsble_check_error(err_code);
}
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "sleep",
"generate" : "jswrap_ble_sleep"
}
Disable Bluetooth advertising and disconnect from any device that connected to
Puck.js as a peripheral (this won't affect any devices that Puck.js initiated
connections to).
This makes Puck.js undiscoverable, so it can't be connected to.
Use `NRF.wake()` to wake up and make Puck.js connectable again.
*/
void jswrap_ble_sleep() {
// set as sleeping
bleStatus |= BLE_IS_SLEEPING;
// stop advertising
jsble_advertising_stop();
// If connected, disconnect.
// when we disconnect, we'll see BLE_IS_SLEEPING and won't advertise
jswrap_ble_disconnect();
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "wake",
"generate" : "jswrap_ble_wake"
}
Enable Bluetooth advertising (this is enabled by default), which allows other
devices to discover and connect to Puck.js.
Use `NRF.sleep()` to disable advertising.
*/
void jswrap_ble_wake() {
bleStatus &= ~BLE_IS_SLEEPING;
jsble_check_error(jsble_advertising_start());
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "restart",
"generate" : "jswrap_ble_restart",
"params" : [
["callback","JsVar","[optional] A function to be called while the softdevice is uninitialised. Use with caution - accessing console/bluetooth will almost certainly result in a crash."]
]
}
Restart the Bluetooth softdevice (if there is currently a BLE connection, it
will queue a restart to be done when the connection closes).
You shouldn't need to call this function in normal usage. However, Nordic's BLE
softdevice has some settings that cannot be reset. For example there are only a
certain number of unique UUIDs. Once these are all used the only option is to
restart the softdevice to clear them all out.
*/
void jswrap_ble_restart(JsVar *callback) {
if (jsble_has_connection()) {
jsiConsolePrintf("BLE Connected, queueing BLE restart for later\n");
bleStatus |= BLE_NEEDS_SOFTDEVICE_RESTART;
} else {
// Not connected, so we can restart now
jsble_restart_softdevice(jsvIsFunction(callback)?callback:NULL);
}
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "eraseBonds",
"#if" : "defined(NRF52_SERIES)",
"generate" : "jswrap_ble_eraseBonds",
"params" : [
["callback","JsVar","[optional] A function to be called while the softdevice is uninitialised. Use with caution - accessing console/bluetooth will almost certainly result in a crash."]
]
}
Delete all data stored for all peers (bonding data used for secure connections). This cannot be done
while a connection is active, so if there is a connection it will be postponed until everything is disconnected
(which can be done by calling `NRF.disconnect()` and waiting).
Booting your device while holding all buttons down together should also have the same effect.
*/
void jswrap_ble_eraseBonds() {
#if PEER_MANAGER_ENABLED
if (jsble_has_connection()) {
jsExceptionHere(JSET_ERROR, "BLE Connected, can't erase bonds.");
} else {
jsble_central_eraseBonds();
}
#endif
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "getAddress",
"generate" : "jswrap_ble_getAddress",
"return" : ["JsVar", "MAC address - a string of the form 'aa:bb:cc:dd:ee:ff'" ]
}
Get this device's default Bluetooth MAC address.
For Puck.js, the last 5 characters of this (e.g. `ee:ff`) are used in the
device's advertised Bluetooth name.
*/
JsVar *jswrap_ble_getAddress() {
#ifdef NRF5X
uint32_t addr0 = NRF_FICR->DEVICEADDR[0];
uint32_t addr1 = NRF_FICR->DEVICEADDR[1];
#else
uint32_t addr0 = 0xDEADDEAD;
uint32_t addr1 = 0xDEAD;
#endif
return jsvVarPrintf("%02x:%02x:%02x:%02x:%02x:%02x",
((addr1>>8 )&0xFF)|0xC0,
((addr1 )&0xFF),
((addr0>>24)&0xFF),
((addr0>>16)&0xFF),
((addr0>>8 )&0xFF),
((addr0 )&0xFF));
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "setAddress",
"#if" : "defined(NRF52_SERIES)",
"generate" : "jswrap_ble_setAddress",
"params" : [
["addr","JsVar","The address to use (as a string)"]
]
}
Set this device's default Bluetooth MAC address:
```
NRF.setAddress("ff:ee:dd:cc:bb:aa random");
```
Addresses take the form:
* `"ff:ee:dd:cc:bb:aa"` or `"ff:ee:dd:cc:bb:aa public"` for a public address
* `"ff:ee:dd:cc:bb:aa random"` for a random static address (the default for
Espruino)
This may throw a `INVALID_BLE_ADDR` error if the upper two bits of the address
don't match the address type.
To change the address, Espruino must restart the softdevice. It will only do so
when it is disconnected from other devices.
*/
void jswrap_ble_setAddress(JsVar *address) {
#ifdef NRF52_SERIES
ble_gap_addr_t p_addr;
if (!bleVarToAddr(address, &p_addr)) {
jsExceptionHere(JSET_ERROR, "Expecting mac address of the form aa:bb:cc:dd:ee:ff");
return;
}
jsvObjectSetChild(execInfo.hiddenRoot, BLE_NAME_MAC_ADDRESS, address);
jswrap_ble_restart(NULL);
#else
jsExceptionHere(JSET_ERROR, "Not implemented");
#endif
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "resolveAddress",
"#if" : "defined(NRF52_SERIES)",
"generate" : "jswrap_ble_resolveAddress",
"params" : [
["options" ,"JsVar", "The address that should be resolved."]
],
"return": ["JsVar", "The resolved address, or `undefined` if it couldn't be resolved."]
}
Try to resolve a **bonded** peer's address from a random private resolvable address. If the peer
is not bonded, there will be no IRK and `undefined` will be returned.
A bunch of devices, especially smartphones, implement address randomisation and periodically change
their bluetooth address to prevent being tracked.
If such a device uses a "random private resolvable address", that address is generated
with the help of an identity resolving key (IRK) that is exchanged during bonding.
If we know the IRK of a device, we can check if an address was potentially generated by that device.
The following will check an address against the IRKs of all bonded devices,
and return the actual address of a bonded device if the given address was likely generated using that device's IRK:
```
NRF.on('connect',addr=> {
// addr could be "aa:bb:cc:dd:ee:ff private-resolvable"
if (addr.endsWith("private-resolvable")) {
let resolved = NRF.resolveAddress(addr);
// resolved is "aa:bb:cc:dd:ee:ff public"
if (resolved) addr = resolved;
}
console.log("Device connected: ", addr);
})
```
You can get the current connection's address using `NRF.getSecurityStatus().connected_addr`,
so can for instance do `NRF.resolveAddress(NRF.getSecurityStatus().connected_addr)`.
*/
JsVar *jswrap_ble_resolveAddress(JsVar *address) {
#if defined(NRF52_SERIES) && PEER_MANAGER_ENABLED==1
return jsble_resolveAddress(address);
#else
jsExceptionHere(JSET_ERROR, "Not implemented");
return 0;
#endif
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "getBattery",
"generate" : "jswrap_ble_getBattery",
"return" : ["float", "Battery level in volts" ]
}
Get the battery level in volts (the voltage that the NRF chip is running off
of).
This is the battery level of the device itself - it has nothing to with any
device that might be connected.
*/
JsVarFloat jswrap_ble_getBattery() {
return jshReadVRef();
}
/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "setAdvertising",
"generate" : "jswrap_ble_setAdvertising",
"params" : [
["data","JsVar","The service data to advertise as an object - see below for more info"],
["options","JsVar","[optional] Object of options"]
]
}
Change the data that Espruino advertises.
Data can be of the form `{ UUID : data_as_byte_array }`. The UUID should be a
[Bluetooth Service
ID](https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx).
For example to return battery level at 95%, do:
```
NRF.setAdvertising({
0x180F : [95] // Service data 0x180F = 95
});
```
Or you could report the current temperature:
```
setInterval(function() {
NRF.setAdvertising({
0x1809 : [Math.round(E.getTemperature())]
});
}, 30000);
```
If you specify a value for the object key, Service Data is advertised. However
if you specify `undefined`, the Service UUID is advertised:
```
NRF.setAdvertising({
0x180D : undefined // Advertise service UUID 0x180D (HRM)
});
```
Service UUIDs can also be supplied in the second argument of `NRF.setServices`,
but those go in the scan response packet.
You can also supply the raw advertising data in an array. For example to
advertise as an Eddystone beacon:
```
NRF.setAdvertising([0x03, // Length of Service List
0x03, // Param: Service List
0xAA, 0xFE, // Eddystone ID
0x13, // Length of Service Data
0x16, // Service Data
0xAA, 0xFE, // Eddystone ID
0x10, // Frame type: URL
0xF8, // Power
0x03, // https://
'g','o','o','.','g','l','/','B','3','J','0','O','c'],
{interval:100});
```
(However for Eddystone we'd advise that you use the [Espruino Eddystone
library](/Puck.js+Eddystone))
**Note:** When specifying data as an array, certain advertising options such as
`discoverable` and `showName` won't have any effect.
**Note:** The size of Bluetooth LE advertising packets is limited to 31 bytes.
If you want to advertise more data, consider using an array for `data` (See
below), or `NRF.setScanResponse`.
You can even specify an array of arrays or objects, in which case each
advertising packet will be used in turn - for instance to make your device
advertise battery level and its name as well as both Eddystone and iBeacon :
```
NRF.setAdvertising([
{0x180F : [E.getBattery()]}, // normal advertising, with battery %
require("ble_ibeacon").get(...), // iBeacon
require("ble_eddystone").get(...), // eddystone
], {interval:300});
```
`options` is an object, which can contain:
```
{
name: "Hello" // The name of the device
showName: true/false // include full name, or nothing
discoverable: true/false // general discoverable, or limited - default is limited
connectable: true/false // whether device is connectable - default is true
scannable : true/false // whether device can be scanned for scan response packets - default is true
whenConnected : true/false // keep advertising when connected (nRF52 only)
// switches to advertising as non-connectable when it is connected
interval: 600 // Advertising interval in msec, between 20 and 10000 (default is 375ms)
manufacturer: 0x0590 // IF sending manufacturer data, this is the manufacturer ID
manufacturerData: [...] // IF sending manufacturer data, this is an array of data
phy: "1mbps/2mbps/coded" // (NRF52833/NRF52840 only) use the long-range coded phy for transmission (1mbps default)
}
```
Setting `connectable` and `scannable` to false gives the lowest power
consumption as the BLE radio doesn't have to listen after sending advertising.
**NOTE:** Non-`connectable` advertising can't have an advertising interval less
than 100ms according to the BLE spec.
So for instance to set the name of Puck.js without advertising any other data
you can just use the command:
```
NRF.setAdvertising({},{name:"Hello"});
```
You can also specify 'manufacturer data', which is another form of advertising
data. We've registered the Manufacturer ID 0x0590 (as Pur3 Ltd) for use with
*Official Espruino devices* - use it to advertise whatever data you'd like, but
we'd recommend using JSON.
For example by not advertising a device name you can send up to 24 bytes of JSON
on Espruino's manufacturer ID:
```
var data = {a:1,b:2};
NRF.setAdvertising({},{
showName:false,
manufacturer:0x0590,
manufacturerData:JSON.stringify(data)
});
```
If you're using [EspruinoHub](https://github.com/espruino/EspruinoHub) then it
will automatically decode this into the following MQTT topics:
* `/ble/advertise/ma:c_:_a:dd:re:ss/espruino` -> `{"a":10,"b":15}`
* `/ble/advertise/ma:c_:_a:dd:re:ss/a` -> `1`
* `/ble/advertise/ma:c_:_a:dd:re:ss/b` -> `2`
Note that **you only have 24 characters available for JSON**, so try to use the
shortest field names possible and avoid floating point values that can be very
long when converted to a String.
*/
void jswrap_ble_setAdvertising(JsVar *data, JsVar *options) {
uint32_t err_code = 0;
bool isAdvertising = bleStatus & BLE_IS_ADVERTISING;
if (jsvIsObject(options)) {
JsVar *v;
v = jsvObjectGetChildIfExists(options, "interval");
if (v) {
uint16_t new_advertising_interval = MSEC_TO_UNITS(jsvGetIntegerAndUnLock(v), UNIT_0_625_MS);
if (new_advertising_interval<0x0020) new_advertising_interval=0x0020;
if (new_advertising_interval>0x4000) new_advertising_interval=0x4000;
if (new_advertising_interval != bleAdvertisingInterval) {
bleAdvertisingInterval = new_advertising_interval;
}