Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ @interface ESSBeaconScanner () <CBCentralManagerDelegate> {
*/
NSMutableDictionary *_tlmCache;

/**
* This cache maps Core Bluetooth deviceIDs to NSData objects containing Eddystone url.
* Then, the next time we see a URL frame for that Eddystone, we can add the most recently seen
* url frame to the sighting.
*/
NSMutableDictionary *_urlCache;

/**
* Beacons we've seen already. If we see an Eddystone and notice that we've seen it before, we
* won't fire a beaconScanner:didFindBeacon:, but instead will fire a
Expand All @@ -71,7 +64,6 @@ - (instancetype)init {
if ((self = [super init]) != nil) {
_onLostTimeout = 15.0;
_tlmCache = [NSMutableDictionary dictionary];
_urlCache = [NSMutableDictionary dictionary];
_seenEddystoneCache = [NSMutableDictionary dictionary];
_beaconOperationsQueue = dispatch_queue_create(kBeaconsOperationQueueName, NULL);
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
Expand Down Expand Up @@ -115,46 +107,46 @@ - (void)centralManagerDidUpdateState:(CBCentralManager *)central {

// This will be called from the |beaconsOperationQueue|.
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {

didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
NSData *beaconServiceData = serviceData[[ESSBeaconInfo eddystoneServiceID]];

ESSFrameType frameType = [ESSBeaconInfo frameTypeForFrame:beaconServiceData];

// If it's a telemetry (TLM) frame, then save it into our cache so that the next time we get a
// UID frame (i.e. an Eddystone "sighting"), we can include the telemetry with it.
ESSFrameType frameType = [ESSBeaconInfo frameTypeForFrame:serviceData];
if (frameType == kESSEddystoneTelemetryFrameType) {
_tlmCache[peripheral.identifier] = [ESSBeaconInfo telemetryDataForFrame:serviceData];
_tlmCache[peripheral.identifier] = beaconServiceData;
} else if (frameType == kESSEddystoneURLFrameType) {
// If it's a URL frame, then save it into our cache so that the next time we get a
// UID frame (i.e. an Eddystone "sighting"), we can include the URL data with it.
NSURL *url = [ESSBeaconInfo URLForForFrame:serviceData];
_urlCache[peripheral.identifier] = url;

// Reporting for URL frames as well, even if the underlying hardware isn't broadcasting UID
// frames.
NSURL *url = [ESSBeaconInfo parseURLFromFrameData:beaconServiceData];

// Report the sighted URL frame.
if ([_delegate respondsToSelector:@selector(beaconScanner:didFindURL:)]) {
[_delegate beaconScanner:self didFindURL:url];
}
} else if (frameType == kESSEddystoneUIDFrameType) {
} else if (frameType == kESSEddystoneUIDFrameType
|| frameType == kESSEddystoneEIDFrameType) {
CBUUID *eddystoneServiceUUID = [ESSBeaconInfo eddystoneServiceID];
NSData *eddystoneServiceData = serviceData[eddystoneServiceUUID];

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

// If we have URL data for this Eddystone, include it in the construction of the
// ESSBeaconInfo object. Otherwise, nil is fine.
NSURL *URL = _urlCache[peripheral.identifier];

ESSBeaconInfo *beaconInfo = [ESSBeaconInfo beaconInfoForUIDFrameData:eddystoneServiceData
URL:URL
telemetry:telemetry
RSSI:RSSI];
ESSBeaconInfo *beaconInfo;
if (frameType == kESSEddystoneUIDFrameType) {
beaconInfo = [ESSBeaconInfo beaconInfoForUIDFrameData:eddystoneServiceData
telemetry:telemetry
RSSI:RSSI];
} else {
beaconInfo = [ESSBeaconInfo beaconInfoForEIDFrameData:eddystoneServiceData
telemetry:telemetry
RSSI:RSSI];
}

if (beaconInfo != nil) {
if (beaconInfo) {
// NOTE: At this point you can choose whether to keep or get rid of the telemetry data. You
// can either opt to include it with every single beacon sighting for this beacon, or
// delete it until we get a new / "fresh" TLM frame. We'll treat it as "report it only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

typedef NS_ENUM(NSUInteger, ESSBeaconType) {
kESSBeaconTypeEddystone = 1,
kESSBeaconTypeEddystoneEID = 2,
};

typedef NS_ENUM(NSUInteger, ESSFrameType) {
kESSEddystoneUnknownFrameType = 0,
kESSEddystoneUIDFrameType = 1,
kESSEddystoneURLFrameType = 2,
kESSEddystoneUIDFrameType,
kESSEddystoneURLFrameType,
kESSEddystoneEIDFrameType,
kESSEddystoneTelemetryFrameType,
};

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

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

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


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

/**
* URL broadcasted by beacon.
*/
@property(nonatomic, strong, readonly) NSURL *URL;

/**
* The scanner has seen a frame for an Eddystone. We'll need to know what type of Eddystone frame
* it is, as there are a few types.
*/
+ (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList;
+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData;

/**
* Given some advertisement data that we have already verified is a TLM frame (using
* frameTypeForFrame:), return the actual telemetry data for that frame.
*/
+ (NSData *)telemetryDataForFrame:(NSDictionary *)advFrameList;

/**
* Given some advertisement data that we have already verified is a URL frame (using
* frameTypeForFrame:), return the URL for that frame.
* Given the service data for a frame we know to be a UID frame, an RSSI sighting,
* and -- optionally -- telemetry data (if we've seen it), create a new ESSBeaconInfo object to
* represent this Eddystone
*/
+ (NSURL *)URLForForFrame:(NSDictionary *)advFrameList;
+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
telemetry:(NSData *)telemetry
RSSI:(NSNumber *)initialRSSI;

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

/**
* If we're given a URL frame, extract the URL from it.
*/
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData;

/**
* Convenience method to save everybody from creating these things all the time.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
static const uint8_t kEddystoneUIDFrameTypeID = 0x00;
static const uint8_t kEddystoneURLFrameTypeID = 0x10;
static const uint8_t kEddystoneTLMFrameTypeID = 0x20;
static const uint8_t kEddystoneEIDFrameTypeID = 0x30;

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

typedef struct __attribute__((packed)) {
uint8_t frameType;
int8_t txPower;
uint8_t beaconID[8];
} ESSEddystoneEIDFrameFields;

// Test equality, ensuring that nil is equal to itself.
static inline BOOL IsEqualOrBothNil(id a, id b) {
return ((a == b) || (a && b && [a isEqual:b]));
Expand Down Expand Up @@ -75,6 +82,8 @@ - (instancetype)initWithType:(ESSBeaconType)beaconType
- (NSString *)description {
if (self.beaconType == kESSBeaconTypeEddystone) {
return [NSString stringWithFormat:@"ESSBeaconID: beaconID=%@", self.beaconID];
} else if (self.beaconType == kESSBeaconTypeEddystoneEID) {
return [NSString stringWithFormat:@"ESSBeaconID (EID): beaconID=%@", self.beaconID];
} else {
return [NSString stringWithFormat:@"ESSBeaconID with invalid type %lu",
(unsigned long)self.beaconType];
Expand Down Expand Up @@ -115,9 +124,7 @@ @implementation ESSBeaconInfo
* Given the advertising frames from CoreBluetooth for a device with the Eddystone Service ID,
* figure out what type of frame it is.
*/
+ (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList {
NSData *frameData = advFrameList[[self eddystoneServiceID]];

+ (ESSFrameType)frameTypeForFrame:(NSData *)frameData {
// It's an Eddystone ADV frame. Now check if it's a UID (ID) or TLM (telemetry) frame.
if (frameData) {
uint8_t frameType;
Expand All @@ -130,28 +137,19 @@ + (ESSFrameType)frameTypeForFrame:(NSDictionary *)advFrameList {
return kESSEddystoneURLFrameType;
case kEddystoneTLMFrameTypeID:
return kESSEddystoneTelemetryFrameType;
case kEddystoneEIDFrameTypeID:
return kESSEddystoneEIDFrameType;
}
}
}

return kESSEddystoneUnknownFrameType;
}

+ (NSData *)telemetryDataForFrame:(NSDictionary *)advFrameList {
// NOTE: We assume that you've already called [ESSBeaconInfo frameTypeForFrame] to confirm that
// this actually IS a telemetry frame.
NSAssert([ESSBeaconInfo frameTypeForFrame:advFrameList] == kESSEddystoneTelemetryFrameType,
@"This should be a TLM frame, but it's not. Whooops");
return advFrameList[[self eddystoneServiceID]];
}

+ (NSURL *)URLForForFrame:(NSDictionary *)advFrameList {
// NOTE: We assume that you've already called [ESSBeaconInfo frameTypeForFrame] to confirm that
// this actually IS a URL frame.
NSAssert([ESSBeaconInfo frameTypeForFrame:advFrameList] == kESSEddystoneURLFrameType,
+ (NSURL *)parseURLFromFrameData:(NSData *)URLFrameData {
NSAssert([ESSBeaconInfo frameTypeForFrame:URLFrameData] == kESSEddystoneURLFrameType,
@"This should be a URL frame, but it's not. Whooops");
NSData *URLFrameData = advFrameList[[self eddystoneServiceID]];


if (!(URLFrameData.length > 0)) {
return nil;
}
Expand All @@ -172,23 +170,23 @@ + (NSURL *)URLForForFrame:(NSDictionary *)advFrameList {
- (instancetype)initWithBeaconID:(ESSBeaconID *)beaconID
txPower:(NSNumber *)txPower
RSSI:(NSNumber *)RSSI
URL:(NSURL *)URL
telemetry:(NSData *)telemetry {
if ((self = [super init]) != nil) {
_beaconID = beaconID;
_txPower = txPower;
_RSSI = RSSI;
_URL = URL;
_telemetry = [telemetry copy];
}

return self;
}

+ (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
URL:(NSURL *)URL
telemetry:(NSData *)telemetry
RSSI:(NSNumber *)RSSI {
NSAssert([ESSBeaconInfo frameTypeForFrame:UIDFrameData] == kESSEddystoneUIDFrameType,
@"This should be a UID frame, but it's not. Whooops");

// Make sure this frame has the correct frame type identifier
uint8_t frameType;
[UIDFrameData getBytes:&frameType length:1];
Expand All @@ -197,26 +195,60 @@ + (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData
}

ESSEddystoneUIDFrameFields uidFrame;

if ([UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields)
|| [UIDFrameData length] == sizeof(ESSEddystoneUIDFrameFields) - sizeof(uidFrame.RFU)) {

[UIDFrameData getBytes:&uidFrame length:(sizeof(ESSEddystoneUIDFrameFields)
- sizeof(uidFrame.RFU))];

NSData *beaconIDData = [NSData dataWithBytes:&uidFrame.beaconID
length:sizeof(uidFrame.beaconID)];

ESSBeaconID *beaconID = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
beaconID:beaconIDData];
if (beaconID == nil) {
return nil;
}

return [[ESSBeaconInfo alloc] initWithBeaconID:beaconID
txPower:@(uidFrame.txPower)
RSSI:RSSI
URL:URL
telemetry:telemetry];
} else {
return nil;
}
}

+ (instancetype)beaconInfoForEIDFrameData:(NSData *)EIDFrameData
telemetry:(NSData *)telemetry
RSSI:(NSNumber *)RSSI {
NSAssert([ESSBeaconInfo frameTypeForFrame:EIDFrameData] == kESSEddystoneEIDFrameType,
@"This should be an EID frame, but it's not. Whooops");

// Make sure this frame has the correct frame type identifier
uint8_t frameType;
[EIDFrameData getBytes:&frameType length:1];
if (frameType != kEddystoneEIDFrameTypeID) {
return nil;
}

ESSEddystoneEIDFrameFields eidFrame;

if ([EIDFrameData length] == sizeof(ESSEddystoneEIDFrameFields)) {
[EIDFrameData getBytes:&eidFrame length:sizeof(ESSEddystoneEIDFrameFields)];
NSData *beaconIDData = [NSData dataWithBytes:&eidFrame.beaconID
length:sizeof(eidFrame.beaconID)];

ESSBeaconID *beaconID = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystoneEID
beaconID:beaconIDData];
if (beaconID == nil) {
return nil;
}

return [[ESSBeaconInfo alloc] initWithBeaconID:beaconID
txPower:@(eidFrame.txPower)
RSSI:RSSI
telemetry:telemetry];
} else {
return nil;
Expand All @@ -225,14 +257,10 @@ + (instancetype)beaconInfoForUIDFrameData:(NSData *)UIDFrameData

- (NSString *)description {
NSString *str = [NSString stringWithFormat:@"Eddystone, id: %@, RSSI: %@, txPower: %@",
_beaconID, _RSSI, _txPower];
if (_URL) {
str = [str stringByAppendingFormat:@", URL: %@", _URL];
}
_beaconID, _RSSI, _txPower];
return str;
}


+ (CBUUID *)eddystoneServiceID {
static CBUUID *_singleton;
static dispatch_once_t oncePredicate;
Expand All @@ -245,15 +273,13 @@ + (CBUUID *)eddystoneServiceID {
}

+ (ESSBeaconInfo *)testBeaconFromBeaconIDString:(NSString *)beaconID {

NSData *beaconIDData = [ESSBeaconInfo hexStringToNSData:beaconID];

ESSBeaconID *beaconIDObj = [[ESSBeaconID alloc] initWithType:kESSBeaconTypeEddystone
beaconID:beaconIDData];

return [[ESSBeaconInfo alloc] initWithBeaconID:beaconIDObj
txPower:@(-20)
RSSI:@(-100)
URL:nil
telemetry:nil];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ - (void)beaconScanner:(ESSBeaconScanner *)scanner didUpdateBeacon:(id)beaconInfo
}

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

@end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Google Inc. All rights reserved.
// Copyright 2015-2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
Loading