My project need to receive BLE advertisement package,and handle the kCBAdvDataManufacturerData without connecting the BLE device.The BLE device send advertisement package 1 time per second.In an empty viewcontroller ,I receive advertisement package 1 time per second,but in my controller where I display the BLE advertisement package data ,the frequency I receive advertisement package reduce to 1 time/4 sec or lower.In this controller ,I send some http request and update UI using NSTimer 1 time per second.I handle kCBAdvDataManufacturerData in dispatch_sync(dispatch_get_global_queue(0, 0),and update UI in mainqueue .
Anybody have any idea to increase the frequency to receive the BLE advertisement package?
-1. First you need to startDiscovery.
-(void)startDiscovery
{
if (self.mBtmanager == nil) {
_mScanState = false;
self.mBtmanager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
self.mBtmanager.delegate = self;
}
if (self.mBtmanager.state != CBCentralManagerStatePoweredOn) {
_mScanState = true;
}
NSDictionary *scanOption = #{CBCentralManagerScanOptionAllowDuplicatesKey:#YES};
[self.mBtmanager scanForPeripheralsWithServices:[[NSArray alloc] initWithObjects:[CBUUID UUIDWithString:JAALEE_ADV_SERVICE_UUID_STRING], nil] options:scanOption];
}
- 2. You need to make connection to Beacon.
-(void)startConnectDevice:(CBPeripheral*)peripheral
{
peripheral.delegate = self;
[self.mBtmanager connectPeripheral:peripheral options:nil];
}
-3. Then change the broadcast interval using writeServicesUUID
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
-finally CONFIGURE_BROADCAST_INTERVAL_CHARACTERISTIC_ will help you.
Related
I am working on a project to display the list of all Bluetooth LE devises with services with specific UUIDs. In method didDiscoverPeripheral, I save the discovered peripherals that are advertising. I use the option dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO] when scanning for peripherals. I have an NSTimer to wake up every 30 seconds to update the list of discovered peripherals and see if all peripherals are still advertising. I use retrievePeripheralsWithIdentifiers method that I pass in an array of NSUUID of discovered and saved peripherals. This method is supposed to return an array of CBPeripherals that are still advertising. But it returns the original array of all the peripherals that I pass in as an argument, and it never sorts out the peripherals that are no longer advertising. Am I using this method incorrectly?
savedPeripherals is an NSDictionary where device ID is the Key, and CBPeripheral is the Value.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:#selector(updateActivePeripherals:)
userInfo:nil
repeats:YES];
- (void) updateActivePeripherals:(NSTimer *)timer
{
NSMutableArray *peripherals = [[NSMutableArray alloc]init];
if (self.savedPeripherals.count > 0)
{
for(id key in self.savedPeripherals)
{
CBPeripheral *item = [self.savedPeripherals objectForKey:key];
NSString *identifier=[NSString stringWithFormat:#"%#", [[item identifier] UUIDString]];
NSUUID *id=[[NSUUID alloc]initWithUUIDString:identifier];
if (id)
[peripherals addObject:id];
}
}
if (peripherals.count > 0)
{
NSArray *list =[_centralManager retrievePeripheralsWithIdentifiers:peripherals];
}
}
}
The documentation doesn't state that retrievePeripheralsWithIdentifiers will return peripherals that are still visible/advertising. Rather, it says it returns:
A list of peripherals that the central manager is able to match to the
provided identifiers.
The central manager will return known peripherals even if the peripheral is not currently visible. This is to allow apps to connect automatically to a particular peripheral once it becomes visible. The workflow goes like this:
Application launches and retrieves desired peripheral identifier (such as from NSUserDefaults)
Application requests CBPeripheral via retrievePeripheralsWithIdentifiers
Application issues a connect to that peripheral
If the peripheral is currently visible, then the connection happens immediately and the delegate method is called
If the peripheral is not currently visible then the connection will happpen when/if the peripheral becomes visible and the delegate method will be called.
In order to achieve the functionality you require, you will need to use CBCentralManagerScanOptionAllowDuplicatesKey:YES and keep an age against each peripheral. For example, you could use the following approach:
Set up a dictionary, keyed by the peripheral identifier
When a peripheral is seen, store an NSNumber in the dictionary with value 30, also store the peripheral in your table view's source array if necessary
Set up an NSTimer to fire every second.
Each time the timer fires, go through the dictionary and decrement the value stored against each key
For each value that has decremented to 0, remove it from the tableview array and update the tableview
I am writing an application which communicates with multiple (between 5 & 10) identical BLE devices. Each BLE device has multiple characteristics some are static, some update and others can be written to.
The application has multiple ViewControllers embedded within a Navigation Controller and is for IOS devices (specifically IOS 8+ and iPhone 6).
In order to make the program efficient and to work with CoreBluetooth I have created to classes to manage the BLE interaction:
BLE Control Class - Which scans for and connects the correct BLE devices.
and
BLE Services Class - Once connected scans the characteristics and sets them appropriately according to their type.
Data sent by a peripheral and received by the manager for known connected characteristics is then stored in a back-end SQLite db.
The issue I am facing is writing back to a connected peripherals characteristic. I have collected the characteristic within a CBCharacteristic, but it does not persist within the class when I attempt to write to it the value of the CBCharacteristic is NULL.
Following is a summary of the code I have used:
CBCharacteristic Definition within the BLEServicesClass
#import "BLEServicesClass.h"
#import "BLEControlClass.h"
NSString *srModeUUIDString = #"346D0003-12A9-11CF-1279-81F2B7A91332";
#interface BLEServicesClass() <CBPeripheralDelegate> {
#private
CBPeripheral *servicePeripheral;
CBCharacteristic *srModeCharacteristic;
#end
didDiscoverCharacteristicForService
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;
{
NSArray *characteristics = [service characteristics];
CBCharacteristic *characteristic;
if ([[characteristic UUID] isEqual:srModeUUID]) {
NSLog(#"didDiscoverServices - Mode Characteristic");
srModeCharacteristic = characteristic;
}
To write to a characteristic
-(void)writeCharacteristic:(CBCharacteristic *)whichCharacteristic data:(NSData*)data device:(NSString *)device
{
NSArray *devices;
devices = [[BLEControlClass sharedInstance] connectedPeripherals];
int i;
for (i = 0; i < [[[BLEControlClass sharedInstance] connectedPeripherals] count]; i++) {
CBPeripheral *peripheral=[[[BLEControlClass sharedInstance] connectedPeripherals] objectAtIndex:i];
peripheral.delegate=self;
NSString *tesfordevice = peripheral.name;
if (tesfordevice == device) {
[whichCharacteristic.service.peripheral writeValue:data forCharacteristic:whichCharacteristic type:CBCharacteristicWriteWithResponse];
}
}
}
This is called by:
-(void)writeModeCharacteristic:(NSData*)data :(NSString *)device
{
[self writeCharacteristic:srModeCharacteristic data:data device:device];
}
My issue is that the srModeCharacteristic is initially set correctly when its is discovered but later is NULL.
Any help please?
I recommend always resolving the characteristics on demand, i.e. by iterating and taking the first with the matching UUID. If there are none, issue a new scan – same for the services.
That way your program will easily survive reconnects.
Make a BLE singleton class as i have done in my application. whenever the app is in run state it persist the value of characteristics throughout the app life cycle.
I have gone over the documentation, but there isn't much information on Multipeer Connectivity related to choosing a possible medium for peers to connect.
Multipeer Connectivity automatically discovers peers based on WiFi, or Bluetooth. Is there a way to limit this to only Bluetooth?
As #kdogisthebest correctly states, there's no way to force Multipeer Connectivity to use a particular network technology, but as your question relates to a particular problem with WiFi, this answer details what I'm doing to work around that.
I've worked around the issue of 'phantom' peers over WiFi by sending a shortened timestamp in the discoveryInfo when creating the MCNearybyServiceAdvertiser. There are several caveats here:
1) This solution assumes both devices have the same time. I ensure this by using a modified version of ios-ntp as the app's time source.
2) It also assumes that Advertising and Browsing do not run for too long. I have a set length of 60 seconds for discovery phases, and I completely re-init the browser/advertiser on each restart.
3) MPC doesn't seem to like too many bytes in the discoveryInfo so sending an NSTimeInterval based on epoch doesn't work. I had to truncate them.
So when my app enters discovery mode, it starts browsing and advertising simultaneously. The advertising code looks like:
- (void)startAdvertising {
if (_advertising){
NSLog(#"Already advertising");
return;
}
self.acceptedPeerIDNameMap = [NSMutableDictionary dictionary];
NSInteger timeStamp = [self shortenedNetworkTimeStamp];
NSDictionary *discoveryInfo = #{kAdvertisingDiscoveryInfoTimestampKey:[NSString stringWithFormat:#"%ld",(long)timeStamp]};
NSLog(#"Starting advertiser");
self.serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_myPeerID
discoveryInfo:discoveryInfo
serviceType:kServiceType];
_serviceAdvertiser.delegate = self;
[_serviceAdvertiser startAdvertisingPeer];
self.advertising = YES;
}
The method shortenedNetworkTimestamp just takes an NSTimeInterval (either using the ntp framework or timeIntervalSinceReferenceDate and removing 1400000000 from it.
Then when the browser discovers a peer, it checks whether the advertiser's timestamp is within the known discovery duration (in my case 60 seconds):
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info {
DLog(#"Browser found peer ID %#",peerID.displayName);
//Only one peer should invite the other
BOOL shouldInvite = [peerID.displayName compare:_myPeerID.displayName]==NSOrderedAscending;
//Don't re-send invitations
if (_peerInfoDisplayNameMap[peerID.displayName]){
DLog(#"Already connected to peerID %#",peerID.displayName);
shouldInvite = NO;
}
else if (_invitedPeerIDNameMap[peerID.displayName]){
DLog(#"Already invited peerID %#",peerID.displayName);
shouldInvite = NO;
}
//Invite if discovery info is valid
if (shouldInvite && [self discoveryInfoIsValid:info]) {
DLog(#"Inviting");
_invitedPeerIDNameMap[peerID.displayName] = peerID;
MCSession *session = [self availableSession];
[_serviceBrowser invitePeer:peerID toSession:session withContext:nil timeout:0];
}
else {
DLog(#"Not inviting");
}
}
The discovery info validity check is pretty simple - just make sure the timestamp sent in the info is inside of the discovery time range (in my case kDiscoveryPhaseDuration is 60 seconds):
- (BOOL)discoveryInfoIsValid:(NSDictionary *)info {
BOOL isValid = YES;
NSString *infoTimeStamp = info[kAdvertisingDiscoveryInfoTimestampKey];
NSTimeInterval sentTimeStamp = (infoTimeStamp) ? [infoTimeStamp doubleValue] : -1;
NSTimeInterval currentTimeStamp = [self shortenedNetworkTimeStamp];
if (sentTimeStamp==-1 || (currentTimeStamp - sentTimeStamp) > kDiscoveryPhaseDuration){
DLog(#"Expired discovery info (current=%f, sent=%f)",currentTimeStamp,sentTimeStamp);
isValid = NO;
}
return isValid;
}
Hopefully this helps. There are many other quirks in MPC that I'm handling in my own code but I think the above covers this specific problem.
This isn't possible with Multipeer Connectivity. There are no methods Apple puts in place to limit the connection to Bluetooth.
One answer here: Multipeer connectivity over Bluetooth? states "There is no explicit setting for bluetooth or Wifi, It will connect devices in whatever possible way they are available."
The method connectPeripheral is defined in a class with CBCentralManagerDelegate. I need to call connectPeripheral from the table view controller when didSelectRowAtIndexPath is selected. Once the peripheral is connected it should continue to execute remaining lines of code in the view controller. I am able to connect to the Peripheral. But before the connection is complete it executes the remaining section of the code. Since the peripheral is not yet connected it does not do the necessary job.
I used dispatch_sync to ensure that the connection is established and then the remaining code is executed but it doesn't work. How can I get around this problem? I am relatively new to IOS programming. Any inputs is highly appreciated.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CBPeripheral *peripheral = nil;
NSArray *devices = [[NSArray alloc] init];
switch (indexPath.section) {
case 0: {
devices = [[BTLECentralManager sharedInstance] foundPeripherals];
peripheral = (CBPeripheral *)[devices objectAtIndex:indexPath.row];
dispatch_sync(syncConnection, ^{
[[BTLECentralManager sharedInstance] connectPeripheral:peripheral];
activePeripheral = [self serviceForPeripheral:peripheral];
NSLog(#"Active Peripheral %#",activePeripheral);
[activePeripheral getTimeDate];
});
break;
}
default:
break;
}
}
The request to connect with the peripheral is an asynchronous process. You cannot just wrap that in a dispatch_sync to make it synchronous. (And you don't want to make it synchronous, anyway.)
That synchronous dispatch of the connectPeripheral is merely synchronously dispatching the request to connect to that peripheral, but doesn't change the inherently asynchronous nature of the connection process, itself. In fact, you generally don't want to dispatch these asynchronous requests to your own background queue. Apple provided a nice asynchronous interface, so you go ahead use it from the main thread.
While you, theoretically, could make a synchronous interface for this connection process, you should not do so. Instead, you should embrace the asynchronous nature of the interface. Either implement the delegates, or possibly wrap the CBCentralManager in some object that provides a block-based interface.
I'm trying to create an app that transfers data between 2+ phones using GKSession. Thing is there are two options:
First: using the GKPeerPicker.. However here I get stuck at the point where I have to implement my own WIFI interface.. apple provides no instructions on how to do that:
- (void)peerPickerController:(GKPeerPickerController *)picker didSelectConnectionType: (GKPeerPickerConnectionType)type {
if (type == GKPeerPickerConnectionTypeOnline) {
picker.delegate = nil;
[picker dismiss];
[picker autorelease];
// Implement your own internet user interface here.
}
}
Second: Skipping GKPeerPicker and doing the whole thing my self, like in this example. However the app dev documentation doesn't provide any instructions on how to send/receive data without using GKPeerPicker.. (nor could I find any example of that on thew web)
I just figured out how to connect devices without the peerpicker. It was a bit of a guessing game because the documentation is pretty unclear and I've looked for so long on the internet for any info about this. I'll try to explain everything here to clear up any questions anyone in the future might have.
From the documentation:
A GKSession object provides the ability to discover and connect to
nearby iOS devices using Bluetooth or Wi-fi.
This was the first step to understand it for me. I thought the GKPeerPickerController was responsible of the advertising and connecting but GKSession actually does all that.
The second thing to understand is that what is referred to as peers are not necessarily connected to you. They can just be nearby waiting to be discovered and connected to. All peers have a state
GKPeerStateAvailable (this is what's useful!)
GKPeerStateUnavailable
GKPeerStateConnected
GKPeerStateDisconnected
GKPeerStateConnecting
So how do we actually connect? Well first we have to create a GKSession object to be able to find peers around us and see when they become available:
// nil will become the device name
GKSession *gkSession = [[GKSession alloc] initWithSessionID:#"something.unique.i.use.my.bundle.name" displayName:nil sessionMode:GKSessionModePeer];
[gkSession setDataReceiveHandler:self withContext:nil];
gkSession.delegate = self;
gkSession.available = YES; // I'm not sure this if this is the default value, this might not be needed
Now we have some delegate calls to respond to. session:didReceiveConnectionRequestFromPeer: and session:peer:didChangeState (you should also handle the calls of GKSessionDelegate for disconnection and failure appropriately)
-(void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
if(state == GKPeerStateDisconnected)
{
// A peer disconnected
}
else if(state == GKPeerStateConnected)
{
// You can now send messages to the connected peer(s)
int number = 1337;
[session sendDataToAllPeers:[NSData dataWithBytes:&number length:4] withDataMode:GKSendDataReliable error:nil];
}
else if (state == GKPeerStateAvailable)
{
// A device became available, meaning we can connect to it. Lets do it! (or at least try and make a request)
/*
Notice: This will connect to every iphone that's nearby you directly.
You would maybe want to make an interface similar to peerpicker instead
In that case, you should just save this peer in a availablePeers array and
call this method later on. For your UI, the name of the peer can be
retrived with [session displayNameForPeer:peerId]
*/
[session connectToPeer:peerID withTimeout:10];
}
}
The other peer now received a request that he should respond to.
-(void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
// We can now decide to deny or accept
bool shouldAccept = YES;
if(shouldAccept)
{
[session acceptConnectionFromPeer:peerID error:nil];
}
else
{
[session denyConnectionFromPeer:peerID];
}
}
Finally to receive our little 1337 message
-(void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession*)session context:(void *)context
{
int number = 1337;
if([data isEqualToData:[NSData dataWithBytes:&number length:4]])
{
NSLog(#"Yey!");
}
}