I can't get didReadRSSI to call back on my peripheral!!
I'm developing on an iPad mini - iOS 8.1.2
I set the peripheral to an NSMutable array and I can call connect, disconnect etc. fine from that array so the peripheral object attached to that array is valid.
My code is below, what is wrong? Why don't I get a didReadRSSI callback on my peripheral??
- (void)updateConnectedRSSITimerFunc {
for(TheCustomPeripheral *arrayItem in self.peripherals) {
if(arrayItem.connected) {
//This is called every 4 seconds from an NSTimer succesfully
[arrayItem.peripheral readRSSI];
NSLog(#"RSSI request");
}
}
}
-(void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
//This never get's called!!!!!
NSLog(#"RSSI returned %#", [RSSI stringValue]);
}
Solved!!! In the timer I added this... I'm still new to iOS so I missed the delegate bit to get callbacks...
arrayItem.peripheral.delegate = self;
As a supplementary question, why do I get this warning? I'm ignoring it because the app runs fine.
Assigning to 'id' from incompatible type 'xxxListTableViewController *const __strong'
Answer: My class had to be a CBPeripheralDelegate, e.g. #interface myInterface ,CBPeripheralDelegate>
Related
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've got a BOOL that seems to change value without being assigned too! However, it only happens when switching between view controllers. Basically I have a TableViewController going to a DetailViewController, the BOOL is on the DetailViewController. It is used to determine whether a timer is currently running. When just staying on the DetailViewController the BOOL works as expected, however when switching between DetailViewController and TableViewController, that's when the problem occurs. In no place on TableViewController is it being assigned to.
I have NSLogged all over the place, I've used lots of breakpoints and also watchpoints on the BOOL itself, however I still can't figure out what's making it change!
Okay, on the viewDidLoad of DetailViewController, the BOOL is updated from a Core Data entity.
- (void)viewDidLoad {
[super viewDidLoad];
// MORE CODE
_running = [[_timerObject valueForKey:#"running"] boolValue];
****_running is NO on initial load**** - expected
****_running is YES when returning to DetailViewController**** - expected
// MORE CODE
}
I then have a button to start/cancel the timer and use the BOOL to toggle the button.
- (IBAction)start:(id)sender {
****_running is NO on first press**** - expected
****_running is YES on second press**** - NOT EXPECTED!?!?!?!
// if timer is running
if (_running) {
// CANCEL TIMER CODE
}
else {
// START TIMER CODE
}
// Update BOOL
_running = !_running;
****_running is YES**** - expected
}
When the timer completes, this method is called.
- (void)timerComplete {
****_running is YES**** - expected
// OTHER CODE HERE
// Cancel timer
[_appDelegate.timerManager stopTimer:_timerID];
// Update BOOL
_running = NO;
****_running is NO**** - expected
// Save to ensure booleans are correct
[self save];
****_running is NO**** - expected
}
My save method looks like this. It is called at the end of timerComplete and also in the viewDidDisappear method.
- (void)save {
// Create variable of managed object context AppDelegate
NSManagedObjectContext *context = [self managedObjectContext];
if (_timerObject) {
// Update existing timer
if ([_nameTextField.text isEqualToString:#""]) {
[_timerObject setValue:#"Timer" forKey:#"name"];
}
else {
[_timerObject setValue:_nameTextField.text forKey:#"name"];
}
[_timerObject setValue:_date forKey:#"timeStart"];
[_timerObject setValue:[NSNumber numberWithBool:_running] forKey:#"running"];
[_timerObject setValue:[NSNumber numberWithBool:_paused] forKey:#"paused"];
//----------------
// Update title of view
self.title = [_timerObject valueForKey:#"name"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
****_running is NO**** - expected
}
The behaviour required to break it is:
- Load DetailViewContoller
- Press Start Timer
- Go back to TableViewController
- Go to DetailViewController
- Wait for Timer to Complete
- Press Start Timer again and nothing happens because _running is YES, not NO!
I've annotated the code segments with the values of "_running" in various different places.
As far as I can tell, nothing happens between timerComplete and pressing start again so I don't know why the BOOL changes from NO to YES in that time! Any help would be greatly appreciated! :)
Just an extra note
NSLog(#"timerObject: %#", [_timerObject valueForKey:#"running"]);
outputs a 0 when placed in the same placed as where _running returns YES in the start timer method! So the core data value appears to be correct, just not _running!
I'm trying to breath life back into a project from iOS 7.1 onto iOS 8.1. Obviously this is a NewsStand app. I need to be able to validate a certificate manually against a different host name. This all happens inside of willSendRequestForAuthenticationChallenge. On iOS 7.1 this all works fine. I even retested with an iPad still running 7.1 and it works, but on iOS 8.1 the method is never being invoked. As a result all Asset downloads fail with didFailWithError being invoked and an error of "The operation couldn’t be completed. (NSURLErrorDomain error -1202.)"
Does anyone know What might have changed in iOS 8.1 that would cause this? Are they not making the call? Did anything change with regards to inheritance on iOS 8.1 that might effect whether methods are detected and called? I did try moving all methods into the top class, but that didn't seem to help. Is it possible this is a result of using a "POST" request?
For the life of me I don't see any obvious code reason why willSendRequestForAuthenticationChallenge is not getting called on iOS 8.1 for NKAssetDownload, but is called for iOS 7.1. I never tested with iOS 8.0, so I can't say for sure it wasn't called there.
Note that the method is called for all my other URL connections I am using on iOS 8.1, so the issue seems to be specific to NKAssetDownload (or at least my invocations of NKAssetDownload).
The relevant code is:
#implementation myDownloader
{
NKIssue * theIssue; // pointer to the magazine Issue object
NSString * theJsonReceiptArrayBase64String;
NKAssetDownload * theAssetDownload;
NSString * sourceURL;
NSString * theFileName;
NSString * issueUniqueId;
NSURLConnection * theConnection;
NSNumber * expectedFileSize;
}
...
-(myDownloader *)initWithIssueAndIdAndSourceAndFinalPath:(NKIssue *)issue uniqueId:(NSString *)uniqueId source:(NSString *)source fileName:(NSString *)fileName fileSize:(NSNumber *) fileSize Receipt:(NSString *)receipt
{
NSLog(#"initWithIssueAndIdAndSourceAndFinalPath");
self = [super init];
if (self)
{
theIssue = issue;
sourceURL = source;
theFileName = fileName;
issueUniqueId = uniqueId;
expectedFileSize = fileSize;
theJsonReceiptArrayBase64String = receipt;
}
return self;
}
...
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
/* never get here on iOS 8.1 */
return NO;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
/* never get here on iOS 8.1 */
[self handleAuthenticationOnConnection:connection withChallenge:challenge];
}
...
-(void) downloadPackage
{
CCommunictionMgr * communicator = [CCommunictionMgr instance];
NSMutableURLRequest * thePackageRequest = [communicator generateJsonPostPackageRequest:sourceURL uniqueId:issueUniqueId recieptData:theJsonReceiptArrayBase64String];
theAssetDownload = [theIssue addAssetWithRequest:thePackageRequest];
[theAssetDownload setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:kASSET_TYPE_PACKAGE, kASSET_TYPE_DK, nil]];
theConnection = [theAssetDownload downloadWithDelegate:self];
}
the object "myDownloader" implements both NSURLConnectionDelegate, NSURLConnectionDownloadDelegate
headerfile
#interface myDownloader : CConnectionDelegate <NSURLConnectionDelegate, NSURLConnectionDownloadDelegate>
...
where the guts of the authentication challenge are handled inside the CConnectionDelegate object.
I am happy to include the guts of other code if needed.
I'm attempting to get Background App Refresh going in my iOS application. However, I'm having some trouble understanding code blocks.
I've done some research on it, and would say I have a beginner's understanding so far. The method in question is:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
This method wants a UIBackgroundFetchResult return type. Due to the complexity of my application though, I cannot return that with ease. There's a lot that happens when pulling data from the internet in Background mode.
In the body of that method, I have a custom method that also has a completion block. What I'm trying to do is define another custom method in my code that would be assigned to the completion handler.
In my data manager, I have a property defined as :
#property (copy, nonatomic) void (^fetchCompleted)(UIBackgroundFetchResult);
In the performFetchWtihCompletionHandler method implementation, I call on my data manager:
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
_fetchCompleted = completionHandler;
_state = DMSMT_WaitingForPartnerData;
[self getActiveQueues];
}
Once my downloads are completed, I call on the fetchCompleted method:
[self fetchCompleted];
Herein lies my problem. I need to pass a UIBackgroundFetchResult argument, but I see no way to do that. I tried [self fetchCompleted:UIBackgroundFetchResultNewData]; but it yells at me.
Any ideas?
Thanks in advance.
EDIT:
Here was the fix. So simple!
if(_fetchCompleted != nil){
[self fetchCompleted](UIBackgroundFetchResultNewData);
}
You are treating fetchCompleted as a method but it is a block! Try this out:
-(void)getActiveQueues {
// Do some work here
// When you are finished...
// Set the appropriate result
UIBackgroundFetchResult result;
// Check to make sure fetchCompleted is not NULL
if (self.fetchCompleted) {
// Invoke the block!
self.fetchCompleted(result);
}
}
This method wants a UIBackgroundFetchResult return type
No, it wants a void return type. One of the parameters is of type UIBackgroundFetchResult. Parameters are not return results. UIBackgroundFetchResult is just a type of variable.
Which appears to flow into your error. [self fetchCompleted] is the getter that will return the fetchCompleted variable. It doesn't do anything with it.
To perform a block, use function-like syntax. E.g. [self fetchCompleted]().
I am getting the battery level of my bluetooth le device (batteryLevel) which is a float in one view. I want to pass it to another view to display it in a textfield.
code in view one
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:
(CBCharacteristic *)characteristic error:(NSError *)error
{[self.testPeripheral readValueForCharacteristic:mycharacteristic];
char batlevel;
[characteristic.value getBytes:&batlevel length:1];
self.batteryLevel = (float)batlevel;
NSLog(#"level;%f",batteryLevel);}
this gives me a value like 80.00000
I want to put this into another view to display.
I have tried in view2.h file I have placed
view1 *t
and then
- (void) batteryIndicatorTimer:(NSTimer *)timer {
TIBLEUIBatteryBar.progress = t.batteryLevel / 100;
[t readBattery:[t testPeripheral]]; // Read battery value of keyfob again
NSLog(#"t.batterylevel:%f",t.batteryLevel);
}
but I don't get a value for t.batteryLevel
what am I doing wrong and how can I do this?
There's a myriad number of ways to accomplish this using delegates or assigning properties in presentingViewControllers/destinationViewControllers, but since I don't know your app flow I'll give you a way that should work with whatever setup you have. Just update batteryLevel in your viewController that is the peripheralDelegate then simply save the value to the NSUserDefaults:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:
(CBCharacteristic *)characteristic error:(NSError *)error
{
//Grab your battery value and store in float
//Then just save to defaults so you can access wherever.. Keep overwriting this value each time you update
[[NSUserDefaults standardUserDefaults]setFloat:batteryVal forKey:#"battery_level"];
}
And then inside your other viewController just assign the value inside viewWillAppear or something.
-(void)viewWillAppear:(BOOL)animated
{
float batteryLevel = [[NSUserDefaults standardUserDefaults]floatForKey:#"battery_level"];
self.yourTextField.text = [NSString stringWithFormat:#"%f",batteryLevel];
}