@stoprocent/bleno 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -30
- package/examples/echo/async.js +47 -0
- package/examples/echo/characteristic.js +15 -17
- package/examples/echo/main.js +19 -1
- package/examples/uart/main.js +3 -2
- package/examples/with-bindings/main.js +35 -0
- package/index.d.ts +166 -121
- package/index.js +5 -1
- package/lib/bleno.js +108 -17
- package/lib/characteristic.js +27 -10
- package/lib/hci-socket/acl-stream.js +3 -3
- package/lib/hci-socket/bindings.js +36 -29
- package/lib/hci-socket/gatt.js +177 -105
- package/lib/hci-socket/hci.js +5 -3
- package/lib/hci-socket/mgmt.js +1 -1
- package/lib/hci-socket/smp.js +5 -4
- package/lib/mac/src/ble_peripheral_manager.h +3 -7
- package/lib/mac/src/ble_peripheral_manager.mm +101 -171
- package/lib/mac/src/bleno_mac.h +0 -1
- package/lib/mac/src/bleno_mac.mm +21 -33
- package/lib/mac/src/callbacks.h +12 -48
- package/lib/mac/src/callbacks.mm +34 -45
- package/lib/mac/src/napi_objc.mm +9 -30
- package/lib/mac/src/objc_cpp.h +2 -2
- package/lib/mac/src/objc_cpp.mm +3 -37
- package/lib/resolve-bindings.js +27 -6
- package/package.json +7 -9
- package/prebuilds/darwin-x64+arm64/@stoprocent+bleno.node +0 -0
- package/prebuilds/win32-ia32/@stoprocent+bleno.node +0 -0
- package/prebuilds/win32-x64/@stoprocent+bleno.node +0 -0
- package/test/characteristic.test.js +158 -11
- package/examples/battery-service/README.md +0 -14
- package/examples/blink1/README.md +0 -44
- package/examples/pizza/README.md +0 -16
- package/lib/mac/src/noble_mac.h +0 -34
- package/lib/mac/src/noble_mac.mm +0 -267
- package/lib/mac/src/peripheral.h +0 -23
- package/with-bindings.js +0 -5
- package/with-custom-binding.js +0 -6
package/lib/mac/src/callbacks.mm
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Created by Georg Vienna on 30.08.18.
|
|
6
6
|
//
|
|
7
|
+
#include <napi-thread-safe-callback.hpp>
|
|
7
8
|
#include "callbacks.h"
|
|
8
|
-
|
|
9
|
-
#import <napi-thread-safe-callback.hpp>
|
|
10
|
-
|
|
11
9
|
#include "napi_objc.h"
|
|
10
|
+
#include "objc_cpp.h"
|
|
12
11
|
|
|
12
|
+
#define _a(val) Napi::String::New(env, convertToBlenoAddress(val))
|
|
13
13
|
#define _s(val) Napi::String::New(env, val)
|
|
14
14
|
#define _b(val) Napi::Boolean::New(env, val)
|
|
15
15
|
#define _n(val) Napi::Number::New(env, val)
|
|
@@ -33,23 +33,28 @@ void Emit::Wrap(const Napi::Value& receiver, const Napi::Function& callback) {
|
|
|
33
33
|
mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
void Emit::AdvertisingStart() {
|
|
37
|
-
mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
|
|
38
|
-
|
|
39
|
-
args = { _s("advertisingStart") };
|
|
36
|
+
void Emit::AdvertisingStart(NSError * _Nullable error) {
|
|
37
|
+
mCallback->call([error](Napi::Env env, std::vector<napi_value>& args) {
|
|
38
|
+
const char *cerror = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
|
39
|
+
args = { _s("advertisingStart"), error ? Napi::Error::New(env, cerror).Value() : env.Null() };
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
void Emit::
|
|
43
|
+
void Emit::AdvertisingStop() {
|
|
44
44
|
mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
args = { _s("advertisingStop") };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void Emit::ServicesSet(NSError * _Nullable error) {
|
|
50
|
+
mCallback->call([error](Napi::Env env, std::vector<napi_value>& args) {
|
|
51
|
+
const char *cerror = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
|
52
|
+
args = { _s("servicesSet"), error ? Napi::Error::New(env, cerror).Value() : env.Null() };
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
void Emit::StateChange(const std::string& state) {
|
|
51
57
|
mCallback->call([state](Napi::Env env, std::vector<napi_value>& args) {
|
|
52
|
-
// emit('stateChange', state);
|
|
53
58
|
args = { _s("stateChange"), _s(state) };
|
|
54
59
|
});
|
|
55
60
|
}
|
|
@@ -58,67 +63,51 @@ void EmitCharacteristic::Wrap(const Napi::Value& receiver, const Napi::Function&
|
|
|
58
63
|
mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
void EmitCharacteristic::ReadRequest(
|
|
62
|
-
mCallback->call([offset, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
63
|
-
// callback(result, data)
|
|
66
|
+
void EmitCharacteristic::ReadRequest(NSUUID *handle, uint16_t offset, std::function<void (uint16_t, NSData *)> completion) {
|
|
67
|
+
mCallback->call([handle, offset, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
64
68
|
auto callable = [completion](const Napi::CallbackInfo& info){
|
|
65
69
|
completion(info[0].As<Napi::Number>().Int32Value(),
|
|
66
70
|
napiToData(info[1].As<Napi::Buffer<Byte>>()));
|
|
67
71
|
};
|
|
68
72
|
Napi::Function cb = Napi::Function::New(env, callable);
|
|
69
|
-
|
|
70
|
-
// emit('readRequest', offset, callback);
|
|
71
|
-
args = { _s("readRequest"), _n(offset), cb };
|
|
73
|
+
args = { _s("readRequest"), _a(handle), _n(offset), cb };
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
void EmitCharacteristic::WriteRequest(NSData *data,
|
|
76
|
-
mCallback->call([data, offset, ignoreResponse, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
77
|
-
// callback(result)
|
|
77
|
+
void EmitCharacteristic::WriteRequest(NSUUID *handle, NSData *data, uint16_t offset, bool ignoreResponse, std::function<void (uint16_t)> completion) {
|
|
78
|
+
mCallback->call([handle, data, offset, ignoreResponse, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
78
79
|
auto callable = [completion](const Napi::CallbackInfo& info){
|
|
79
80
|
completion(info[0].As<Napi::Number>().Int32Value());
|
|
80
81
|
};
|
|
81
82
|
Napi::Function cb = Napi::Function::New(env, callable);
|
|
82
|
-
|
|
83
|
-
// emit('writeRequest', data, offset, ignoreResponse, callback)
|
|
84
|
-
args = { _s("writeRequest"), toBufferFromNSData(env, data), _n(offset), _b(ignoreResponse), cb };
|
|
83
|
+
args = { _s("writeRequest"), _a(handle), toBufferFromNSData(env, data), _n(offset), _b(ignoreResponse), cb };
|
|
85
84
|
});
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
void EmitCharacteristic::Subscribe(
|
|
89
|
-
mCallback->call([maxValueSize, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
90
|
-
// callback(data)
|
|
87
|
+
void EmitCharacteristic::Subscribe(NSUUID *handle, uint16_t maxValueSize, std::function<void (NSData *)> completion) {
|
|
88
|
+
mCallback->call([handle, maxValueSize, completion](Napi::Env env, std::vector<napi_value>& args) {
|
|
91
89
|
auto callable = [completion](const Napi::CallbackInfo& info){
|
|
92
90
|
completion(napiToData(info[0].As<Napi::Buffer<Byte>>()));
|
|
93
91
|
};
|
|
94
92
|
Napi::Function cb = Napi::Function::New(env, callable);
|
|
95
|
-
|
|
96
|
-
// emit('subscribe', maxValueSize, callback)
|
|
97
|
-
args = { _s("subscribe"), _n(maxValueSize), cb };
|
|
93
|
+
args = { _s("subscribe"), _a(handle), _n(maxValueSize), cb };
|
|
98
94
|
});
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
void EmitCharacteristic::Unsubscribe() {
|
|
102
|
-
mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
|
|
103
|
-
|
|
104
|
-
args = { _s("unsubscribe") };
|
|
97
|
+
void EmitCharacteristic::Unsubscribe(NSUUID *handle) {
|
|
98
|
+
mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
|
|
99
|
+
args = { _s("unsubscribe"), _a(handle) };
|
|
105
100
|
});
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
void EmitCharacteristic::Notify() {
|
|
109
|
-
mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
|
|
110
|
-
|
|
111
|
-
args = { _s("notify") };
|
|
103
|
+
void EmitCharacteristic::Notify(NSUUID *handle) {
|
|
104
|
+
mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
|
|
105
|
+
args = { _s("notify"), _a(handle) };
|
|
112
106
|
});
|
|
113
107
|
}
|
|
114
108
|
|
|
115
|
-
void EmitCharacteristic::Indicate() {
|
|
116
|
-
mCallback->call([](Napi::Env env, std::vector<napi_value>& args) {
|
|
117
|
-
|
|
118
|
-
args = { _s("indicate") };
|
|
109
|
+
void EmitCharacteristic::Indicate(NSUUID *handle) {
|
|
110
|
+
mCallback->call([handle](Napi::Env env, std::vector<napi_value>& args) {
|
|
111
|
+
args = { _s("indicate"), _a(handle) };
|
|
119
112
|
});
|
|
120
113
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// emit('notify');
|
|
124
|
-
// emit('indicate');
|
package/lib/mac/src/napi_objc.mm
CHANGED
|
@@ -25,7 +25,7 @@ NSString* napiToUuidString(Napi::String string) {
|
|
|
25
25
|
|
|
26
26
|
NSArray* napiToUuidArray(Napi::Array array) {
|
|
27
27
|
NSMutableArray* serviceUuids = [NSMutableArray arrayWithCapacity:array.Length()];
|
|
28
|
-
for(
|
|
28
|
+
for(uint32_t i = 0; i < array.Length(); i++) {
|
|
29
29
|
Napi::Value val = array[i];
|
|
30
30
|
[serviceUuids addObject:napiToUuidString(val.As<Napi::String>())];
|
|
31
31
|
}
|
|
@@ -37,15 +37,14 @@ NSData* napiToData(Napi::Buffer<Byte> buffer) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
NSNumber* napiToNumber(Napi::Number number) {
|
|
40
|
-
return [NSNumber
|
|
40
|
+
return [NSNumber numberWithLong:number.Int64Value()];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
NSArray<CBMutableService *> *napiArrayToCBMutableServices(Napi::Array array) {
|
|
44
|
-
// NSLog(@"napiArrayToCBMutableServices");
|
|
45
44
|
|
|
46
45
|
NSMutableArray *services = [NSMutableArray array];
|
|
47
46
|
|
|
48
|
-
for (
|
|
47
|
+
for (uint32_t i = 0; i < array.Length(); i++) {
|
|
49
48
|
Napi::Value v = array[i];
|
|
50
49
|
Napi::Object obj = v.As<Napi::Object>();
|
|
51
50
|
|
|
@@ -56,12 +55,8 @@ NSArray<CBMutableService *> *napiArrayToCBMutableServices(Napi::Array array) {
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
CBMutableService *napiToCBMutableService(Napi::Object obj) {
|
|
59
|
-
// NSLog(@"napiToCBMutableService");
|
|
60
|
-
|
|
61
58
|
NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
|
|
62
59
|
|
|
63
|
-
// NSLog(@"napiArrayToCBMutableService: uuid:%@", uuid);
|
|
64
|
-
|
|
65
60
|
CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:uuid]
|
|
66
61
|
primary:YES];
|
|
67
62
|
|
|
@@ -73,11 +68,9 @@ CBMutableService *napiToCBMutableService(Napi::Object obj) {
|
|
|
73
68
|
|
|
74
69
|
|
|
75
70
|
NSArray<CBMutableCharacteristic *> *napiArrayToCBMutableCharacteristics(Napi::Array array) {
|
|
76
|
-
// NSLog(@"napiArrayToCBMutableCharacteristics");
|
|
77
|
-
|
|
78
71
|
NSMutableArray *characteristics = [NSMutableArray array];
|
|
79
72
|
|
|
80
|
-
for (
|
|
73
|
+
for (uint32_t i = 0; i < array.Length(); i++) {
|
|
81
74
|
Napi::Value v = array[i];
|
|
82
75
|
Napi::Object obj = v.As<Napi::Object>();
|
|
83
76
|
|
|
@@ -88,13 +81,9 @@ NSArray<CBMutableCharacteristic *> *napiArrayToCBMutableCharacteristics(Napi::Ar
|
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
CBMutableCharacteristic *napiToCBMutableCharacteristic(Napi::Object obj) {
|
|
91
|
-
// NSLog(@"napiToCBMutableCharacteristic");
|
|
92
|
-
|
|
93
84
|
NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
|
|
94
|
-
// NSLog(@"napiToCBMutableCharacteristic: cUUID:%@", uuid);
|
|
95
85
|
|
|
96
86
|
NSData *value = obj.Get("value").IsBuffer() ? napiToData(obj.Get("value").As<Napi::Buffer<Byte>>()) : nil;
|
|
97
|
-
// NSLog(@"napiToCBMutableCharacteristic: value:%@", value);
|
|
98
87
|
|
|
99
88
|
auto properties = obj.Get("properties").As<Napi::Array>();
|
|
100
89
|
auto secure = obj.Get("secure").As<Napi::Array>();
|
|
@@ -112,8 +101,6 @@ CBMutableCharacteristic *napiToCBMutableCharacteristic(Napi::Object obj) {
|
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
CBCharacteristicProperties napiToCBCharacteristicProperties(Napi::Array properties, Napi::Array secure) {
|
|
115
|
-
// NSLog(@"napiToCBCharacteristicProperties");
|
|
116
|
-
|
|
117
104
|
NSArray<NSString *> *p = napiToStringArray(properties);
|
|
118
105
|
NSArray<NSString *> *s = napiToStringArray(secure);
|
|
119
106
|
|
|
@@ -151,8 +138,6 @@ CBCharacteristicProperties napiToCBCharacteristicProperties(Napi::Array properti
|
|
|
151
138
|
}
|
|
152
139
|
|
|
153
140
|
CBAttributePermissions napiToCBAttributePermissions(Napi::Array properties, Napi::Array secure) {
|
|
154
|
-
// NSLog(@"napiToCBAttributePermissions");
|
|
155
|
-
|
|
156
141
|
NSArray<NSString *> *p = napiToStringArray(properties);
|
|
157
142
|
NSArray<NSString *> *s = napiToStringArray(secure);
|
|
158
143
|
|
|
@@ -186,11 +171,9 @@ CBAttributePermissions napiToCBAttributePermissions(Napi::Array properties, Napi
|
|
|
186
171
|
}
|
|
187
172
|
|
|
188
173
|
NSArray<CBDescriptor *> *napiArrayToCBDescriptors(Napi::Array array) {
|
|
189
|
-
// NSLog(@"napiArrayToCBDescriptors");
|
|
190
|
-
|
|
191
174
|
NSMutableArray *descriptors = [NSMutableArray array];
|
|
192
175
|
|
|
193
|
-
for (
|
|
176
|
+
for (uint32_t i = 0; i < array.Length(); i++) {
|
|
194
177
|
Napi::Value v = array[i];
|
|
195
178
|
Napi::Object obj = v.As<Napi::Object>();
|
|
196
179
|
|
|
@@ -204,8 +187,6 @@ CBDescriptor *napiToCBDescriptor(Napi::Object obj) {
|
|
|
204
187
|
NSString *uuid = napiToUuidString(obj.Get("uuid").ToString());
|
|
205
188
|
NSString *value = napiToString(obj.Get("value").ToString());
|
|
206
189
|
|
|
207
|
-
// NSLog(@"napiToCBDescriptor uuid:%@ value:%@", uuid, value);
|
|
208
|
-
|
|
209
190
|
return [[CBMutableDescriptor alloc] initWithType:[CBUUID UUIDWithString:uuid]
|
|
210
191
|
value:value];
|
|
211
192
|
}
|
|
@@ -213,7 +194,7 @@ CBDescriptor *napiToCBDescriptor(Napi::Object obj) {
|
|
|
213
194
|
NSArray<NSString *> *napiToStringArray(Napi::Array array) {
|
|
214
195
|
NSMutableArray *ret = [NSMutableArray arrayWithCapacity:array.Length()];
|
|
215
196
|
|
|
216
|
-
for (
|
|
197
|
+
for (uint32_t i = 0; i < array.Length(); i++) {
|
|
217
198
|
Napi::Value v = array[i];
|
|
218
199
|
Napi::String str = v.ToString();
|
|
219
200
|
|
|
@@ -224,11 +205,9 @@ NSArray<NSString *> *napiToStringArray(Napi::Array array) {
|
|
|
224
205
|
}
|
|
225
206
|
|
|
226
207
|
std::map<Napi::String, Napi::Object> napiArrayToUUIDEmitters(Napi::Array services) {
|
|
227
|
-
// NSLog(@"napiArrayToUUIDEmitters");
|
|
228
|
-
|
|
229
208
|
std::map<Napi::String, Napi::Object> map;
|
|
230
209
|
|
|
231
|
-
for (
|
|
210
|
+
for (uint32_t i = 0; i < services.Length(); i++) {
|
|
232
211
|
Napi::Value vS = services[i];
|
|
233
212
|
Napi::Object objS = vS.As<Napi::Object>();
|
|
234
213
|
|
|
@@ -237,7 +216,7 @@ std::map<Napi::String, Napi::Object> napiArrayToUUIDEmitters(Napi::Array service
|
|
|
237
216
|
map[uuidS] = objS;
|
|
238
217
|
|
|
239
218
|
Napi::Array characteristics = objS.Get("characteristics").As<Napi::Array>();
|
|
240
|
-
for (
|
|
219
|
+
for (uint32_t j = 0; j < characteristics.Length(); j++) {
|
|
241
220
|
Napi::Value vC = characteristics[j];
|
|
242
221
|
Napi::Object objC = vC.As<Napi::Object>();
|
|
243
222
|
|
|
@@ -266,7 +245,7 @@ BOOL getBool(const Napi::Value& value, BOOL def) {
|
|
|
266
245
|
|
|
267
246
|
NSArray* napiToCBUuidArray(Napi::Array array) {
|
|
268
247
|
NSMutableArray* serviceUuids = [NSMutableArray arrayWithCapacity:array.Length()];
|
|
269
|
-
for(
|
|
248
|
+
for(uint32_t i = 0; i < array.Length(); i++) {
|
|
270
249
|
Napi::Value val = array[i];
|
|
271
250
|
[serviceUuids addObject:napiToCBUuidString(val.As<Napi::String>())];
|
|
272
251
|
}
|
package/lib/mac/src/objc_cpp.h
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
#include <string>
|
|
4
4
|
#include <vector>
|
|
5
|
+
|
|
5
6
|
#import <Foundation/Foundation.h>
|
|
6
7
|
#import <CoreBluetooth/CoreBluetooth.h>
|
|
7
|
-
#include "peripheral.h"
|
|
8
8
|
|
|
9
9
|
#define IF(type, var, code) type var = code; if(var)
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@ std::string StringFromCBPeripheralState(CBManagerState state);
|
|
|
21
21
|
|
|
22
22
|
NSString* getNSUuid(CBPeripheral* peripheral);
|
|
23
23
|
std::string getUuid(CBPeripheral* peripheral);
|
|
24
|
-
std::string
|
|
24
|
+
std::string convertToBlenoAddress(NSUUID* uuid);
|
|
25
25
|
std::vector<std::string> getServices(NSArray<CBService*>* services);
|
|
26
26
|
std::vector<std::pair<std::string, std::vector<std::string>>> getCharacteristics(NSArray<CBCharacteristic*>* characteristics);
|
|
27
27
|
std::vector<std::string> getDescriptors(NSArray<CBDescriptor*>* descriptors);
|
package/lib/mac/src/objc_cpp.mm
CHANGED
|
@@ -7,28 +7,6 @@
|
|
|
7
7
|
#include "objc_cpp.h"
|
|
8
8
|
|
|
9
9
|
#if defined(MAC_OS_X_VERSION_10_13)
|
|
10
|
-
//#pragma clang diagnostic push
|
|
11
|
-
//#pragma clang diagnostic ignored "-Wunguarded-availability"
|
|
12
|
-
//std::string stateToString(CBManagerState state)
|
|
13
|
-
//{
|
|
14
|
-
// switch(state) {
|
|
15
|
-
// case CBManagerStatePoweredOff:
|
|
16
|
-
// return "poweredOff";
|
|
17
|
-
// case CBManagerStatePoweredOn:
|
|
18
|
-
// return "poweredOn";
|
|
19
|
-
// case CBManagerStateResetting:
|
|
20
|
-
// return "resetting";
|
|
21
|
-
// case CBManagerStateUnauthorized:
|
|
22
|
-
// return "unauthorized";
|
|
23
|
-
// case CBManagerStateUnknown:
|
|
24
|
-
// return "unknown";
|
|
25
|
-
// case CBManagerStateUnsupported:
|
|
26
|
-
// return "unsupported";
|
|
27
|
-
// }
|
|
28
|
-
// return "unknown";
|
|
29
|
-
//}
|
|
30
|
-
//#pragma clang diagnostic pop
|
|
31
|
-
|
|
32
10
|
// In the 10.13 SDK, CBPeripheral became a subclass of CBPeer, which defines
|
|
33
11
|
// -[CBPeer identifier] as partially available. Pretend it still exists on
|
|
34
12
|
// CBPeripheral. At runtime the implementation on CBPeer will be invoked.
|
|
@@ -82,21 +60,9 @@ std::string getUuid(CBPeripheral* peripheral) {
|
|
|
82
60
|
return std::string([peripheral.identifier.UUIDString UTF8String]);
|
|
83
61
|
}
|
|
84
62
|
|
|
85
|
-
std::string
|
|
86
|
-
NSString*
|
|
87
|
-
|
|
88
|
-
IF(NSDictionary*, cache, [plist objectForKey:@"CoreBluetoothCache"]) {
|
|
89
|
-
IF(NSDictionary*, entry, [cache objectForKey:deviceUuid]) {
|
|
90
|
-
IF(NSNumber*, type, [entry objectForKey:@"DeviceAddressType"]) {
|
|
91
|
-
*addressType = [type boolValue] ? RANDOM : PUBLIC;
|
|
92
|
-
}
|
|
93
|
-
IF(NSString*, address, [entry objectForKey:@"DeviceAddress"]) {
|
|
94
|
-
return [address UTF8String];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return "";
|
|
63
|
+
std::string convertToBlenoAddress(NSUUID* uuid) {
|
|
64
|
+
NSString* addressString = [[uuid.UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString];
|
|
65
|
+
return std::string([addressString UTF8String]);
|
|
100
66
|
}
|
|
101
67
|
|
|
102
68
|
std::vector<std::string> getServices(NSArray<CBService*>* services) {
|
package/lib/resolve-bindings.js
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
|
+
const platform = os.platform();
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
const platform = os.platform();
|
|
4
|
+
const Bleno = require('./bleno');
|
|
5
5
|
|
|
6
|
+
function loadBindings (bindingType = null, options = {}) {
|
|
7
|
+
switch (bindingType) {
|
|
8
|
+
case 'hci':
|
|
9
|
+
return new (require('./hci-socket/bindings'))(options);
|
|
10
|
+
case 'mac':
|
|
11
|
+
return new (require('./mac/bindings'))(options);
|
|
12
|
+
default:
|
|
13
|
+
throw new Error('Unsupported binding type: ' + bindingType);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Default binding selection logic
|
|
18
|
+
function getDefaultBindings (options = {}) {
|
|
6
19
|
if (
|
|
7
20
|
platform === 'linux' ||
|
|
8
|
-
platform === 'freebsd' ||
|
|
21
|
+
platform === 'freebsd' ||
|
|
9
22
|
platform === 'win32' ||
|
|
10
23
|
platform === 'android' ||
|
|
11
24
|
process.env.BLUETOOTH_HCI_SOCKET_UART_PORT ||
|
|
12
|
-
process.env.BLUETOOTH_HCI_SOCKET_FORCE_UART
|
|
13
|
-
|
|
25
|
+
process.env.BLUETOOTH_HCI_SOCKET_FORCE_UART
|
|
26
|
+
) {
|
|
27
|
+
return loadBindings('hci', options);
|
|
14
28
|
} else if (platform === 'darwin') {
|
|
15
|
-
return
|
|
29
|
+
return loadBindings('mac', options);
|
|
16
30
|
} else {
|
|
17
31
|
throw new Error('Unsupported platform ' + platform);
|
|
18
32
|
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = function (bindingType = 'default', options = {}) {
|
|
36
|
+
if (bindingType === 'default') {
|
|
37
|
+
return new Bleno(getDefaultBindings(options));
|
|
38
|
+
}
|
|
39
|
+
return new Bleno(loadBindings(bindingType, options));
|
|
19
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoprocent/bleno",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A Node.js module for implementing BLE (Bluetooth Low Energy) peripherals",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@semantic-release/changelog": "^6.0.3",
|
|
50
50
|
"@semantic-release/exec": "^6.0.3",
|
|
51
51
|
"@semantic-release/git": "^10.0.1",
|
|
52
|
-
"@semantic-release/github": "^
|
|
52
|
+
"@semantic-release/github": "^11.0.1",
|
|
53
53
|
"jshint": "^2.13.6",
|
|
54
54
|
"cross-env": "^7.0.3",
|
|
55
55
|
"eslint": "^8.57.1",
|
|
@@ -60,19 +60,17 @@
|
|
|
60
60
|
"mocha": "^10.7.0",
|
|
61
61
|
"nyc": "^17.0.0",
|
|
62
62
|
"prebuildify": "^6.0.1",
|
|
63
|
-
"prebuildify-cross": "
|
|
63
|
+
"prebuildify-cross": "^5.1.1",
|
|
64
64
|
"should": "^13.2.3"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"debug": "^4.
|
|
67
|
+
"debug": "^4.4.0",
|
|
68
68
|
"napi-thread-safe-callback": "^0.0.6",
|
|
69
|
-
"node-addon-api": "^8.1
|
|
70
|
-
"node-gyp-build": "^4.8.
|
|
69
|
+
"node-addon-api": "^8.3.1",
|
|
70
|
+
"node-gyp-build": "^4.8.4"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
|
-
"@stoprocent/bluetooth-hci-socket": "^1.
|
|
74
|
-
"bplist-parser": "0.3.2",
|
|
75
|
-
"xpc-connect": "^3.0.0"
|
|
73
|
+
"@stoprocent/bluetooth-hci-socket": "^2.1.0"
|
|
76
74
|
},
|
|
77
75
|
"publishConfig": {
|
|
78
76
|
"access": "public"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const should = require('should');
|
|
4
4
|
|
|
5
5
|
const Characteristic = require('../lib/characteristic');
|
|
6
|
+
Characteristic.RESULT_SUCCESS = 0x99; // override default value as 0x00 isnt the best value for this test
|
|
6
7
|
|
|
7
8
|
describe('Characteristic', function () {
|
|
8
9
|
const mockUuid = 'mockuuid';
|
|
@@ -141,34 +142,180 @@ describe('Characteristic', function () {
|
|
|
141
142
|
|
|
142
143
|
it('should handle read request', function (done) {
|
|
143
144
|
const characteristic = new Characteristic({});
|
|
145
|
+
const handle = 0;
|
|
144
146
|
|
|
145
|
-
characteristic.emit('readRequest', 0, function (result, data) {
|
|
146
|
-
result.should.equal(
|
|
147
|
+
characteristic.emit('readRequest', handle, 0, function (result, data) {
|
|
148
|
+
result.should.equal(Characteristic.RESULT_UNLIKELY_ERROR);
|
|
147
149
|
should(data).equal(null);
|
|
148
|
-
|
|
149
150
|
done();
|
|
150
151
|
});
|
|
151
152
|
});
|
|
152
153
|
|
|
153
154
|
it('should handle write request', function (done) {
|
|
154
155
|
const characteristic = new Characteristic({});
|
|
156
|
+
const handle = 0;
|
|
155
157
|
|
|
156
|
-
characteristic.emit('writeRequest', Buffer.alloc(0), 0, false, function (result) {
|
|
157
|
-
result.should.equal(
|
|
158
|
-
|
|
158
|
+
characteristic.emit('writeRequest', handle, Buffer.alloc(0), 0, false, function (result) {
|
|
159
|
+
result.should.equal(Characteristic.RESULT_UNLIKELY_ERROR);
|
|
159
160
|
done();
|
|
160
161
|
});
|
|
161
162
|
});
|
|
162
163
|
|
|
163
164
|
it('should handle unsubscribe', function () {
|
|
164
165
|
const characteristic = new Characteristic({});
|
|
166
|
+
const handle = 0;
|
|
167
|
+
|
|
168
|
+
characteristic._maxValueSizes.set(handle, mockMaxValueSize);
|
|
169
|
+
characteristic._updateValueCallbacks.set(handle, mockUpdateValueCallback);
|
|
170
|
+
|
|
171
|
+
characteristic.emit('unsubscribe', handle);
|
|
172
|
+
|
|
173
|
+
should(characteristic.getMaxValueSize(handle)).equal(undefined);
|
|
174
|
+
should(characteristic._maxValueSizes.has(handle)).equal(false);
|
|
175
|
+
should(characteristic._updateValueCallbacks.has(handle)).equal(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Handle-based updates', function () {
|
|
179
|
+
it('should handle subscribe with different handles', function () {
|
|
180
|
+
const characteristic = new Characteristic({});
|
|
181
|
+
const handle1 = 1;
|
|
182
|
+
const handle2 = 2;
|
|
183
|
+
const maxValueSize1 = 20;
|
|
184
|
+
const maxValueSize2 = 30;
|
|
185
|
+
let updateCallback1Called = false;
|
|
186
|
+
let updateCallback2Called = false;
|
|
187
|
+
|
|
188
|
+
const updateCallback1 = (data) => {
|
|
189
|
+
updateCallback1Called = true;
|
|
190
|
+
should(data).equal('test1');
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const updateCallback2 = (data) => {
|
|
194
|
+
updateCallback2Called = true;
|
|
195
|
+
should(data).equal('test2');
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Subscribe first client
|
|
199
|
+
characteristic.emit('subscribe', handle1, maxValueSize1, updateCallback1);
|
|
200
|
+
should(characteristic.getMaxValueSize(handle1)).equal(maxValueSize1);
|
|
201
|
+
should(characteristic._updateValueCallbacks.has(handle1)).equal(true);
|
|
202
|
+
should(characteristic._updateValueCallbacks.get(handle1)).equal(updateCallback1);
|
|
203
|
+
|
|
204
|
+
// Subscribe second client
|
|
205
|
+
characteristic.emit('subscribe', handle2, maxValueSize2, updateCallback2);
|
|
206
|
+
should(characteristic.getMaxValueSize(handle2)).equal(maxValueSize2);
|
|
207
|
+
should(characteristic._updateValueCallbacks.has(handle2)).equal(true);
|
|
208
|
+
should(characteristic._updateValueCallbacks.get(handle2)).equal(updateCallback2);
|
|
209
|
+
|
|
210
|
+
// Test updates for each handle
|
|
211
|
+
characteristic._updateValueCallbacks.get(handle1)('test1');
|
|
212
|
+
should(updateCallback1Called).equal(true);
|
|
213
|
+
|
|
214
|
+
characteristic._updateValueCallbacks.get(handle2)('test2');
|
|
215
|
+
should(updateCallback2Called).equal(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should handle unsubscribe for specific handle', function () {
|
|
219
|
+
const characteristic = new Characteristic({});
|
|
220
|
+
const handle1 = 1;
|
|
221
|
+
const handle2 = 2;
|
|
222
|
+
const maxValueSize = 20;
|
|
223
|
+
|
|
224
|
+
// Subscribe both clients
|
|
225
|
+
characteristic.emit('subscribe', handle1, maxValueSize, () => {});
|
|
226
|
+
characteristic.emit('subscribe', handle2, maxValueSize, () => {});
|
|
227
|
+
|
|
228
|
+
// Unsubscribe first client
|
|
229
|
+
characteristic.emit('unsubscribe', handle1);
|
|
230
|
+
|
|
231
|
+
// First client should be removed
|
|
232
|
+
should(characteristic.getMaxValueSize(handle1)).equal(undefined);
|
|
233
|
+
should(characteristic._updateValueCallbacks.has(handle1)).equal(false);
|
|
234
|
+
|
|
235
|
+
// Second client should still be subscribed
|
|
236
|
+
should(characteristic.getMaxValueSize(handle2)).equal(maxValueSize);
|
|
237
|
+
should(characteristic._updateValueCallbacks.has(handle2)).equal(true);
|
|
238
|
+
|
|
239
|
+
// Unsubscribe second client
|
|
240
|
+
characteristic.emit('unsubscribe', handle2);
|
|
241
|
+
|
|
242
|
+
// Both clients should be removed
|
|
243
|
+
should(characteristic.getMaxValueSize(handle1)).equal(undefined);
|
|
244
|
+
should(characteristic.getMaxValueSize(handle2)).equal(undefined);
|
|
245
|
+
should(characteristic._updateValueCallbacks.has(handle1)).equal(false);
|
|
246
|
+
should(characteristic._updateValueCallbacks.has(handle2)).equal(false);
|
|
247
|
+
});
|
|
165
248
|
|
|
166
|
-
|
|
167
|
-
|
|
249
|
+
it('should handle read requests for different handles', function (done) {
|
|
250
|
+
const handle1 = 1;
|
|
251
|
+
const handle2 = 2;
|
|
252
|
+
|
|
253
|
+
let readCallback1Called = false;
|
|
254
|
+
let readCallback2Called = false;
|
|
255
|
+
|
|
256
|
+
const characteristic = new Characteristic({
|
|
257
|
+
onReadRequest: (handle, offset, callback) => {
|
|
258
|
+
if (handle === handle1) {
|
|
259
|
+
readCallback1Called = true;
|
|
260
|
+
callback(Characteristic.RESULT_SUCCESS, Buffer.from('test1'));
|
|
261
|
+
} else if (handle === handle2) {
|
|
262
|
+
readCallback2Called = true;
|
|
263
|
+
callback(Characteristic.RESULT_SUCCESS, Buffer.from('test2'));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
168
267
|
|
|
169
|
-
|
|
268
|
+
// Test read request for first handle
|
|
269
|
+
characteristic.emit('readRequest', handle1, 0, (result, data) => {
|
|
270
|
+
should(result).equal(Characteristic.RESULT_SUCCESS);
|
|
271
|
+
should(data.toString()).equal('test1');
|
|
272
|
+
should(readCallback1Called).equal(true);
|
|
273
|
+
});
|
|
170
274
|
|
|
171
|
-
|
|
172
|
-
|
|
275
|
+
// Test read request for second handle
|
|
276
|
+
characteristic.emit('readRequest', handle2, 0, (result, data) => {
|
|
277
|
+
should(result).equal(Characteristic.RESULT_SUCCESS);
|
|
278
|
+
should(data.toString()).equal('test2');
|
|
279
|
+
should(readCallback2Called).equal(true);
|
|
280
|
+
done();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle write requests for different handles', function (done) {
|
|
285
|
+
const handle1 = 1;
|
|
286
|
+
const handle2 = 2;
|
|
287
|
+
|
|
288
|
+
const data1 = Buffer.from('test1');
|
|
289
|
+
const data2 = Buffer.from('test2');
|
|
290
|
+
|
|
291
|
+
let writeCallback1Called = false;
|
|
292
|
+
let writeCallback2Called = false;
|
|
293
|
+
|
|
294
|
+
const characteristic = new Characteristic({
|
|
295
|
+
onWriteRequest: (handle, data, offset, withoutResponse, callback) => {
|
|
296
|
+
if (handle === handle1) {
|
|
297
|
+
writeCallback1Called = true;
|
|
298
|
+
should(data).equal(data1);
|
|
299
|
+
callback(Characteristic.RESULT_SUCCESS);
|
|
300
|
+
} else if (handle === handle2) {
|
|
301
|
+
writeCallback2Called = true;
|
|
302
|
+
should(data).equal(data2);
|
|
303
|
+
callback(Characteristic.RESULT_SUCCESS);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
// Test write request for first handle
|
|
308
|
+
characteristic.emit('writeRequest', handle1, data1, 0, false, (result) => {
|
|
309
|
+
should(result).equal(Characteristic.RESULT_SUCCESS);
|
|
310
|
+
should(writeCallback1Called).equal(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Test write request for second handle
|
|
314
|
+
characteristic.emit('writeRequest', handle2, data2, 0, false, (result) => {
|
|
315
|
+
should(result).equal(Characteristic.RESULT_SUCCESS);
|
|
316
|
+
should(writeCallback2Called).equal(true);
|
|
317
|
+
done();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
173
320
|
});
|
|
174
321
|
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# BLE Battery Service
|
|
2
|
-
|
|
3
|
-
This example provides a BLE battery service (0x180F) for your Mac.
|
|
4
|
-
|
|
5
|
-
Install dependencies
|
|
6
|
-
|
|
7
|
-
npm install
|
|
8
|
-
|
|
9
|
-
Run the example
|
|
10
|
-
|
|
11
|
-
node main.js
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
NOTE: This example no longer works on OSX starting in 10.10 (Yosemite). Apple has apparently blacklisted the battery uuid.
|