I would like to create a Garmin wearable app (Data Field), which would communicate with my iOS app using Bluetooth LE (BluetoothLowEnergy API on Garmin and CoreBluetooth on iOS). There's a limitation of Garmin's API - it can work only as a central device, so I configured my iPhone to act as a "virtual" peripheral (I tested it both with my own debug app and LightBlue).
I managed to establish a connection between my Garmin Vivoactive 3 Music and my iPhone, but I still have some issues to make it work.
From Garmin wearable I managed to search, find and pair a device (my iPhone virtual peripheral), so that both: self.pairedDevice = BluetoothLowEnergy.pairDevice(scanResult) and BluetoothLowEnergy.getPairedDevices().next() don't return nulls.
The problem I have is that this callback is never called on a Garmin device:
function onConnectedStateChanged(device, connectionState) {
if (connectionState==BluetoothLowEnergy.CONNECTION_STATE_CONNECTED) {
displayString = "Connected";
}
if (connectionState==BluetoothLowEnergy.CONNECTION_STATE_DISCONNECTED) {
displayString = "Disconnected";
}
}
Moreover, when discovering my virtual peripheral, I can see available services in advertisement data, but once the devices are paired, calling device.getServices() returns an empty iterator.
I already checked that BluetoothLowEnergy.getAvailableConnectionCount() is 3, so there should be no problem with regard to connections number limit. Is there any way to force the connection?
On iOS I do something like this:
let service = CBMutableService(type: serviceCbuuid, primary: true)
let writeableCharacteristic = CBMutableCharacteristic(type: characteristicCbuuid,
properties: [.write],
value: nil,
permissions: [.writeable])
service.characteristics = [writeableCharacteristic]
currentService = service
peripheralManager = CBPeripheralManager(delegate: self,
queue: DispatchQueue.global(qos: .userInteractive))
then I add currentService using peripheralManager?.add(currentService) and in didAdd service callback I start advertising by calling peripheral.startAdvertising(options).
Maybe I miss some setting on the iOS end to make this work?
Related
We need our app to receive notifications from the OS when the connects or disconnects from a bluetooth audio device (specifically, the one in their car).
The app gets notified when the BT device initially connects, but it then immediately seems to disconnect and logs the error:
BTCentralManager::DisconnectedPeripheral > SoundCore mini(ATT)
ERROR Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us."
...and the "DisconnectedPeripheral" event is never actually fired.
We're unsure how to simply receive connect and disconnect events while the app is backgrounded.
Do we need to connect the peripheral with the central manager? We only need to know if the audio device is connected or not - we don't need to interact with it in any way.
The events never call a second time from the background, after getting the disconnect peripheral. Presumably because of the error message we are receiving.
Sample code below:
public class BTCentralManager : CBCentralManagerDelegate
{
public CBCentralManager centralManager;
public static CBPeripheral peripheral;
public BTCentralManager()
{
System.Diagnostics.Debug.WriteLine("BTCentralManager::Constructor > ");
centralManager = new CBCentralManager(this, new DispatchQueue("myqueue"),
new CBCentralInitOptions { ShowPowerAlert = true, RestoreIdentifier = "myRestoreIdentifier" });
NSUuid[] arr = { new NSUuid("7e9002be-547f-42bc-8d56-209736f70aa2") }; //Sound core mini
var devices = centralmanager.retrieveperipheralswithidentifiers(arr);
peripheral = devices.firstordefault();
//is the only way to trigger the events, we need to first connect the peripheral to central manager???
centralManager.connectPeripheral(peripheral, new PeripheralConnectionOptions
{
NotifyOnConnection = true,
NotifyOnDisconnection = true,
NotifyOnNotification = true
});
}
//Always is triggered inclusive in background
public override void UpdatedState(CBCentralManager central)
{
System.Diagnostics.Debug.WriteLine("BTCentralManager::UpdatedState > " + central.State.ToString());
}
//Only is triggered when the device is first time connected ( Inclusive in background)
public override void ConnectedPeripheral(CBCentralManager central, CBPeripheral peripheral)
{
System.Diagnostics.Debug.WriteLine("BTCentralManager::ConnectedPeripheral > " + peripheral.Name);
//After the connect made successfully I receive this error, and never connect again to the device
//Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us."
}
public override void DisconnectedPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error)
{
System.Diagnostics.Debug.WriteLine("BTCentralManager::DisconnectedPeripheral > " + peripheral.Name + " ERROR>" + error.Description);
}
public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
{
System.Diagnostics.Debug.WriteLine("BTCentralManager::DiscoveredPeripheral > " + peripheral.Name);
// base.DiscoveredPeripheral(central, peripheral, advertisementData, RSSI);
}
}
as far as I understand your question, you need to observe the availability of a bluetooth audio device. I had some investigations in this a couple of time ago and the result was not really satisfying me. Here's my conclusion:
1.) CoreBluetooth is intented to be used for Bluetooth 4.0 / Bluetooth Low Energy devices only. Many Bluetooth headphones or even car radios are still not bluetooth 4.x. So, unless you can rely on your user's bluetooth audio device to be 4.x, CoreBluetooth might be a waste of time.
2.) Your app wont be notified about a audio device being connected when your app is in background.
So far no good. But, there might be some approaches that may help.
1.) By using the CLLocationManager, you may start observing e.g. the compass (not the location to save battery) to get notified whenever the phone has been moved. Simply check the connected audio devices when the app is calling the CLLocationManagerDelegate. This is of course not very efficient, but it might work.
2.) use iBeacons and CLBeaconRegions if available. Place an iBeacon in the user's car and start observing the beacon.
I know, this is not exactly what you want to hear, but I'm afraid there's no straight forward solution for your problem.
cheers,
Peter
I am programming a MIDI tool on my iPad which connects to a synthesizer using Bluetooth.
First thing I did was to use the iPad as Central controller with the CABTMidiCentralViewController - if I connect to a Bluetooth device here I immediately get a notification that a new MIDI device has connected and everything's fine.
Now to support another Bluetooth dongle I need to config the iPad as peripheral, for that I use CABTMidiLocalPeripheralViewController. On the other side of BT (the central device which is NOT the iPad) I see the iPad advertising its service and I can connect to it, however I have no idea how to recognize this connection on the iPad side - I don't get any notification about new devices. Probably I'm missing something here?
I use CABTMidiLocalPeripheralViewController just like in the documentation example for CABTMidiCentralViewController, so that's the code (it's just displaying the vc anyway, isn't it?):
- (void)configCABTMIDIPeripheral:(id)sender
{
CABTMIDILocalPeripheralViewController *viewController =[CABTMIDILocalPeripheralViewController new];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: #selector(didConfigCABTMIDIPeripheral:)];
navController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popC = navController.popoverPresentationController;
popC.permittedArrowDirections = UIPopoverArrowDirectionAny;
popC.sourceRect = [sender frame];
UIButton *button = (UIButton *)sender;
popC.sourceView = button.superview;
[self.delegateBaseViewController presentViewController:navController animated:YES completion:nil];
}
As for the notifications I register a static callback function when I create a MIDI client - my guess is that as a peripheral device this works different, maybe someone here can point me in the right direction:
OSStatus s = 0;
s = MIDIClientCreate((CFStringRef)#"MIDI Client", PGMIDINotifyProc, arc_cast<void>(self), &_client); // Create MIDI Client
s = MIDIOutputPortCreate(_client, (CFStringRef)#"Output Port", &_outputPort); // Create MIDI output port
s = MIDIInputPortCreate(_client, (CFStringRef)#"Input Port", PGMIDIReadProc, arc_cast<void>(self), &_inputPort); // Create MIDI input port
...with PGMIDINotifyProc looking like...
void PGMIDINotifyProc(const MIDINotification *message, void *refCon)
{
NSLog(#"PGMIDINotifyProc");
PGMidi *self = arc_cast<PGMidi>(refCon);
[self midiNotify:message];
}
(Credits to PGMidi at https://github.com/petegoodliffe/PGMidi)
So... how can I see if my peripheral iPad did connect to a MIDI device?
UPDATE
After a long break I decided to give it another try, this time I made some tests by implementing a CBPeripheralManager and a CBCentralManager on two devices (one peripheral and one central) to have a closer look on what's going on behind the scenes:
Without going too much into detail, here are some observations:
if the peripheral device doesn't power on the CBPeripheralManager, no Bluetooth services are offered. Makes sense.
if the peripheral device offers some random service, the central device will discover this service they connect and can exchange data. Great.
if the peripheral device still offers some random service while the central specifically looks for the MIDI service / characteristics, it will find the MIDI service and connect to it. I don't get any notifications about this on the peripheral side, since I'm just the delegate of the peripheral offering some random service.
if both the peripheral device and the central offer / scan for the MIDI service / characteristics, they don't find anything.
It's like the MIDI service appears on it's own if I offer some other service but is not there if I offer no service. Moreover the iPad seems to block or catch the MIDI service / characteristic if I try to offer it myself.
Any ideas here? Come on :) :)
Assuming you use CABTMIDILocalPeripheralViewController to connect, try replace arc_cast<void>(self) with (__bridge void*)self as the current PGMidi library does, but if you use this library you should not write this client initialization yourself, it is created inside the [[PGMidi alloc] init] call. anyway can you see the PGMIDINotifyProc is been called?
let me know
I am designing an application which will communicate with a TI LaunchPad through a BLE Mini by utilizing CoreBluetooth, however I am struggling to transmit data between my iPhone and the Red Bear BLE Mini. Currently, I am trying to send a command which will instruct the Launchpad to turn an LED on and off. I have implemented all of the CoreBluetooth methods and confirmed that I am connected to the BLE Mini. I also have discovered this device's singular service and the five characteristics associated with this device with my iPhone application, so I know I am connected to the device. After confirming this connection, I attempted to write to the write characteristic, but I'm not sure if I wrote to this characteristic properly. Also, I'm not sure how I should be encoding the data. I have tried both ascii and utf8. Here is what I wrote:
let string = "LED-ON"
let data = string.data(using: .ascii)
if manager.write_char != nil{
print("trying")
print(manager.write_char.uuid)
peripheral.writeValue(data!, for: manager.write_char, type: CBCharacteristicWriteType.withoutResponse)
The print statement confirms that this characteristic has the uuid that I defined as being the write characteristic
let WRITE_CHAR = "713D0004-503E-4C75-BA94-3148F18D941E"
I believe this is the correct write characteristic. I have also tried 71230003. On the Launchpad side, I have uploaded the following code:
int incomingByte = 0; // for incoming serial data
void setup() {
// put your setup code here, to run once:
Serial.begin(19200);
Serial1.begin(9600);
pinMode(7, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
//if (Serial1.available()){
// Serial.print("available");
//Read in data
Serial.print(Serial1.available());
Serial.print(" ");
Serial.print(Serial1.read());
Serial.print("\n");
if (Serial1.available() > 0) {
// read the incoming byte:
incomingByte = Serial1.read();
// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
}
In the serial monitor, all I receive are 0 from the available check and -1 from the read method both of which signify that no data is being received. Am I transmitting data to the BLE Mini correctly? If so, am I reading it incorrectly? Additionally, is there something I'm supposed to do to tell the BLE Mini to transmit data? Sorry for the long winded question. Thank you so much for the help.
I have an iOS app which advertises itself successfully using a CBPeripheralManager. In a Chrome App, I have successfully discovered the device (hence obtaining its address).
I have added a service with a custom UUID to the peripheral manager. didAddService is called (after the peripheral manager is powered on), and so I assume that part is successful.
I call chrome.bluetoothLowEnergy.connect(device address) in the Chrome app. The callback function is called without issue, and so I believe the connection is successful. There is no function called within the app when the connection has occurred - should there be?
I then call chrome.bluetooth.getServices(device address) in the Chrome app. An array of services is returned, however the array is empty. I believe it should return an array of length 1, not 0.
Am I overlooking something? Why are no services being returned? Should any of the peripheralManager functions be called during these operations?
Connect code:
function connect(address) {
/* Connect persistently to the device. */
chrome.bluetoothLowEnergy.connect(address, {persistent: true}, function() {
if(chrome.runtime.lastError) {
/* If we click a green device, we get this. We should try and handle it
earlier. e.g. by removing the listener, or instead using the click
to disconnect the device. */
if(chrome.runtime.lastError.message == 'Already connected')
setDeviceConnected(address);
/* Print error and exit. */
console.log(chrome.runtime.lastError.message);
return;
}
console.log('Successfully connected to ' + address);
setDeviceConnected(address);
// getDeviceService();
getDeviceServices(address);
});
return true;
}
Get services code:
function getDeviceServices(address) {
chrome.bluetoothLowEnergy.getServices(address, function(services) {
console.log('services.length: ' + services.length);
});
}
Peripheral setup code:
- (void)setupPeripheral {
_serviceName = SERVICE_NAME;
_serviceUUID = [CBUUID UUIDWithString:SERVICE_UUID_STRING];
_characteristicUUID = [CBUUID UUIDWithString:CHARACTERISTIC_UUID_STRING];
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
_characteristic = [[CBMutableCharacteristic alloc] initWithType:_characteristicUUID
properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
_service = [[CBMutableService alloc] initWithType:_serviceUUID primary:YES];
_service.characteristics = #[_characteristic];
NSLog(#"Peripheral set up");
}
Start advertising (add the service just before we start advertising):
/* Start advertising */
- (void)startAdvertising {
NSLog(#"Starting advertising...");
NSDictionary *advertisment = #{
CBAdvertisementDataServiceUUIDsKey : #[self.serviceUUID],
CBAdvertisementDataLocalNameKey: self.serviceName
};
[self addServices];
[self.peripheralManager startAdvertising:advertisment];
}
Call to start advertising within didUpdateState:
/* Did update state */
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOn:
NSLog(#"peripheralStateChange: Powered On");
// When the bluetooth has turned on, start advertising.
[self startAdvertising];
break;
Update
On connecting to the peripheral app with OSX app LightBlue, an alert shows up on the app to request a pairing. Then LightBlue can read characteristics from the app. When connecting with the Chrome app, the pairing alert does not show up.
Having chatted to web-bluetooth developers at W3C, I have now been able to discover services from the device, by updating chrome from v51 to v52 (apparantly also v53 works).
However currently (though I am yet to try it out), reading characteristics is currently unsupported for OSX, as seen here.
(I have not yet looked into it but this may provide some options on how to overcome the issues described here.)
I've ran into this problem myself. I've developed a simple app that scans for devices and their services, but all devices found always report back an empty array of services. I have this problem on both OsX and ChromeOS.
There's an exception to this: when the device has previously been paired, I was able to get the services array on ChromeOS. This is confusing to me, since nothing I've read anywhere says a device has to be manually paired first, and there's no programatic way to pair a device one discovered.
EDIT: Upgrading to Chrome 53 fixed the problem. The site linked in louisdeb's answer is related to web-bluetooth, not chrome.bluetooth, so characteristics may actually work.
I need to get the list of paired bluetooth devices(iOS Devices) as same as the list in 'Bluetooth' section in iOS settings as shown in below picture.
Is it possible?
Have you seen any apps doing this type of functionality?
I have tried the following:
link1, link2, link3, link4, link5, link6
But nothing helped me clearly to get the exact list. I hope there should be a way to achieve this. Please help me by sharing your experience.
Thank you.
It's not possible to retrieve list of paired peripherals from iOS. Neither it's possible to check if specific peripheral is paired.
Retrieving peripheral which is paired
There are two cases which you need to consider:
Peripheral may be already connected in the system (iOS connects automatically with some peripherals in order to for example display battery level). In this case peripheral won't be broadcasting and detection using scanForPeripherals won't work.
Peripheral is paired, but disconnected. In this case retrieveConnectedPeripherals(withServices:) won't work.
Therefore to retrieve your peripheral you need to combine both things. First you need to check if it's in peripherals returned from retrieveConnectedPeripherals(withServices:). If not you should scanForPeripherals.
If you want to retrieve peripheral which is out of range, you can try to use retrievePeripherals(withIdentifiers:), however it may return also not paired devices and it relies on peripheral's UUID which you have to save after pairing.
Detecting if peripheral is paired
There is one way to detect if the specific peripheral is paired. You need to try to read from protected characteristic (which requires encryption - bonding). If you receive expected data, it means that user accepted pairing request. Otherwise you will receive empty response or none.
References
You can read more about Bluetooth in my articles:
Original answer
Swift – Bluetooth Low Energy communication using Flow Controllers
How to communicate with Bluetooth Low Energy devices on iOS
Bluetooth Low Energy – background mode on iOS
You can use ShowBluetoothAccessoryPicker if you are using the Classic Bluetooh into an MFi device.
https://developer.apple.com/documentation/externalaccessory/eaaccessorymanager/1613913-showbluetoothaccessorypicker
The code below is a C# but you can easily convert it into the IOS (object c your swift)
EAAccessoryManager.SharedAccessoryManager.ShowBluetoothAccessoryPicker(null, completion: ((Foundation.NSError error) => {
Console.WriteLine("My callback");
if (error != null && (uint)error.Code != (uint)EABluetoothAccessoryPickerError.AlreadyConnected)
{
Console.WriteLine(String.Format("Error code: {0} Desc: {1}", error.Code, error.DebugDescription));
Console.WriteLine("Failed? " + EABluetoothAccessoryPickerError.Failed.ToString());
Console.WriteLine("Failed? " + Convert.ToInt64(EABluetoothAccessoryPickerError.Failed));
}
}));
You need to find the service UUID in which you are interested, in my case it works perfectly,
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:SERVICE_UUID]]
options:options];
and when it will find any device which advertise same service UUID, then it will appear in the screen which you have pointed above.
handle didDiscoverperipherel in this way:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
_discoveredPeripheral = peripheral;
if(![self.mRemoteDevices containsObject:_discoveredPeripheral])
{
NSArray *peripherels = [self.centralManager retrievePeripheralsWithIdentifiers:#[_discoveredPeripheral.identifier]];
[self.mRemoteDevices addObject:[peripherels objectAtIndex:0]];
}
}