Reputation: 1531
Ok I am extremely new to micro-controller code, but just as new to Objective-c bluetooth code.
What I Want
I would like to connect to my Bluetooth Bluefruit Friend UART LE (NRF51822 chip) with an iOS app and send it a 128 bit token and have it respond with an HID Consumer code.
What I Have Tried
Currently, I can connect to the chip with a serial monitor and send through the consumer codes I want and it will respond by doing that action. So if I send the code to mute the device or volume up/down, it will then mute/volume up/down in my iOS device. This is great!
Now I need to do this without the serial monitor on my laptop and to introduce the concept of sending a token instead of the HID code and have it respond with the HID code. This is extremely tricky and seems to introduce new concepts to me like services and channels. It seems that most Bluetooth devices come with a number of available custom services that could can added by the developer and allow the separation of read versus write actions. Each service allows for a number of characteristics such as read/write/notify/update and more.
So... I have assumed the solution to what I want can be used through these services. If I had one service for reading the token and another service for writing a GATT HID Consumer Code
then I should be able to connect via Bluetooth in objective-c to each service individually. This would allow me the distinction between sending the token and receiving the code back.
The Problem
As you can see I have many NSLogs peppered throughout the code below, currently none of those logs show any of the services I have put on the box. Services always prints as nil. If I use the iOS app from the app store called RF Connect
, I can connect to the box and I can see that those services are most definitely there...
The Code I Have Tried
Here is the current code I have been using to just try and detect the devices services... which I thought is a good place to start here.
BluetoothMgr.h
#import <Foundation/Foundation.h>
@protocol BluetoothMgrDelegate <NSObject>
@optional
- (void)didConnectToDevice;
- (void)didDisconnectFromDevice;
@end
@interface BluetoothMgr : NSObject {
}
@property (nonatomic, weak) id <BluetoothMgrDelegate> delegate;
+ (BluetoothMgr *)sharedInstance;
- (void)scanForPeripherals;
- (void)disconnectDevice;
- (void)connectToDevice;
@end
BluetoothMgr.m
#define SERVICE_UUID @"0000BBB5-2222-3333-4444-AABBCCDDEEFF"
#define CARACHTERISTIC_UUID @"0000BBB6-1111-2222-3333-AABBCCDDEEFF"
#import "BluetoothMgr.h"
#import <UIkit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import <QuartzCore/QuartzCore.h>
@interface BluetoothMgr () <CBCentralManagerDelegate, CBPeripheralDelegate> {
int count;
}
@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBPeripheral *myPeripheral;
@property (nonatomic, strong) CBUUID *deviceUUID;
@property (nonatomic, strong) CBService *functionalityServiceLayer;
@property (nonatomic, strong) CBCharacteristic *characteristic;
@end
@implementation BluetoothMgr
+ (BluetoothMgr *)sharedInstance {
static BluetoothMgr *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[BluetoothMgr alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if(self) {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil
options:@{ CBCentralManagerOptionRestoreIdentifierKey : @"DriveSafeDevice"}];
}
return self;
}
- (void)connectToDevice {
if(_myPeripheral != nil) {
[self.centralManager connectPeripheral:self.myPeripheral
options:@{ CBConnectPeripheralOptionNotifyOnConnectionKey: [NSNumber numberWithInt:1] }
];
}
}
- (void)scanForPeripherals {
NSLog(@"*** scan for Bluetooth peripherals");
if (_centralManager.state == CBCentralManagerStatePoweredOn) {
NSLog(@"--- central manager powered on. Start scanning.");
[self.centralManager scanForPeripheralsWithServices:@[self.deviceUUID] options:nil];
}
}
- (void)disconnectDevice {
if (!(self.centralManager == nil || _myPeripheral == nil)) {
[self.centralManager cancelPeripheralConnection:_myPeripheral];
}
}
#pragma mark - CBCentralManager delegate methods
- (void)centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary<NSString *,id> *)state {
NSLog(@"--- will restore state.");
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
NSLog(@"centralManagerDidUpdateState invoked...");
// Determine the state of the peripheral
if ([central state] == CBCentralManagerStatePoweredOff) {
NSLog(@"CoreBluetooth BLE hardware is powered off");
} else if (central.state == CBCentralManagerStatePoweredOn) {
NSLog(@"CoreBluetooth BLE hardware is powered on.");
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(@"did discover characteristic! peripheral: %@", peripheral);
NSLog(@"did discover characteristic! characteristic: %@", characteristic);
NSLog(@"did discover characteristic! error: %@", error);
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(@"did discover service! peripheral: %@", peripheral);
NSLog(@"services??: %@", peripheral.services);
NSLog(@"did discover service! error: %@", error);
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"peripheral: %@", peripheral);
NSLog(@"advertisement data: %@", advertisementData);
if([peripheral.name containsString:@"Adafruit Bluefruit LE"]) {
NSLog(@"central: %@", central);
NSLog(@"peripheral: %@", peripheral);
NSLog(@"advertisement data: %@", advertisementData);
NSLog(@"RSSI: %@", RSSI);
NSLog(@"found peripheral services!: %@", peripheral.services);
self.myPeripheral = peripheral;
self.myPeripheral.delegate = self;
//self.serviceUUID = ;
self.deviceUUID = [CBUUID UUIDWithString:[peripheral.identifier UUIDString]];
[self connectToDevice];
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"--- didConnectPeripheral");
NSLog(@"peripheral in didConnect function: %@", peripheral);
NSLog(@"any services??: %@", peripheral.services);
[peripheral discoverServices:@[self.deviceUUID]];
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
NSLog(@"--- connected to peripheral in foreground");
[[NSNotificationCenter defaultCenter] postNotificationName:@"connectedToDevice" object:self];
[self triggerLocalNotification];
} else {
NSLog(@"--- connected to peripheral in background");
[[NSNotificationCenter defaultCenter] postNotificationName:@"connectedToDevice" object:self];
[self triggerLocalNotification];
//[self.delegate didConnectToDevice];
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(nonnull CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"--- did disconnect ConnectPeripheral");
}
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray<CBService *> *)invalidatedServices {
NSLog(@"peripheral services: %@", peripheral.services);
NSLog(@"services invalidated: %@", invalidatedServices);
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"Failed to connect");
}
- (void)sendCodeToBTDevice:(NSString *)code
characteristic:(CBCharacteristic *)characteristic {
if(code != nil) {
NSData *data = [code dataUsingEncoding:NSUTF8StringEncoding];
[self.ourPeripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
@end
Final Thoughts...
My knowledge gap in how to write custom services / characteristics in an Arduino board / any BT device was clearly the problem here. I wasn't sure if I was supposed to be creating the services/characteristics in the device or in my OBJ-C app.
The concept is now laughable considering how obvious it is now that these are created in the device not the app.
Upvotes: 0
Views: 2221
Reputation: 1531
For anyone searching on Google I did end up finding the correct answer here on this matter.
It turned out that I needed to create custom services/characteristics on the peripheral BT device (not the iOS device). Once I created those custom services for receiving the 128 bit token string it was very easy to have the device respond back on a separate service layer for HID.
I have updated my code above to reflect this answer so check out the code to follow along with this answer.
In my didDiscoverPeripheral
method of my bluetooth manager class, I am making sure that the device UUID matches the known device UUID. Once I have found the right device by UUID I set the peripheral object's (passed in by this native method by default) delegate to self
(since my class uses the peripheral delegate) and then I am assigning the self.ourPeripheral
property of the class to the current peripheral obj. Once I have both set, I launch my custom connectToDevice
method which aligns the centralManager
property to connect with the given peripheral.
In the didConnectPeripheral
method given by the centralManager delegate, I call [peripheral discoverServices:nil];
which launched the delegate method called didDiscoverServices
. From here I then iterate over all discovered services looking for the predetermined service UUID I set in the custom service of the BT peripheral I built. Once I have found the custom service on the box I then call [peripheral discoverCharacteristics:nil forService:service];
. This also automatically calls the delegate method (as you'd expect) for didDiscoverCharacteristicsForService
. Again, more iteration and in this use case I am instead searching for the characteristic UUID that I have written in my service on the box for listening to BT serial communications.
Once I have found the right characteristic I then save it globally as a strong property of the class and then I can use my sendCodeToBTDevice
method shown above. This writes the data as text/string to the device over BT, is received over serial by the box, interpreted as a 128 bit token and validated. If valid, it responds on a different service layer sending back an HID compliant consumer code as requested.
Upvotes: 0
Reputation: 1568
Yes this is possible. In the HID device set a characteristic that you are going to update to notify. Then within the app setNotify
to true
and listen for didUpdateValueFor
Reference
Then what you do is write to the CBCharacteristic
that you wish to write to and once the write to the other CBCharacteristic
the didUpdateValueFor
will be called.
Upvotes: 0