@rodrigo7/react-native-beacons-manager 1.0.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/.flowconfig +21 -0
- package/.nvmrc +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +12 -0
- package/.vscode/settings.json +24 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/ReactNativeBeaconsManager.podspec +13 -0
- package/android/.project +17 -0
- package/android/.settings/org.eclipse.buildship.core.prefs +2 -0
- package/android/build.gradle +108 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +6 -0
- package/android/gradle/wrapper/gradle.properties +2 -0
- package/android/gradlew +160 -0
- package/android/gradlew.bat +90 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/mackentoch/beaconsandroid/BeaconsAndroidModule.java +457 -0
- package/android/src/main/java/com/mackentoch/beaconsandroid/BeaconsAndroidPackage.java +29 -0
- package/index.js +10 -0
- package/ios/RNiBeacon/RNiBeacon/ESSBeaconScanner.h +41 -0
- package/ios/RNiBeacon/RNiBeacon/ESSBeaconScanner.m +204 -0
- package/ios/RNiBeacon/RNiBeacon/ESSEddystone.h +117 -0
- package/ios/RNiBeacon/RNiBeacon/ESSEddystone.m +352 -0
- package/ios/RNiBeacon/RNiBeacon/ESSTimer.h +72 -0
- package/ios/RNiBeacon/RNiBeacon/ESSTimer.m +107 -0
- package/ios/RNiBeacon/RNiBeacon/RNiBeacon.h +16 -0
- package/ios/RNiBeacon/RNiBeacon/RNiBeacon.m +467 -0
- package/ios/RNiBeacon/RNiBeacon.xcodeproj/project.pbxproj +290 -0
- package/jsconfig.json +9 -0
- package/lib/next/module.types.js +169 -0
- package/lib/next/new.module.android.js +451 -0
- package/lib/next/new.module.ios.js +176 -0
- package/package.json +65 -0
- package/typings/index.d.ts +174 -0
- package/typings.json +7 -0
- package/yarn-error.log +4182 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Copyright 2015 Google Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
#import <CoreBluetooth/CoreBluetooth.h>
|
|
16
|
+
|
|
17
|
+
#import "ESSBeaconScanner.h"
|
|
18
|
+
#import "ESSEddystone.h"
|
|
19
|
+
#import "ESSTimer.h"
|
|
20
|
+
|
|
21
|
+
static const char *const kBeaconsOperationQueueName = "kESSBeaconScannerBeaconsOperationQueueName";
|
|
22
|
+
static NSString *const kESSEddystoneServiceID = @"FEAA";
|
|
23
|
+
static NSString *const kSeenCacheBeaconInfo = @"beacon_info";
|
|
24
|
+
static NSString *const kSeenCacheOnLostTimer = @"on_lost_timer";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
28
|
+
* Private Additions to ESSBeaconScanner
|
|
29
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
30
|
+
*/
|
|
31
|
+
@interface ESSBeaconScanner () <CBCentralManagerDelegate> {
|
|
32
|
+
CBCentralManager *_centralManager;
|
|
33
|
+
dispatch_queue_t _beaconOperationsQueue;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This cache maps Core Bluetooth deviceIDs to NSData objects containing Eddystone telemetry.
|
|
37
|
+
* Then, the next time we see a UID frame for that Eddystone, we can add the most recently seen
|
|
38
|
+
* telemetry frame to the sighting.
|
|
39
|
+
*/
|
|
40
|
+
NSMutableDictionary *_tlmCache;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Beacons we've seen list.
|
|
44
|
+
*/
|
|
45
|
+
NSMutableDictionary *_eddystoneBeaconsCache;
|
|
46
|
+
|
|
47
|
+
BOOL _shouldBeScanning;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@end
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
54
|
+
* Implementation for ESSBeaconScanner
|
|
55
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
56
|
+
*/
|
|
57
|
+
@implementation ESSBeaconScanner
|
|
58
|
+
|
|
59
|
+
- (instancetype)init {
|
|
60
|
+
if ((self = [super init]) != nil) {
|
|
61
|
+
_onLostTimeout = 5.0;
|
|
62
|
+
_tlmCache = [NSMutableDictionary dictionary];
|
|
63
|
+
_beaconOperationsQueue = dispatch_queue_create(kBeaconsOperationQueueName, NULL);
|
|
64
|
+
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
|
|
65
|
+
queue:_beaconOperationsQueue];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return self;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)startScanning {
|
|
72
|
+
dispatch_async(_beaconOperationsQueue, ^{
|
|
73
|
+
if (_centralManager.state != CBCentralManagerStatePoweredOn) {
|
|
74
|
+
NSLog(@"CBCentralManager state is %ld, cannot start or stop scanning",
|
|
75
|
+
(long)_centralManager.state);
|
|
76
|
+
_shouldBeScanning = YES;
|
|
77
|
+
} else {
|
|
78
|
+
NSLog(@"Starting to scan for Eddystones");
|
|
79
|
+
_eddystoneBeaconsCache = [[NSMutableDictionary alloc] init];
|
|
80
|
+
NSArray *services = @[
|
|
81
|
+
[CBUUID UUIDWithString:kESSEddystoneServiceID]
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
// We do not want multiple discoveries of the same beacon to be coalesced into one.
|
|
85
|
+
// (Unfortunately this is ignored when we are in the background.)
|
|
86
|
+
NSDictionary *options = @{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES };
|
|
87
|
+
[_centralManager scanForPeripheralsWithServices:services options:options];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)stopScanning {
|
|
93
|
+
_shouldBeScanning = NO;
|
|
94
|
+
[_centralManager stopScan];
|
|
95
|
+
[self clearRemainingTimers];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
|
|
99
|
+
if (central.state == CBCentralManagerStatePoweredOn && _shouldBeScanning) {
|
|
100
|
+
[self startScanning];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// This will be called from the |beaconsOperationQueue|.
|
|
105
|
+
- (void)centralManager:(CBCentralManager *)central
|
|
106
|
+
didDiscoverPeripheral:(CBPeripheral *)peripheral
|
|
107
|
+
advertisementData:(NSDictionary *)advertisementData
|
|
108
|
+
RSSI:(NSNumber *)RSSI {
|
|
109
|
+
NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
|
|
110
|
+
NSData *beaconServiceData = serviceData[[ESSBeaconInfo eddystoneServiceID]];
|
|
111
|
+
|
|
112
|
+
ESSFrameType frameType = [ESSBeaconInfo frameTypeForFrame:beaconServiceData];
|
|
113
|
+
|
|
114
|
+
// If it's a telemetry (TLM) frame, then save it into our cache so that the next time we get a
|
|
115
|
+
// UID frame (i.e. an Eddystone "sighting"), we can include the telemetry with it.
|
|
116
|
+
if (frameType == kESSEddystoneTelemetryFrameType) {
|
|
117
|
+
_tlmCache[peripheral.identifier] = beaconServiceData;
|
|
118
|
+
} else if (frameType == kESSEddystoneURLFrameType) {
|
|
119
|
+
NSURL *url = [ESSBeaconInfo parseURLFromFrameData:beaconServiceData];
|
|
120
|
+
|
|
121
|
+
// Report the sighted URL frame.
|
|
122
|
+
if ([_delegate respondsToSelector:@selector(beaconScanner:didFindURL:)]) {
|
|
123
|
+
[_delegate beaconScanner:self didFindURL:url];
|
|
124
|
+
}
|
|
125
|
+
} else if (frameType == kESSEddystoneUIDFrameType
|
|
126
|
+
|| frameType == kESSEddystoneEIDFrameType) {
|
|
127
|
+
CBUUID *eddystoneServiceUUID = [ESSBeaconInfo eddystoneServiceID];
|
|
128
|
+
NSData *eddystoneServiceData = serviceData[eddystoneServiceUUID];
|
|
129
|
+
|
|
130
|
+
// If we have telemetry data for this Eddystone, include it in the construction of the
|
|
131
|
+
// ESSBeaconInfo object. Otherwise, nil is fine.
|
|
132
|
+
NSData *telemetry = _tlmCache[peripheral.identifier];
|
|
133
|
+
|
|
134
|
+
ESSBeaconInfo *beaconInfo;
|
|
135
|
+
if (frameType == kESSEddystoneUIDFrameType) {
|
|
136
|
+
beaconInfo = [ESSBeaconInfo beaconInfoForUIDFrameData:eddystoneServiceData
|
|
137
|
+
telemetry:telemetry
|
|
138
|
+
RSSI:RSSI];
|
|
139
|
+
} else {
|
|
140
|
+
beaconInfo = [ESSBeaconInfo beaconInfoForEIDFrameData:eddystoneServiceData
|
|
141
|
+
telemetry:telemetry
|
|
142
|
+
RSSI:RSSI];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (beaconInfo) {
|
|
146
|
+
// NOTE: At this point you can choose whether to keep or get rid of the telemetry data. You
|
|
147
|
+
// can either opt to include it with every single beacon sighting for this beacon, or
|
|
148
|
+
// delete it until we get a new / "fresh" TLM frame. We'll treat it as "report it only
|
|
149
|
+
// when you see it", so we'll delete it each time.
|
|
150
|
+
[_tlmCache removeObjectForKey:peripheral.identifier];
|
|
151
|
+
|
|
152
|
+
// If we haven't seen this Eddystone before, fire a beaconScanner:didFindBeacon: and mark it
|
|
153
|
+
// as seen.
|
|
154
|
+
if (!_eddystoneBeaconsCache[beaconInfo.beaconID]) {
|
|
155
|
+
ESSTimer *onLostTimer = [ESSTimer scheduledTimerWithDelay:_onLostTimeout
|
|
156
|
+
onQueue:dispatch_get_main_queue()
|
|
157
|
+
block:
|
|
158
|
+
^(ESSTimer *timer) {
|
|
159
|
+
ESSBeaconInfo *lostBeaconInfo =
|
|
160
|
+
_eddystoneBeaconsCache[beaconInfo.beaconID][kSeenCacheBeaconInfo];
|
|
161
|
+
if (lostBeaconInfo) {
|
|
162
|
+
[_eddystoneBeaconsCache removeObjectForKey:beaconInfo.beaconID];
|
|
163
|
+
[self notifyDidRangeBeacon:_eddystoneBeaconsCache];
|
|
164
|
+
}
|
|
165
|
+
}];
|
|
166
|
+
|
|
167
|
+
_eddystoneBeaconsCache[beaconInfo.beaconID] = @{
|
|
168
|
+
kSeenCacheBeaconInfo: beaconInfo,
|
|
169
|
+
kSeenCacheOnLostTimer: onLostTimer,
|
|
170
|
+
};
|
|
171
|
+
[self notifyDidRangeBeacon:_eddystoneBeaconsCache];
|
|
172
|
+
} else {
|
|
173
|
+
// Reset the onLost timer.
|
|
174
|
+
[_eddystoneBeaconsCache[beaconInfo.beaconID][kSeenCacheOnLostTimer] reschedule];
|
|
175
|
+
[self notifyDidRangeBeacon:_eddystoneBeaconsCache];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
NSLog(@"Unsupported frame type (%d) detected. Ignorning.", (int)frameType);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Commont event about beacons changes in region
|
|
185
|
+
*/
|
|
186
|
+
- (void)notifyDidRangeBeacon:(NSMutableDictionary *)beacons {
|
|
187
|
+
if (![_delegate respondsToSelector:@selector(beaconScanner:didRangeBeacon:)]) return;
|
|
188
|
+
|
|
189
|
+
NSMutableArray *beaconArray = [[NSMutableArray alloc] init];
|
|
190
|
+
for (id key in beacons) {
|
|
191
|
+
[beaconArray addObject:beacons[key][kSeenCacheBeaconInfo]];
|
|
192
|
+
}
|
|
193
|
+
[_delegate beaconScanner:self didRangeBeacon:beaconArray];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)clearRemainingTimers {
|
|
197
|
+
for (ESSBeaconID *beaconID in _eddystoneBeaconsCache) {
|
|
198
|
+
[_eddystoneBeaconsCache[beaconID][kSeenCacheOnLostTimer] cancel];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_eddystoneBeaconsCache = nil;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Copyright 2015 Google Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
#import <Foundation/Foundation.h>
|
|
16
|
+
#import <CoreBluetooth/CoreBluetooth.h>
|
|
17
|
+
|
|
18
|
+
typedef NS_ENUM(NSUInteger, ESSBeaconType) {
|
|
19
|
+
kESSBeaconTypeEddystone = 1,
|
|
20
|
+
kESSBeaconTypeEddystoneEID = 2,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
typedef NS_ENUM(NSUInteger, ESSFrameType) {
|
|
24
|
+
kESSEddystoneUnknownFrameType = 0,
|
|
25
|
+
kESSEddystoneUIDFrameType,
|
|
26
|
+
kESSEddystoneURLFrameType,
|
|
27
|
+
kESSEddystoneEIDFrameType,
|
|
28
|
+
kESSEddystoneTelemetryFrameType,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
33
|
+
* ESSBeaconID
|
|
34
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
35
|
+
*/
|
|
36
|
+
@interface ESSBeaconID : NSObject <NSCopying>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The type of the beacon. Currently only a couple of types are supported.
|
|
40
|
+
*/
|
|
41
|
+
@property(nonatomic, assign, readonly) ESSBeaconType beaconType;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The raw beaconID data.
|
|
45
|
+
*/
|
|
46
|
+
@property(nonatomic, copy, readonly) NSData *beaconID;
|
|
47
|
+
|
|
48
|
+
@end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
53
|
+
* ESSBeaconInfo
|
|
54
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
55
|
+
*/
|
|
56
|
+
@interface ESSBeaconInfo : NSObject
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The most recent RSSI we got for this sighting. Sometimes the OS cannot compute one reliably, so
|
|
60
|
+
* this value can be null.
|
|
61
|
+
*/
|
|
62
|
+
@property(nonatomic, strong, readonly) NSNumber *RSSI;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The beaconID for this Eddystone. All beacons have an ID.
|
|
66
|
+
*/
|
|
67
|
+
@property(nonatomic, strong, readonly) ESSBeaconID *beaconID;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The telemetry that may or may not have been seen for this beacon. If it's set, the contents of
|
|
71
|
+
* it aren't terribly relevant to us, in general. See the Eddystone spec for more information
|
|
72
|
+
* if you're really interested in the exact details.
|
|
73
|
+
*/
|
|
74
|
+
@property(nonatomic, copy, readonly) NSData *telemetry;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Transmission power reported by beacon. This is in dB.
|
|
78
|
+
*/
|
|
79
|
+
@property(nonatomic, strong, readonly) NSNumber *txPower;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The scanner has seen a frame for an Eddystone. We'll need to know what type of Eddystone frame
|
|
83
|
+
* it is, as there are a few types.
|
|
84
|
+
*/
|
|
85
|
+
+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Given the service data for a frame we know to be a UID frame, an RSSI sighting,
|
|
89
|
+
* and -- optionally -- telemetry data (if we've seen it), create a new ESSBeaconInfo object to
|
|
90
|
+
* represent this Eddystone
|
|
91
|
+
*/
|
|
92
|
+
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
|
|
93
|
+
telemetry:(NSData *)telemetry
|
|
94
|
+
RSSI:(NSNumber *)initialRSSI;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Given the service data for a frame we know to be a UID frame, an RSSI sighting,
|
|
98
|
+
* and -- optionally -- telemetry data (if we've seen it), create a new ESSBeaconInfo object to
|
|
99
|
+
* represent this Eddystone
|
|
100
|
+
*/
|
|
101
|
+
+ (instancetype)beaconInfoForEIDFrameData:(NSData *)EIDFrameData
|
|
102
|
+
telemetry:(NSData *)telemetry
|
|
103
|
+
RSSI:(NSNumber *)initialRSSI;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* If we're given a URL frame, extract the URL from it.
|
|
107
|
+
*/
|
|
108
|
+
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convenience method to save everybody from creating these things all the time.
|
|
112
|
+
*/
|
|
113
|
+
+ (CBUUID *)eddystoneServiceID;
|
|
114
|
+
|
|
115
|
+
+ (ESSBeaconInfo *)testBeaconFromBeaconIDString:(NSString *)beaconID;
|
|
116
|
+
|
|
117
|
+
@end
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Copyright 2015 Google Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
#import "ESSEddystone.h"
|
|
16
|
+
|
|
17
|
+
#import <CoreBluetooth/CoreBluetooth.h>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The Bluetooth Service ID for Eddystones.
|
|
21
|
+
*/
|
|
22
|
+
static NSString *const kEddystoneServiceID = @"FEAA";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Eddystones can have different frame types. Within the frames, these are (some of) the possible
|
|
26
|
+
* values. See the Eddystone spec for complete details.
|
|
27
|
+
*/
|
|
28
|
+
static const uint8_t kEddystoneUIDFrameTypeID = 0x00;
|
|
29
|
+
static const uint8_t kEddystoneURLFrameTypeID = 0x10;
|
|
30
|
+
static const uint8_t kEddystoneTLMFrameTypeID = 0x20;
|
|
31
|
+
static const uint8_t kEddystoneEIDFrameTypeID = 0x30;
|
|
32
|
+
|
|
33
|
+
// Note that for these Eddystone structures, the endianness of the individual fields is big-endian,
|
|
34
|
+
// so you'll want to translate back to host format when necessary.
|
|
35
|
+
// Note that in the Eddystone spec, the beaconID (UID) is divided into 2 fields, a 10 byte namespace
|
|
36
|
+
// and a 6 byte instance id. However, since we ALWAYS use these in combination as a 16 byte
|
|
37
|
+
// beaconID, we'll have our structure here reflect that.
|
|
38
|
+
typedef struct __attribute__((packed)) {
|
|
39
|
+
uint8_t frameType;
|
|
40
|
+
int8_t txPower;
|
|
41
|
+
uint8_t beaconID[16];
|
|
42
|
+
uint8_t RFU[2];
|
|
43
|
+
} ESSEddystoneUIDFrameFields;
|
|
44
|
+
|
|
45
|
+
typedef struct __attribute__((packed)) {
|
|
46
|
+
uint8_t frameType;
|
|
47
|
+
int8_t txPower;
|
|
48
|
+
uint8_t beaconID[8];
|
|
49
|
+
} ESSEddystoneEIDFrameFields;
|
|
50
|
+
|
|
51
|
+
// Test equality, ensuring that nil is equal to itself.
|
|
52
|
+
static inline BOOL IsEqualOrBothNil(id a, id b) {
|
|
53
|
+
return ((a == b) || (a && b && [a isEqual:b]));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
58
|
+
* ESSBeaconID
|
|
59
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
60
|
+
*/
|
|
61
|
+
@implementation ESSBeaconID
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* This property is orginally declared in a superclass (NSObject), so cannot be auto-synthesized.
|
|
65
|
+
*/
|
|
66
|
+
@synthesize hash = _hash;
|
|
67
|
+
|
|
68
|
+
- (instancetype)initWithType:(ESSBeaconType)beaconType
|
|
69
|
+
beaconID:(NSData *)beaconID {
|
|
70
|
+
self = [super init];
|
|
71
|
+
if (self) {
|
|
72
|
+
_beaconType = beaconType;
|
|
73
|
+
_beaconID = [beaconID copy];
|
|
74
|
+
_hash = 31 * self.beaconType + [self.beaconID hash];
|
|
75
|
+
}
|
|
76
|
+
return self;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* So that whenever you convert this to a string, you get something useful.
|
|
81
|
+
*/
|
|
82
|
+
- (NSString *)description {
|
|
83
|
+
if (self.beaconType == kESSBeaconTypeEddystone) {
|
|
84
|
+
return [NSString stringWithFormat:@"ESSBeaconID: beaconID=%@", self.beaconID];
|
|
85
|
+
} else if (self.beaconType == kESSBeaconTypeEddystoneEID) {
|
|
86
|
+
return [NSString stringWithFormat:@"ESSBeaconID (EID): beaconID=%@", self.beaconID];
|
|
87
|
+
} else {
|
|
88
|
+
return [NSString stringWithFormat:@"ESSBeaconID with invalid type %lu",
|
|
89
|
+
(unsigned long)self.beaconType];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (BOOL)isEqual:(id)object {
|
|
94
|
+
if (object == self) {
|
|
95
|
+
return YES;
|
|
96
|
+
}
|
|
97
|
+
if (!self
|
|
98
|
+
|| !object
|
|
99
|
+
|| !([self isKindOfClass:[object class]] || [object isKindOfClass:[self class]])) {
|
|
100
|
+
return NO;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ESSBeaconID *other = (ESSBeaconID *)object;
|
|
104
|
+
return ((self.beaconType == other.beaconType) &&
|
|
105
|
+
IsEqualOrBothNil(self.beaconID, other.beaconID));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (id)copyWithZone:(NSZone *)zone {
|
|
109
|
+
// Immutable object: 'copy' by reusing the same instance.
|
|
110
|
+
return self;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
118
|
+
* ESSBeaconInfo
|
|
119
|
+
*=-----------------------------------------------------------------------------------------------=
|
|
120
|
+
*/
|
|
121
|
+
@implementation ESSBeaconInfo
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Given the advertising frames from CoreBluetooth for a device with the Eddystone Service ID,
|
|
125
|
+
* figure out what type of frame it is.
|
|
126
|
+
*/
|
|
127
|
+
+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData {
|
|
128
|
+
// It's an Eddystone ADV frame. Now check if it's a UID (ID) or TLM (telemetry) frame.
|
|
129
|
+
if (frameData) {
|
|
130
|
+
uint8_t frameType;
|
|
131
|
+
if ([frameData length] > 1) {
|
|
132
|
+
frameType = ((uint8_t *)[frameData bytes])[0];
|
|
133
|
+
switch (frameType) {
|
|
134
|
+
case kEddystoneUIDFrameTypeID:
|
|
135
|
+
return kESSEddystoneUIDFrameType;
|
|
136
|
+
case kEddystoneURLFrameTypeID:
|
|
137
|
+
return kESSEddystoneURLFrameType;
|
|
138
|
+
case kEddystoneTLMFrameTypeID:
|
|
139
|
+
return kESSEddystoneTelemetryFrameType;
|
|
140
|
+
case kEddystoneEIDFrameTypeID:
|
|
141
|
+
return kESSEddystoneEIDFrameType;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return kESSEddystoneUnknownFrameType;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData {
|
|
150
|
+
NSAssert([ESSBeaconInfo frameTypeForFrame:URLFrameData] == kESSEddystoneURLFrameType,
|
|
151
|
+
@"This should be a URL frame, but it's not. Whooops");
|
|
152
|
+
|
|
153
|
+
if (!(URLFrameData.length > 0)) {
|
|
154
|
+
return nil;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
unsigned char urlFrame[20];
|
|
158
|
+
[URLFrameData getBytes:&urlFrame length:URLFrameData.length];
|
|
159
|
+
|
|
160
|
+
NSString *urlScheme = [self getURLScheme:*(urlFrame+2)];
|
|
161
|
+
|
|
162
|
+
NSString *urlString = urlScheme;
|
|
163
|
+
for (int i = 0; i < URLFrameData.length - 3; i++) {
|
|
164
|
+
urlString = [urlString stringByAppendingString:[self getEncodedString:*(urlFrame + i + 3)]];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return [NSURL URLWithString:urlString];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
- (instancetype)initWithBeaconID:(ESSBeaconID *)beaconID
|
|
171
|
+
txPower:(NSNumber *)txPower
|
|
172
|
+
RSSI:(NSNumber *)RSSI
|
|
173
|
+
telemetry:(NSData *)telemetry {
|
|
174
|
+
if ((self = [super init]) != nil) {
|
|
175
|
+
_beaconID = beaconID;
|
|
176
|
+
_txPower = txPower;
|
|
177
|
+
_RSSI = RSSI;
|
|
178
|
+
_telemetry = [telemetry copy];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return self;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
|
|
185
|
+
telemetry:(NSData *)telemetry
|
|
186
|
+
RSSI:(NSNumber *)RSSI {
|
|
187
|
+
NSAssert([ESSBeaconInfo frameTypeForFrame:UIDFrameData] == kESSEddystoneUIDFrameType,
|
|
188
|
+
@"This should be a UID frame, but it's not. Whooops");
|
|
189
|
+
|
|
190
|
+
// Make sure this frame has the correct frame type identifier
|
|
191
|
+
uint8_t frameType;
|
|
192
|
+
[UIDFrameData getBytes:&frameType length:1];
|
|
193
|
+
if (frameType != kEddystoneUIDFrameTypeID) {
|
|
194
|
+
return nil;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ESSEddystoneUIDFrameFields uidFrame;
|
|
198
|
+
|
|
199
|
+
if ([UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields)
|
|
200
|
+
|| [UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields) - sizeof(uidFrame.RFU)) {
|
|
201
|
+
|
|
202
|
+
[UIDFrameData getBytes:&uidFrame length:(sizeof(ESSEddystoneUIDFrameFields)
|
|
203
|
+
- sizeof(uidFrame.RFU))];
|
|
204
|
+
|
|
205
|
+
NSData *beaconIDData = [NSData dataWithBytes:&uidFrame.beaconID
|
|
206
|
+
length:sizeof(uidFrame.beaconID)];
|
|
207
|
+
|
|
208
|
+
ESSBeaconID *beaconID = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
|
|
209
|
+
beaconID:beaconIDData];
|
|
210
|
+
if (beaconID == nil) {
|
|
211
|
+
return nil;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return [[ESSBeaconInfo alloc] initWithBeaconID:beaconID
|
|
215
|
+
txPower:@(uidFrame.txPower)
|
|
216
|
+
RSSI:RSSI
|
|
217
|
+
telemetry:telemetry];
|
|
218
|
+
} else {
|
|
219
|
+
return nil;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
+ (instancetype)beaconInfoForEIDFrameData:(NSData *)EIDFrameData
|
|
224
|
+
telemetry:(NSData *)telemetry
|
|
225
|
+
RSSI:(NSNumber *)RSSI {
|
|
226
|
+
NSAssert([ESSBeaconInfo frameTypeForFrame:EIDFrameData] == kESSEddystoneEIDFrameType,
|
|
227
|
+
@"This should be an EID frame, but it's not. Whooops");
|
|
228
|
+
|
|
229
|
+
// Make sure this frame has the correct frame type identifier
|
|
230
|
+
uint8_t frameType;
|
|
231
|
+
[EIDFrameData getBytes:&frameType length:1];
|
|
232
|
+
if (frameType != kEddystoneEIDFrameTypeID) {
|
|
233
|
+
return nil;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
ESSEddystoneEIDFrameFields eidFrame;
|
|
237
|
+
|
|
238
|
+
if ([EIDFrameData length] == sizeof(ESSEddystoneEIDFrameFields)) {
|
|
239
|
+
[EIDFrameData getBytes:&eidFrame length:sizeof(ESSEddystoneEIDFrameFields)];
|
|
240
|
+
NSData *beaconIDData = [NSData dataWithBytes:&eidFrame.beaconID
|
|
241
|
+
length:sizeof(eidFrame.beaconID)];
|
|
242
|
+
|
|
243
|
+
ESSBeaconID *beaconID = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystoneEID
|
|
244
|
+
beaconID:beaconIDData];
|
|
245
|
+
if (beaconID == nil) {
|
|
246
|
+
return nil;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return [[ESSBeaconInfo alloc] initWithBeaconID:beaconID
|
|
250
|
+
txPower:@(eidFrame.txPower)
|
|
251
|
+
RSSI:RSSI
|
|
252
|
+
telemetry:telemetry];
|
|
253
|
+
} else {
|
|
254
|
+
return nil;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
- (NSString *)description {
|
|
259
|
+
NSString *str = [NSString stringWithFormat:@"Eddystone, id: %@, RSSI: %@, txPower: %@",
|
|
260
|
+
_beaconID, _RSSI, _txPower];
|
|
261
|
+
return str;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
+ (CBUUID *)eddystoneServiceID {
|
|
265
|
+
static CBUUID *_singleton;
|
|
266
|
+
static dispatch_once_t oncePredicate;
|
|
267
|
+
|
|
268
|
+
dispatch_once(&oncePredicate, ^{
|
|
269
|
+
_singleton = [CBUUID UUIDWithString:kEddystoneServiceID];
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return _singleton;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
+ (ESSBeaconInfo *)testBeaconFromBeaconIDString:(NSString *)beaconID {
|
|
276
|
+
NSData *beaconIDData = [ESSBeaconInfo hexStringToNSData:beaconID];
|
|
277
|
+
ESSBeaconID *beaconIDObj = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
|
|
278
|
+
beaconID:beaconIDData];
|
|
279
|
+
|
|
280
|
+
return [[ESSBeaconInfo alloc] initWithBeaconID:beaconIDObj
|
|
281
|
+
txPower:@(-20)
|
|
282
|
+
RSSI:@(-100)
|
|
283
|
+
telemetry:nil];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
+ (NSData *)hexStringToNSData:(NSString *)hexString {
|
|
287
|
+
NSMutableData *data = [[NSMutableData alloc] init];
|
|
288
|
+
unsigned char whole_byte;
|
|
289
|
+
char byte_chars[3] = {'\0','\0','\0'};
|
|
290
|
+
|
|
291
|
+
int i;
|
|
292
|
+
for (i = 0; i < [hexString length]/2; i++) {
|
|
293
|
+
byte_chars[0] = [hexString characterAtIndex:i * 2];
|
|
294
|
+
byte_chars[1] = [hexString characterAtIndex:i * 2 + 1];
|
|
295
|
+
whole_byte = strtol(byte_chars, NULL, 16);
|
|
296
|
+
[data appendBytes:&whole_byte length:1];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return data;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
+ (NSString *)getURLScheme:(char)hexChar {
|
|
303
|
+
switch (hexChar) {
|
|
304
|
+
case 0x00:
|
|
305
|
+
return @"http://www.";
|
|
306
|
+
case 0x01:
|
|
307
|
+
return @"https://www.";
|
|
308
|
+
case 0x02:
|
|
309
|
+
return @"http://";
|
|
310
|
+
case 0x03:
|
|
311
|
+
return @"https://";
|
|
312
|
+
default:
|
|
313
|
+
return nil;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
+ (NSString *)getEncodedString:(char)hexChar {
|
|
318
|
+
switch (hexChar) {
|
|
319
|
+
case 0x00:
|
|
320
|
+
return @".com/";
|
|
321
|
+
case 0x01:
|
|
322
|
+
return @".org/";
|
|
323
|
+
case 0x02:
|
|
324
|
+
return @".edu/";
|
|
325
|
+
case 0x03:
|
|
326
|
+
return @".net/";
|
|
327
|
+
case 0x04:
|
|
328
|
+
return @".info/";
|
|
329
|
+
case 0x05:
|
|
330
|
+
return @".biz/";
|
|
331
|
+
case 0x06:
|
|
332
|
+
return @".gov/";
|
|
333
|
+
case 0x07:
|
|
334
|
+
return @".com";
|
|
335
|
+
case 0x08:
|
|
336
|
+
return @".org";
|
|
337
|
+
case 0x09:
|
|
338
|
+
return @".edu";
|
|
339
|
+
case 0x0a:
|
|
340
|
+
return @".net";
|
|
341
|
+
case 0x0b:
|
|
342
|
+
return @".info";
|
|
343
|
+
case 0x0c:
|
|
344
|
+
return @".biz";
|
|
345
|
+
case 0x0d:
|
|
346
|
+
return @".gov";
|
|
347
|
+
default:
|
|
348
|
+
return [NSString stringWithFormat:@"%c", hexChar];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@end
|