I have multiple BLE devices connected each 'notifying' when their battery state changes.
How can Identify which BLE is notifying when data is read after being notified of a change in state?
// Instance method to get the battery state
- (void) getBattery:(CBCharacteristic *)characteristic
{
//----------------
NSLog(#"getBattery");
//----------------
{
// Get the Battery Data
NSLog(#"Battery Level is : %# (HEX)" , characteristic.value);
}
}
This is the code reading the data when notified to...
Thanks for your answer, but it appears I'm only receiving data from the last service setup to notify from. Here is the calling code for Notify...
Hi, That's great. I can identify the device notifying when data has changed.
But, it appears that my code is only notifying and collecting data from the last characteristic identified.
Here is the calling code:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
// Retrieve the characteristic value for battery
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:SR1_DEVICE_BATTERY_LEVEL_SERVICE_UUID]]) {
[self getBattery:characteristic];
}
}
When a peripheral sends a notification that a value has changed, the CBPeripheralDelegate method didUpdateValueForCharacteristic is invoked. This method includes a reference to the CBPeripheral. You don't show how you get from this method to your getBattery method, but you need pass the peripheral when you do.
You can also reference the peripheral property of the service property of the CBCharacteristic -
CBPeripheral *p=characteristic.service.peripheral;
Related
I have a BLEPeripheral that holds about thirty CBCHaracteristics. I'm building an app that interfaces with a custom BLE device. When the view loads I need to write, read, and subscribe to Characteristics on that device. The problem I'm having is I'm not sure when all my Characteristics have been assigned to the data model and if I try to load my view before thats finished my app will crash. I've confirmed this by adding a count integer and incrementing the count after it discovers each characteristic, then when my count int == 30, I load the UI. This feel extremely clunky and I feel like theres a better way to do this.
I'm trigger the loading of the data on the UI view with an observer notification. To give a little more information, I'm using a splitview, user selects the ble object that want to connect to. Once it's connected it hides the splitview and shows the detailview.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
if(_itemCount == 30) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"STOPINDICATOR" object:self];
}
// Serial Number
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:SERIAL_NUMBER]]) {
_serialNumber = characteristic;
_itemCount++;
}
// MFG Name
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:MFG_NAME]]) {
_mfgName = characteristic;
_itemCount++;
}
// Device Type
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:DEVICE_TYPE]]) {
_deviceType = characteristic;
_itemCount++;
}
// Model Number
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:MODEL_NUMBER]]) {
_modelNumber = characteristic;
_itemCount++;
}
}
}
I am trying to write a BTLE monitoring app that receives notifications. I have built both the central app and a mock peripheral app using Core Bluetooth (on iOS 7.1). My central can connect fine to the peripheral, which advertises a service that has 2 CBCharacteristicPropertyNotify characteristics. But here is what happens once the central discovers the characteristics (removing non-essential code for clarity).
PD = CBPeripheralDelegate in central
PMD = CBPeripheralManagerDelegate in peripheral
PD callback invoked
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for (CBCharacteristic *characteristic in service.characteristics)
{
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
PMD callback invoked twice for the 2 characteristics
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:FILTER_GENUINE_CHARACTERISTIC_UUID]])
{
BOOL genuineSent = [self.peripheralManager updateValue:genuineValue
forCharacteristic:self.filterGenuineCharacteristic
onSubscribedCentrals:nil];
}
else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:FILTER_LOADING_CHARACTERISTIC_UUID]])
{
BOOL loadingSent = [self.peripheralManager updateValue:loadingValue
forCharacteristic:self.filterLoadingCharacteristic
onSubscribedCentrals:nil];
}
}
Note: The first time this method is invoked the return from updateValue:forCharacteristic:onSubscribedCentrals: is YES. The second time the return is always NO.
PD callback invoked twice for the 2 characteristics
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
Note: characteristic.isNotifying is always YES.
But that¹s it, no other callback methods are invoked!
PMD callback peripheralManagerIsReadyToUpdateSubscribers: is never called for the updateValue:forCharacteristic:onSubscribedCentrals: call that returned NO
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
PD callback peripheral:didUpdateValueForCharacteristic:error: is never called for the updateValue:forCharacteristic:onSubscribedCentrals: call that returned YES
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
I have been testing these apps on an iPad Air and iPhone 5s, both running iOS 7.1.
Any idea why the notification is never invoked?
Im using MCNearbyServiceBrowser and MCNearbyServiceAdvertiser to join two peers to a MCSession. I am able to send data between them using MCSession's sendData method. All seems to be working as expected until I randomly (and not due to any event I control) receive a MCSessionStateNotConnected via the session's MCSessionDelegate didChangeState handler. Additionally, the MCSession's connectedPeers array no longer has my peers.
Two questions: Why? and How do i keep the MCSession from disconnecting?
This is a bug, which I just reported to Apple. The docs claim the didReceiveCertificate callback is optional, but it's not. Add this method to your MCSessionDelegate:
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
The random disconnects should cease.
UPDATE After using a support ticket to Apple, they confirmed that calling sendData too often and with too much data can cause disconnects.
I have had disconnects when hitting break points and when backgrounding. Since the break points won't happen on the app store, you need to handle the backgrounding case by beginning a background task when your app is about to enter the background. Then end this task when your app comes back to the foreground. On iOS 7 this gives you about 3 background minutes which is better than nothing.
An additional strategy would be to schedule a local notification for maybe 15 seconds before your background time expires by using [[UIApplication sharedApplication] backgroundTimeRemaining], that way you can bring the user back into the app before it suspends and the multi peer framework has to be shutdown. Perhaps the local notification would warn them that their session will expire in 10 seconds or something...
If the background task expires and the app is still in the background, you have to tear down everything related to multi-peer connectivity, otherwise you will get crashes.
- (void) createExpireNotification
{
[self killExpireNotification];
if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
self.expireNotification = n;
self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
self.expireNotification.alertBody = TR(#"Text_MultiPeerIsAboutToExpire");
self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
self.expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
}
}
- (void) killExpireNotification
{
if (self.expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
self.expireNotification = nil;
}
}
- (void) applicationWillEnterBackground
{
self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self shutdownMultiPeerStuff];
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void) applicationWillEnterForeground
{
[self killExpireNotification];
if (self.taskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}
}
- (void) applicationWillTerminate
{
[self killExpireNotification];
[self stop]; // shutdown multi-peer
}
You'll also want this handler in your MCSession delegate due to Apple bug:
- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
if (certificateHandler != nil) { certificateHandler(YES); }
}
There are many causes of this, and the two answers thus far are both correct in my experience. Another which you'll find in other similar questions is this: Only one peer can accept another's invitation.
So, to clarify, if you set up an app where all devices are both advertisers and browsers, any devices can freely invite any others found to join a session. However, between any two given devices, only one device can actually accept the invitation and connect to the other device. If both devices accept each others' invitations they will disconnect within a minute or less.
Note that this limitation does not prevent the desired behavior because - unlike what my intuition stated before I built my multipeer implementation - when one device accepts an invitation and connects to another device they both become connected and receive connection delegate methods and can send each other messages.
Therefore, if you are connecting devices which both browse and advertise, send invitations freely but only accept one of a pair.
The problem of only accepting one of two invitations can be solved a myriad of ways. To begin, understand that you can pass any arbitrary object or dictionary (archived as data) as the context argument in an invitation. Therefore, both devices have access to any arbitrary information about the other (and of course itself). So, you could use at least these strategies:
simply compare: the display name of the peerID. But there's no guarantee these won't be equal.
store the date your multipeer controller was initialized and use that for comparison
give each peer a UUID and send this for comparison (my technique, in which each device - indeed each user of the app on a device - has a persistent UUID it employs).
etc - any object which supports both NSCoding and compare: will do fine.
I've been having similar problems. It seems though that if I have run my app on one iOS device, and connected to another, then quit and relaunch (say when I rerun from Xcode), then I am in a situation where I get a Connected message and then a Not Connected message a little later. This was throwing me off. But looking more carefully, I can see that the Not Connected message is actually meant for a different peerId than the one that has connected.
I think the problem here is that most samples I've seen just care about the displayName of the peerID, and neglect the fact that you can get multiple peerIDs for the same device/displayName.
I am now checking the displayName first and then verifying that the peerID is the same, by doing a compare of the pointers.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(#"remnant connection drop");
return; // note that I don't care if player is nil, since I don't want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...
I was disconnecting immediately after I accepted the connection request. Observing the state, I saw it change from MCSessionStateConnected to MCSessionStateNotConnected.
I am creating my sessions with:
[[MCSession alloc] initWithPeer:peerID]
NOT the instantiation method dealing with security certificates:
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference
Based on Andrew's tip above, I added the delegate method
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
and the disconnects stopped.
I have an Cocoa Application (Mac OS X SDK 10.7) that is performing some processes via Grand Central Dispatch (GCD). These processes are manipulating some Core Data NSManagedObjects (non-document-based) in a manner that I believe is thread safe (creating a new managedObjectContext for use in this thread).
The problem I have is when the user tries to quit the application while the dispatch queue is still running.
The NSApplication delegate is being called before actually quitting.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
I get an error "Could not merge changes." Which is somewhat expected since there are still operations being performed through the different managedObjectContext. I am then presented with the NSAlert from the template that is generated with a core data application.
In the Threading Programming Guide there is a section called "Be Aware of Thread Behaviors at Quit Time" which alludes to using replyToApplicationShouldTerminate: method. I'm having a little trouble implementing this.
What I would like is for my application to complete processing the queued items and then terminate without presenting an error message to the user. It would also be helpful to update the view or use a sheet to let the user know that the app is performing some action and will terminate when the action is complete.
Where and how would I implement this behavior?
Solution:
So I had a few different issues here.
I had blocks that were accessing core data in a dispatch_queue preventing my application from terminating gracefully.
When I tried to add a new item to the dispatch_queue a new instance of the dispatch_queue was started on a new thread.
What I did to solve this was use NSNotificationCenter in my AppDelegate (where (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender was being called. In the template code that Core Data generates add the following:
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:#"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
Then in AppController add an observer for the notification (I added this to awakeFromNib):
- (void)awakeFromNib {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(terminateApplicationFromQueue:) name:#"TerminateApplicationFromQueue" object:nil];
// Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
appTerminating.isAppTerminating = NO;
appTerminating.isTerminatingNow = NO;
}
I have also created a struct that can be checked against to see if the user wants to terminate the application. (I set the initial state of the struct in awakeFromNib above). Place the struct after your #synthesize statements:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
Now for the long-running dispatch_queue that is preventing the app from gracefully terminating. When I initially create this dispatch_queue, a for loop is used to add the items that need updating. After this for loop is executed, I have tacked on another queue item that will check the struct to see if the app should terminate:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(#"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(#"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
And the method to be called when the notification is received:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(#"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(#"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
I haven't dealt with this myself, but just from my reading of the docs, it looks like what you should do is:
Return NSTerminateLater from applicationShouldTerminate:. This lets the system know that your app isn't ready to terminate just yet, but will do so shortly.
Enqueue a "final" block on your dispatch queue. (You need to make sure that other blocks are not enqueued after this. This block will then be run after all the other work has been performed. Note the queue must be serial -- not one of the concurrent queues) for this to work correctly.) The "final" block should do [NSApp replyToApplicationShouldTerminate:YES];, which will complete the normal termination process.
There isn't any direct way to find out whether a GCD queue is still working. The only other thing that you can do (that I know of) to handle this is to put all of the blocks into a dispatch group, and then wait on the group in applicationShouldTerminate: (using dispatch_group_wait().
This is with reference to the StackOverflow question Managing multiple asynchronous NSURLConnection connections
I have multiple asynchronous HTTP requests being made at the same time. All these use the same NSURLConnection delegate functions. (The receivedData object is different for each connection as specified in the other question above. In the delegate, I parse the receivedDate object, and do additional operations on those parsed strings)
Everything works fine for me so far, but I'm not sure if I need to do anything to ensure correct “multithreaded” behavior.
Is it possible that more than two connections will use the delegate at the same time? (I would think yes)
If yes, how is it resolved? (Does Cocoa do this automatically?)
Do I need to have additional checks in place to ensure that each request is handled “correctly”?
I enhanced the Three20 library to implement asynchronous connections across multiple threads in order to fetch data even if the user was playing with the UI. After many hours of chasing down random memory leaks that were detected within the CFNetwork framework I finally root caused the issue. I was occasionally losing track of responses and data.
Any data structures which are accessed by multiple threads must be protected by an appropriate lock. If you are not using locks to access shared data structures in a mutually exclusive manner then you are not thread safe. See the "Using Locks" section of Apple's Threading Programming Guide.
The best solution is to subclass NSURLConnection and add instance variables to store its associated response and response data. In each connection delegate method you then cast the NSURLConnection to your subclass and access those instance variables. This is guaranteed to be mutually exclusive because every connection will be bundled with its own response and data. I highly recommend trying this since it is the cleanest solution. Here's the code from my implementation:
#interface TTURLConnection : NSURLConnection {
NSHTTPURLResponse* _response;
NSMutableData* _responseData;
}
#property(nonatomic,retain) NSHTTPURLResponse* response;
#property(nonatomic,retain) NSMutableData* responseData;
#end
#implementation TTURLConnection
#synthesize response = _response, responseData = _responseData;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate {
NSAssert(self != nil, #"self is nil!");
// Initialize the ivars before initializing with the request
// because the connection is asynchronous and may start
// calling the delegates before we even return from this
// function.
self.response = nil;
self.responseData = nil;
self = [super initWithRequest:request delegate:delegate];
return self;
}
- (void)dealloc {
[self.response release];
[self.responseData release];
[super dealloc];
}
#end
/////////////////////////////////////////////////////////////////
////// NSURLConnectionDelegate
- (void)connection:(NSURLConnection*)connection
didReceiveResponse:(NSHTTPURLResponse*)response {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
ttConnection.response = response;
ttConnection.responseData = [NSMutableData
dataWithCapacity:contentLength];
}
- (void)connection:(NSURLConnection*)connection
didReceiveData:(NSData*)data {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
[ttConnection.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
if (ttConnection.response.statusCode == 200) {
// Connection success
}
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
// Handle the error
}
Assuming you're launching all of the (asynchronous) connections on a single thread, then the delegate messages will all get posted in that thread's run loop. Therefore the delegate only needs to be able to deal with one message being handled at once; the run loop will hand one message off at a time. This means that while the order of the delegate messages is unknown and the next message could come from any connection object, there will be no concurrent execution of your delegate methods.
However, were you actually trying to use the same delegate object across multiple threads, rather than just using the asynchronous nature of the API, then you would need to deal with concurrent delegate methods.
Yes it's possible to have multiple connections. the notification object contains a pointer to the NSURLConnection that triggered the notification.
Internally I guess NSURLConnection listens to a socket and does something like this when it has data ready.
[your_delegate
performSelectorOnMainThread:#selector(connectionCallback:)
withObject:self
waitUntilDone:NO];
so you don't have to worry about it being multithreaded, NSURLConnection will take care of this. For simplicity I have written self, in the real world a NSNotification object is given.
You shouldn't have to do any checks related to multithreading.