Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.

Commit d3c2af4

Browse files
committed
Merge pull request #145 from google/master
Updates for ObjC and Swift scanners, EID support
2 parents 6c9a0a5 + 66a80a1 commit d3c2af4

9 files changed

Lines changed: 321 additions & 182 deletions

File tree

tools/ios-eddystone-scanner-sample/EddystoneScannerSample/ESSBeaconScanner.m

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ @interface ESSBeaconScanner () <CBCentralManagerDelegate> {
4141
*/
4242
NSMutableDictionary *_tlmCache;
4343

44-
/**
45-
* This cache maps Core Bluetooth deviceIDs to NSData objects containing Eddystone url.
46-
* Then, the next time we see a URL frame for that Eddystone, we can add the most recently seen
47-
* url frame to the sighting.
48-
*/
49-
NSMutableDictionary *_urlCache;
50-
5144
/**
5245
* Beacons we've seen already. If we see an Eddystone and notice that we've seen it before, we
5346
* won't fire a beaconScanner:didFindBeacon:, but instead will fire a
@@ -71,7 +64,6 @@ - (instancetype)init {
7164
if ((self = [super init]) != nil) {
7265
_onLostTimeout = 15.0;
7366
_tlmCache = [NSMutableDictionary dictionary];
74-
_urlCache = [NSMutableDictionary dictionary];
7567
_seenEddystoneCache = [NSMutableDictionary dictionary];
7668
_beaconOperationsQueue = dispatch_queue_create(kBeaconsOperationQueueName, NULL);
7769
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
@@ -115,46 +107,46 @@ - (void)centralManagerDidUpdateState:(CBCentralManager *)central {
115107

116108
// This will be called from the |beaconsOperationQueue|.
117109
- (void)centralManager:(CBCentralManager *)central
118-
didDiscoverPeripheral:(CBPeripheral *)peripheral
119-
advertisementData:(NSDictionary *)advertisementData
120-
RSSI:(NSNumber *)RSSI {
121-
110+
didDiscoverPeripheral:(CBPeripheral *)peripheral
111+
advertisementData:(NSDictionary *)advertisementData
112+
RSSI:(NSNumber *)RSSI {
122113
NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
114+
NSData *beaconServiceData = serviceData[[ESSBeaconInfo eddystoneServiceID]];
115+
116+
ESSFrameType frameType = [ESSBeaconInfo frameTypeForFrame:beaconServiceData];
123117

124118
// If it's a telemetry (TLM) frame, then save it into our cache so that the next time we get a
125119
// UID frame (i.e. an Eddystone "sighting"), we can include the telemetry with it.
126-
ESSFrameType frameType = [ESSBeaconInfo frameTypeForFrame:serviceData];
127120
if (frameType == kESSEddystoneTelemetryFrameType) {
128-
_tlmCache[peripheral.identifier] = [ESSBeaconInfo telemetryDataForFrame:serviceData];
121+
_tlmCache[peripheral.identifier] = beaconServiceData;
129122
} else if (frameType == kESSEddystoneURLFrameType) {
130-
// If it's a URL frame, then save it into our cache so that the next time we get a
131-
// UID frame (i.e. an Eddystone "sighting"), we can include the URL data with it.
132-
NSURL *url = [ESSBeaconInfo URLForForFrame:serviceData];
133-
_urlCache[peripheral.identifier] = url;
134-
135-
// Reporting for URL frames as well, even if the underlying hardware isn't broadcasting UID
136-
// frames.
123+
NSURL *url = [ESSBeaconInfo parseURLFromFrameData:beaconServiceData];
124+
125+
// Report the sighted URL frame.
137126
if ([_delegate respondsToSelector:@selector(beaconScanner:didFindURL:)]) {
138127
[_delegate beaconScanner:self didFindURL:url];
139128
}
140-
} else if (frameType == kESSEddystoneUIDFrameType) {
129+
} else if (frameType == kESSEddystoneUIDFrameType
130+
|| frameType == kESSEddystoneEIDFrameType) {
141131
CBUUID *eddystoneServiceUUID = [ESSBeaconInfo eddystoneServiceID];
142132
NSData *eddystoneServiceData = serviceData[eddystoneServiceUUID];
143133

144134
// If we have telemetry data for this Eddystone, include it in the construction of the
145135
// ESSBeaconInfo object. Otherwise, nil is fine.
146136
NSData *telemetry = _tlmCache[peripheral.identifier];
147137

148-
// If we have URL data for this Eddystone, include it in the construction of the
149-
// ESSBeaconInfo object. Otherwise, nil is fine.
150-
NSURL *URL = _urlCache[peripheral.identifier];
151-
152-
ESSBeaconInfo *beaconInfo = [ESSBeaconInfo beaconInfoForUIDFrameData:eddystoneServiceData
153-
URL:URL
154-
telemetry:telemetry
155-
RSSI:RSSI];
138+
ESSBeaconInfo *beaconInfo;
139+
if (frameType == kESSEddystoneUIDFrameType) {
140+
beaconInfo = [ESSBeaconInfo beaconInfoForUIDFrameData:eddystoneServiceData
141+
telemetry:telemetry
142+
RSSI:RSSI];
143+
} else {
144+
beaconInfo = [ESSBeaconInfo beaconInfoForEIDFrameData:eddystoneServiceData
145+
telemetry:telemetry
146+
RSSI:RSSI];
147+
}
156148

157-
if (beaconInfo != nil) {
149+
if (beaconInfo) {
158150
// NOTE: At this point you can choose whether to keep or get rid of the telemetry data. You
159151
// can either opt to include it with every single beacon sighting for this beacon, or
160152
// delete it until we get a new / "fresh" TLM frame. We'll treat it as "report it only

tools/ios-eddystone-scanner-sample/EddystoneScannerSample/ESSEddystone.h

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717

1818
typedef NS_ENUM(NSUInteger, ESSBeaconType) {
1919
kESSBeaconTypeEddystone = 1,
20+
kESSBeaconTypeEddystoneEID = 2,
2021
};
2122

2223
typedef NS_ENUM(NSUInteger, ESSFrameType) {
2324
kESSEddystoneUnknownFrameType = 0,
24-
kESSEddystoneUIDFrameType = 1,
25-
kESSEddystoneURLFrameType = 2,
25+
kESSEddystoneUIDFrameType,
26+
kESSEddystoneURLFrameType,
27+
kESSEddystoneEIDFrameType,
2628
kESSEddystoneTelemetryFrameType,
2729
};
2830

@@ -34,8 +36,7 @@ typedef NS_ENUM(NSUInteger, ESSFrameType) {
3436
@interface ESSBeaconID : NSObject <NSCopying>
3537

3638
/**
37-
* Currently there's only the Eddystone format, but we'd like to leave the door open to other
38-
* possibilities, so let's have a beacon type here in the info.
39+
* The type of the beacon. Currently only a couple of types are supported.
3940
*/
4041
@property(nonatomic, assign, readonly) ESSBeaconType beaconType;
4142

@@ -60,7 +61,6 @@ typedef NS_ENUM(NSUInteger, ESSFrameType) {
6061
*/
6162
@property(nonatomic, strong, readonly) NSNumber *RSSI;
6263

63-
6464
/**
6565
* The beaconID for this Eddystone. All beacons have an ID.
6666
*/
@@ -78,39 +78,35 @@ typedef NS_ENUM(NSUInteger, ESSFrameType) {
7878
*/
7979
@property(nonatomic, strong, readonly) NSNumber *txPower;
8080

81-
/**
82-
* URL broadcasted by beacon.
83-
*/
84-
@property(nonatomic, strong, readonly) NSURL *URL;
85-
8681
/**
8782
* The scanner has seen a frame for an Eddystone. We'll need to know what type of Eddystone frame
8883
* it is, as there are a few types.
8984
*/
90-
+ (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList;
85+
+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData;
9186

9287
/**
93-
* Given some advertisement data that we have already verified is a TLM frame (using
94-
* frameTypeForFrame:), return the actual telemetry data for that frame.
95-
*/
96-
+ (NSData *)telemetryDataForFrame:(NSDictionary *)advFrameList;
97-
98-
/**
99-
* Given some advertisement data that we have already verified is a URL frame (using
100-
* frameTypeForFrame:), return the URL for that frame.
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
10191
*/
102-
+ (NSURL *)URLForForFrame:(NSDictionary *)advFrameList;
92+
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
93+
telemetry:(NSData *)telemetry
94+
RSSI:(NSNumber *)initialRSSI;
10395

10496
/**
10597
* Given the service data for a frame we know to be a UID frame, an RSSI sighting,
10698
* and -- optionally -- telemetry data (if we've seen it), create a new ESSBeaconInfo object to
10799
* represent this Eddystone
108100
*/
109-
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
110-
URL:(NSURL *)URL
101+
+ (instancetype)beaconInfoForEIDFrameData:(NSData *)EIDFrameData
111102
telemetry:(NSData *)telemetry
112103
RSSI:(NSNumber *)initialRSSI;
113104

105+
/**
106+
* If we're given a URL frame, extract the URL from it.
107+
*/
108+
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData;
109+
114110
/**
115111
* Convenience method to save everybody from creating these things all the time.
116112
*/

tools/ios-eddystone-scanner-sample/EddystoneScannerSample/ESSEddystone.m

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
static const uint8_t kEddystoneUIDFrameTypeID = 0x00;
2929
static const uint8_t kEddystoneURLFrameTypeID = 0x10;
3030
static const uint8_t kEddystoneTLMFrameTypeID = 0x20;
31+
static const uint8_t kEddystoneEIDFrameTypeID = 0x30;
3132

3233
// Note that for these Eddystone structures, the endianness of the individual fields is big-endian,
3334
// so you'll want to translate back to host format when necessary.
@@ -41,6 +42,12 @@ typedef struct __attribute__((packed)) {
4142
uint8_t RFU[2];
4243
} ESSEddystoneUIDFrameFields;
4344

45+
typedef struct __attribute__((packed)) {
46+
uint8_t frameType;
47+
int8_t txPower;
48+
uint8_t beaconID[8];
49+
} ESSEddystoneEIDFrameFields;
50+
4451
// Test equality, ensuring that nil is equal to itself.
4552
static inline BOOL IsEqualOrBothNil(id a, id b) {
4653
return ((a == b) || (a && b && [a isEqual:b]));
@@ -75,6 +82,8 @@ - (instancetype)initWithType:(ESSBeaconType)beaconType
7582
- (NSString *)description {
7683
if (self.beaconType == kESSBeaconTypeEddystone) {
7784
return [NSString stringWithFormat:@"ESSBeaconID: beaconID=%@", self.beaconID];
85+
} else if (self.beaconType == kESSBeaconTypeEddystoneEID) {
86+
return [NSString stringWithFormat:@"ESSBeaconID (EID): beaconID=%@", self.beaconID];
7887
} else {
7988
return [NSString stringWithFormat:@"ESSBeaconID with invalid type %lu",
8089
(unsigned long)self.beaconType];
@@ -115,9 +124,7 @@ @implementation ESSBeaconInfo
115124
* Given the advertising frames from CoreBluetooth for a device with the Eddystone Service ID,
116125
* figure out what type of frame it is.
117126
*/
118-
+ (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList {
119-
NSData *frameData = advFrameList[[self eddystoneServiceID]];
120-
127+
+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData {
121128
// It's an Eddystone ADV frame. Now check if it's a UID (ID) or TLM (telemetry) frame.
122129
if (frameData) {
123130
uint8_t frameType;
@@ -130,28 +137,19 @@ + (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList {
130137
return kESSEddystoneURLFrameType;
131138
case kEddystoneTLMFrameTypeID:
132139
return kESSEddystoneTelemetryFrameType;
140+
case kEddystoneEIDFrameTypeID:
141+
return kESSEddystoneEIDFrameType;
133142
}
134143
}
135144
}
136145

137146
return kESSEddystoneUnknownFrameType;
138147
}
139148

140-
+ (NSData *)telemetryDataForFrame:(NSDictionary *)advFrameList {
141-
// NOTE: We assume that you've already called [ESSBeaconInfo frameTypeForFrame] to confirm that
142-
// this actually IS a telemetry frame.
143-
NSAssert([ESSBeaconInfo frameTypeForFrame:advFrameList] == kESSEddystoneTelemetryFrameType,
144-
@"This should be a TLM frame, but it's not. Whooops");
145-
return advFrameList[[self eddystoneServiceID]];
146-
}
147-
148-
+ (NSURL *)URLForForFrame:(NSDictionary *)advFrameList {
149-
// NOTE: We assume that you've already called [ESSBeaconInfo frameTypeForFrame] to confirm that
150-
// this actually IS a URL frame.
151-
NSAssert([ESSBeaconInfo frameTypeForFrame:advFrameList] == kESSEddystoneURLFrameType,
149+
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData {
150+
NSAssert([ESSBeaconInfo frameTypeForFrame:URLFrameData] == kESSEddystoneURLFrameType,
152151
@"This should be a URL frame, but it's not. Whooops");
153-
NSData *URLFrameData = advFrameList[[self eddystoneServiceID]];
154-
152+
155153
if (!(URLFrameData.length > 0)) {
156154
return nil;
157155
}
@@ -172,23 +170,23 @@ + (NSURL *)URLForForFrame:(NSDictionary *)advFrameList {
172170
- (instancetype)initWithBeaconID:(ESSBeaconID *)beaconID
173171
txPower:(NSNumber *)txPower
174172
RSSI:(NSNumber *)RSSI
175-
URL:(NSURL *)URL
176173
telemetry:(NSData *)telemetry {
177174
if ((self = [super init]) != nil) {
178175
_beaconID = beaconID;
179176
_txPower = txPower;
180177
_RSSI = RSSI;
181-
_URL = URL;
182178
_telemetry = [telemetry copy];
183179
}
184180

185181
return self;
186182
}
187183

188184
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
189-
URL:(NSURL *)URL
190185
telemetry:(NSData *)telemetry
191186
RSSI:(NSNumber *)RSSI {
187+
NSAssert([ESSBeaconInfo frameTypeForFrame:UIDFrameData] == kESSEddystoneUIDFrameType,
188+
@"This should be a UID frame, but it's not. Whooops");
189+
192190
// Make sure this frame has the correct frame type identifier
193191
uint8_t frameType;
194192
[UIDFrameData getBytes:&frameType length:1];
@@ -197,26 +195,60 @@ + (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
197195
}
198196

199197
ESSEddystoneUIDFrameFields uidFrame;
200-
198+
201199
if ([UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields)
202200
|| [UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields) - sizeof(uidFrame.RFU)) {
203-
201+
204202
[UIDFrameData getBytes:&uidFrame length:(sizeof(ESSEddystoneUIDFrameFields)
205203
- sizeof(uidFrame.RFU))];
206-
204+
207205
NSData *beaconIDData = [NSData dataWithBytes:&uidFrame.beaconID
208206
length:sizeof(uidFrame.beaconID)];
209-
207+
210208
ESSBeaconID *beaconID = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
211209
beaconID:beaconIDData];
212210
if (beaconID == nil) {
213211
return nil;
214212
}
215-
213+
216214
return [[ESSBeaconInfo alloc] initWithBeaconID:beaconID
217215
txPower:@(uidFrame.txPower)
218216
RSSI:RSSI
219-
URL:URL
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
220252
telemetry:telemetry];
221253
} else {
222254
return nil;
@@ -225,14 +257,10 @@ + (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
225257

226258
- (NSString *)description {
227259
NSString *str = [NSString stringWithFormat:@"Eddystone, id: %@, RSSI: %@, txPower: %@",
228-
_beaconID, _RSSI, _txPower];
229-
if (_URL) {
230-
str = [str stringByAppendingFormat:@", URL: %@", _URL];
231-
}
260+
_beaconID, _RSSI, _txPower];
232261
return str;
233262
}
234263

235-
236264
+ (CBUUID *)eddystoneServiceID {
237265
static CBUUID *_singleton;
238266
static dispatch_once_t oncePredicate;
@@ -245,15 +273,13 @@ + (CBUUID *)eddystoneServiceID {
245273
}
246274

247275
+ (ESSBeaconInfo *)testBeaconFromBeaconIDString:(NSString *)beaconID {
248-
249276
NSData *beaconIDData = [ESSBeaconInfo hexStringToNSData:beaconID];
250-
251277
ESSBeaconID *beaconIDObj = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
252278
beaconID:beaconIDData];
279+
253280
return [[ESSBeaconInfo alloc] initWithBeaconID:beaconIDObj
254281
txPower:@(-20)
255282
RSSI:@(-100)
256-
URL:nil
257283
telemetry:nil];
258284
}
259285

tools/ios-eddystone-scanner-sample/EddystoneScannerSample/ViewController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ - (void)beaconScanner:(ESSBeaconScanner *)scanner didUpdateBeacon:(id)beaconInfo
5252
}
5353

5454
- (void)beaconScanner:(ESSBeaconScanner *)scanner didFindURL:(NSURL *)url {
55-
NSLog(@"I Saw an URL!: %@", url);
55+
NSLog(@"I Saw a URL!: %@", url);
5656
}
5757

5858
@end

tools/ios-eddystone-scanner-sample/EddystoneScannerSampleSwift/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2015 Google Inc. All rights reserved.
1+
// Copyright 2015-2016 Google Inc. All rights reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)