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.
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 not new to iOS coding but very new to BLE in iOS.
I have successfully written code in my Main View Controller to access and write to a BLE device. So all the peripheral and manager stuff is tested.
My app will get a list of BLE devices, connect to them for the serial number, disconnect from them, and then put them in a tableView. When the user selects one, I need to go to a different view and reconnect. I want to write another class as a utility to connect to the peripheral, which I am successfully passing to the utility and read and write to it in the utility. The peripheral.state is connected.
Every time I try to write to the peripheral, my code crashes. However the peripheral reports that the data was written.
So to figure out what was wrong I started to just try and read the peripheral with the same results, Crash.
I'm starting a new manager in the utility and centralManagerDidUpdateState is not getting called there. I also tried it without the manager thinking that if I knew of the peripheral, it would just work.
So, how do I get centralManager to work in the new class? Do you have to somehow stop the manager in the main class?
- (void)getPeripheralStatus:(CBPeripheral *)peripheral{
device_Info_UUID = [CBUUID UUIDWithString:kDevice_Info_SID];
security_UUID = [CBUUID UUIDWithString:kSecurity_SID];
status_UUID = [CBUUID UUIDWithString:kStatus_SID];
device_ID_char_UUID =[CBUUID UUIDWithString:kSecurity_char_DeviceID];
SHA256in = [[NSMutableString alloc]initWithString:lockMasterKey];
mgr = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
peripheral.delegate = self;
[self.mgr connectPeripheral:peripheral options:nil];
This is the line works in the main controller but not in my Utility.
[self.lock writeValue:myID forCharacteristic:charac type:CBCharacteristicWriteWithResponse];
Thanks!
I am not sure passing a peripheral to other viewcontroller is a "good idea" unless you don't need further update on the peripheral. If you want a "fresh" peripheral in multiple view controller i recommend to use singleton design thus you can handle all delegates at once and reduce redundant codes.
sample code I found.
https://github.com/kickingvegas/YmsCoreBluetooth
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]];
}
}
What is the best way to get notification while network interface becomes available/unavailable in iOS, different from Reachability? (maybe there is some good way of doing in SystemConfiguration or CFNetworks framework, or somehow using the native unix sockets networking API?)
Instead of checking network reachability I'm just checking if I'm able to get network info with this function, the only problem is when to check. I don't want to fire up an NSTimer every 0.1 second to do this, though it is solution, I would rather like to somehow get notified when user switches WiFi on/off in settings. (Reachability takes several seconds to nofity me when Im disabling wwan interface in settings.)
- (void)initializeCurrentNetworkInfo
{
NSArray* interfacesSupported = (__bridge_transfer NSArray*) CNCopySupportedInterfaces();
NSLog(#"%s: Supported interfaces: %#", __func__, interfacesSupported);
NSDictionary* interfaceInfo = nil;
for (NSString *interfaceName in interfacesSupported) {
interfaceInfo = (__bridge_transfer NSDictionary*) CNCopyCurrentNetworkInfo((__bridge_retained CFStringRef)interfaceName);
if (interfaceInfo && [interfaceInfo count]) {
self.isInterfaceActive = YES;
self.currentSSID = [NSString stringWithString:interfaceInfo[#"SSID"]];
self.currentBSSID = [NSString stringWithString:interfaceInfo[#"BSSID"]];
break;
} else if (!interfaceInfo){
self.isInterfaceActive = NO;
self.currentBSSID = #"ff:ff:ff:ff:ff:ff";;
self.currentSSID = #"interface is unavailable";
}
}
}
Info: I'm developing the UPNP application, and each time interface becomes available/unavailable I subsequently initialise or nullify my UPNP service object.
The problem is that in some rare occasions my app crashes, while I'm changing Wi-Fi switch on/off consequently for the purpose of testing. (Reachability works fine in the background)
So I could have traced it and found it that it obviously crashes while trying to receive an httpu datagram on socket of no longer available interface, when switching wifi off (recvLen = cg_socket_recv(sock, dgmPkt);), if I understand it right, it means that the background thread listening on socket in my UPNP framework (CyberGarages' CyberLink) is unaware of interface state changes(bug probably?), that's why I really want to stop the upnpService as fast as possible once the interface state changes.
I'm using the Tony Millions' Reachability version, and once I receive networkReachabilityChanged: I check for available interfaces, but I guess it's not the best way of doing it.
Thanks.