Get IOBluetoothDevice * from CBPeripheral * (Mac OS X) - bluetooth

I am wondering if there is a way i can get an IOBluetoothDevice * object from a CBPeripheral * object because I am making an advanced bluetooth controller framework (Its going to be based on IOBluetooth) and Im using it so scan for Bluetooth Classic and Bluetooth Low Energy devices. Here are some of the problems:
IOBluetooth does allow you to search for both networks but for some reason its not showing up all of the Bluetooth Low Energy Devices that CoreBluetooth is.
If I use CoreBluetooth to search for Bluetooth Low Energy Devices I won't be able to get the address which I require for later use.
So is there any way i can get an IOBluetoothDevice object from a CBPeripheral?
thanks :D

I found out that I can search through /Library/Preferences/com.apple.bluetooth.plist > CoreBluetoothCache which just happens to contain the UUID and in its dictionary DeviceAddress = the address ;).
so I can get the UUID of a CBPeripheral by using the method
NSString *uuid = [[<*CBPeripheral*> identifier] UUIDString];
and then looking it up in the property list
NSDicionary *btdict = [NSDictionary dictionaryWithContentsOfFile:#"/Library/Preferences/com.apple.bluetooth.plist"];
NSDictionary *bleDevices = [btDict objectForKey:#"CoreBluetoothCache"];
NSString *address = [[bleDevices objectForKey:uuid] objectForKey:#"DeviceAddress"];
Then create a new IOBluetoothDevice with that address:
IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:address];
Its as easy as that :D

Swift 5.3 solution:
fileprivate func getDeviceAddress(for cbPeripheral: CBPeripheral) -> String?
{
if let userDefaults = UserDefaults(suiteName: "/Library/Preferences/com.apple.Bluetooth")
{
if let coreBluetoothCache = userDefaults.object(forKey: "CoreBluetoothCache") as? [String : Any]
{
if let deviceData = coreBluetoothCache[cbPeripheral.identifier.uuidString] as? [String : Any]
{
return deviceData["DeviceAddress"] as? String
}
}
}
return nil
}
Usage:
if let deviceAddress = self.getDeviceAddress(for: peripheral)
{
if let bluetoothDevice = IOBluetoothDevice(addressString: deviceAddress)
{
// Do your stuff
}
}

Related

device.GattServices returns an empty set for BLE devices on a Windows universal app

I am using the sample from Microsoft. When an advertisement is received I am calling
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress);
and then
device.GattServices()
but that always returns an empty list. Why is this happening? I have found no answer whatsoever.
If you want this to work by using advertisement watcher, you need to target the windows 10 creators update(10.0;Build 15063) and use the latest SDK, otherwise you will have to pair the device first.
To get the GattServices, first check if the device is not null.
Then use:
var serviceResult = await bluetoothLeDevice.GetGattServicesAsync();
if (serviceResult.Status == GattCommunicationStatus.Success)
{
//Do something with servivicResult list
}
But there is a catch; It can be that serviceResult.Status returns success, but not all or no services have been found yet.
My solution is to put it in a loop with a short delay and try it a few times until serviceResult count stays the same.
I wanted to put more explanation in my comment, but something went wrong and cannot edit my comment so I will add it as an answer.
I had exactly the same problem. for some reason you have to initialize your BLEdevice as null.
private BluetoothLEDevice device = null;
Also to prevent that you advertisementWatcher is setting your device over and over, use an if statement to set it only when the divice is null,
if (device == null)
{
device = await BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress);
}
or if you want to set multiple devices than add them to a collection and make sure each device is only added once.
This is my working code :
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher,
BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
BluetoothLEAdvertisementType advertisementType = eventArgs.AdvertisementType;
short rssi = eventArgs.RawSignalStrengthInDBm;
string localName = eventArgs.Advertisement.LocalName;
string manufacturerDataString = "";
var manufacturerSections = eventArgs.Advertisement.ManufacturerData;
if (manufacturerSections.Count > 0)
{
// Only print the first one of the list
var manufacturerData = manufacturerSections[0];
var data = new byte[manufacturerData.Data.Length];
using (var reader = DataReader.FromBuffer(manufacturerData.Data))
{
reader.ReadBytes(data);
}
manufacturerDataString = string.Format("0x{0}: {1}",
manufacturerData.CompanyId.ToString("X"),
BitConverter.ToString(data));
}
string res = string.Format("type={0}, rssi={1}, name={2}, manufacturerData=[{3}]",
advertisementType.ToString(),
rssi.ToString(),
localName,
manufacturerDataString);
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
textBoxWatcher.Text = res;
});
if (device == null)
{
device = await BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress);
if (device != null)
{
var deviceInfo = device.DeviceInformation;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (device.Name != string.Empty)
{
//ResultCollection is a observable observable collection of blueutooth devices
// to bind to a listvieuw,it is not needed!
ResultCollection.Add(new BleDevice(device));
if (deviceInfo.Name == "HMSoft")
{
if (ResultCollection[0] is BleDevice bleDevice)
{
BleDeviceId = bleDevice.Id;
SelectedBleDeviceName = bleDevice.Name;
}
Connect();
}
}
});
}
}

CBPeripheral advertisementData is different when discovering peripherals on OSX vs iOS (GAP/GATT)

I am hoping to port some of my CoreBluetooth code from iOS to OS X. I've set up a shared set of CoreBluetooth wrappers which are consumed by both an iOS app and an OS X app in exactly the same manner with the same BLE devices.
Scanning for peripherals:
override init() {
super.init()
let queue = DispatchQueue.global(qos: .background)
centralManager = CBCentralManager(delegate: self, queue: queue)
}
func startScanning() {
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
let deviceUUID = CBUUID(string: Project.Service.Device)
let recoveryUUID = CBUUID(string: Project.Service.DFURecovery)
centralManager?.scanForPeripherals(withServices: [deviceUUID, recoveryUUID], options: options)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
// Inspect advertisementData here to decipher what kind of device
}
On my iOS app, didDiscoverPeripheral is fired. Then when I inspect the advertisement data I get all the keys/values that I am expecting:
{
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = "My Device";
kCBAdvDataManufacturerData = <34045254 5877f283 43fdd12d ff530978 45000000 000050c2 6500>;
kCBAdvDataServiceData = {
Battery = <64>;
};
kCBAdvDataServiceUUIDs = (
"My Inforamtion"
);
}
However when this same code is run (scanning for the same devices) from an OS X app, the advertisement data is missing some of the fields.
{
kCBAdvDataIsConnectable = 1;
kCBAdvDataManufacturerData = <34045254 5877f36e 43fdd12d ff530978 45000000 000050c2 6500>;
}
The following key/value pairs are missing from advertisedData.
kCBAdvDataLocalName
kCBAdvDataServiceData
kCBAdvDataServiceUUIDs
I've tried adding those keys to the scanForPeripherals call like so:
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: true,
CBAdvertisementDataLocalNameKey: true,
CBAdvertisementDataServiceDataKey: true,
CBAdvertisementDataServiceUUIDsKey: true]
let deviceUUID = CBUUID(string: Nightlight.Service.Device)
let recoveryUUID = CBUUID(string: Nightlight.Service.DFURecovery)
centralManager?.scanForPeripherals(withServices: [deviceUUID, recoveryUUID], options: options)
With no effect.
OSX may call didDiscoverPeripheral multiple times per device, each call with different advertisementData. The solution I came up with is to write a cache.
import Foundation
import CoreBluetooth
class AdvertisementDataCache {
/// A dictionary of advertised data to peripheral.uuid
private var cache: [UUID: [String: Any]] = [:]
/// Appends advertisementData to our cache
/// for each unique `peripheral.uuid`.
func append(
advertisementData: [String: Any],
to peripheral: CBPeripheral
) -> [String: Any] {
// Join our cached adverts (our `cache` ivar)
// with new adverts (`advertisementData`)
let joined = advertisementData.reduce(
cache[peripheral.identifier] ?? [:]
) {
var all = $0
all[$1.key] = $1.value
return all
}
// write back to our private iVar
cache[peripheral.identifier] = joined
// Return all cached averts for this peripheral
return joined
}
/// Purges all cached avertisements for all peripherals
func clear() {
cache.removeAll()
}
}
And then in didDiscoverPeripheral:
private var advertisementDataCache = AdvertisementDataCache()
public func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData adPart: [String: Any],
rssi RSSI: NSNumber
) {
let advertisementData = advertisementDataCache.append(
advertisementData: adPart,
to: peripheral
)
// advertisementData now contains all data contained in multiple callbacks
}
Update. I am using UIKitForMac Beta 2 (now called Catalyst) and am seeing the same issue. The solution listed above (caching/aggregating the adverstisment data) applies in this situation as well.

Retrieving company name from HMService and/or HMAccessory object instance

I am using Home Kit Accessory simulator and I'd like to retrieve the company name of an accessory from an instance of HMService. However, when I add a breakpoint I cannot see any field related to the company name (I searched in both HMService and HMAccessory).
Any suggestion?
You can get name of Manufacturer from HMServiceTypeAccessoryInformation service, Service contains characteristic array in this there is HMCharacteristicTypeManufacturer characteristic.
You can use this to display name of company.
- (HMCharacteristic *)characteristicForAccessory:(HMAccessory *)accessoryValue{
HMAccessory *thisAccessory = accessoryValue;
HMService *service;
for (HMService *thisService in thisAccessory.services) {
if([thisService.serviceType isEqualToString:HMServiceTypeAccessoryInformation]) {
service = thisService;
}
}
HMCharacteristic *characteristic;
if (service) {
for (HMCharacteristic *charact in service.characteristics) {
if ([charact.characteristicType isEqualToString:HMCharacteristicTypeManufacturer]) {
characteristic = charact;
}
}
}
return characteristic;
}
Use Characteristic object's value property to get name of manufacturer.
Like characteristic.value
Take a look at Raeid Saqur's RSHomeKit framework:
You can get accessory by calling service.accessory. Then use:
+ (NSString *)getManufacturerNameForHMAccessory:(HMAccessory *)accessory;
+ (NSString *)getManufacturerNameForHMAccessory:(HMAccessory *)accessory {
if (!accessory) {
return nil;
}
HMCharacteristic *manufacturer = [HomeKitUtility getCharacteristicWithUUID:HMCharacteristicTypeManufacturer forAccessory:accessory];
if (manufacturer && manufacturer.value) {
return (NSString *)manufacturer.value;
}
return nil;
}

How to switch to Front Camera in Windows Phone 8.1 (WinRT/Jupiter)

I can't seem to find the property for the MediaCapture class that allows me to detect the front camera and switch to it if available. Here is my current setup of the device, it all works as expected on Windows (front cam) and Phone (rear cam). None of the Microsoft samples show the front camera being used in Universal or WP 8.1 (WinRT/Jupiter).
mediaCaptureManager = new MediaCapture();
await mediaCaptureManager.InitializeAsync();
if (mediaCaptureManager.MediaCaptureSettings.VideoDeviceId != "" && mediaCaptureManager.MediaCaptureSettings.AudioDeviceId != "")
{
StartStopRecordingButton.IsEnabled = true;
TakePhotoButton.IsEnabled = true;
ShowStatusMessage("device initialized successfully!");
mediaCaptureManager.VideoDeviceController.PrimaryUse = CaptureUse.Video;
mediaCaptureManager.SetPreviewRotation(VideoRotation.Clockwise90Degrees);
mediaCaptureManager.SetRecordRotation(VideoRotation.Clockwise90Degrees);
mediaCaptureManager.RecordLimitationExceeded += RecordLimitationExceeded;
mediaCaptureManager.Failed += Failed;
}
There is a sample on the Microsoft github page that is relevant, although they target Windows 10. Still, the APIs should work on 8/8.1.
UniversalCameraSample: This one does capture photos, and supports portrait and landscape orientations. Here is the relevant part:
private static async Task<DeviceInformation> FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel desiredPanel)
{
// Get available devices for capturing pictures
var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
// Get the desired camera by panel
DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desiredPanel);
// If there is no device mounted on the desired panel, return the first device found
return desiredDevice ?? allVideoDevices.FirstOrDefault();
}
And you can use it like so:
// Attempt to get the front camera if one is available, but use any camera device if not
var cameraDevice = await FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel.Front);
if (cameraDevice == null)
{
Debug.WriteLine("No camera device found!");
return;
}
// Create MediaCapture and its settings
_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings { VideoDeviceId = cameraDevice.Id };
// Initialize MediaCapture
try
{
await _mediaCapture.InitializeAsync(settings);
_isInitialized = true;
}
catch (UnauthorizedAccessException)
{
Debug.WriteLine("The app was denied access to the camera");
}
catch (Exception ex)
{
Debug.WriteLine("Exception when initializing MediaCapture with {0}: {1}", cameraDevice.Id, ex.ToString());
}
Have a closer look at the sample to see how to get all the details. Or, to have a walkthrough, you can watch the camera session from the recent //build/ conference, which includes a little bit of a walkthrough through some camera samples.
Here is how to get the device's available cameras and set the front one for the stream:
mediaCaptureManager = new MediaCapture();
var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
var deviceInfo = devices[0]; //grab first result
foreach (var device in devices)
{
if (device.Name.ToLowerInvariant().Contains("front"))
{
deviceInfo = frontCamera = device;
hasFrontCamera = true;
}
if (device.Name.ToLowerInvariant().Contains("back"))
{
rearCamera = device;
}
}
var mediaSettings = new MediaCaptureInitializationSettings
{
MediaCategory = MediaCategory.Communications,
StreamingCaptureMode = StreamingCaptureMode.AudioAndVideo,
VideoDeviceId = deviceInfo.Id
};
await mediaCaptureManager.InitializeAsync(mediaSettings);
You'll need to consider rotation because front and rear cameras on different devices have different rotations, but this will initialize your MediaCapture properly

How to convert a NSString to a String in MonoTouch

I am trying to implement push notifications for MonoTouch but I couldnĀ“t find samples of this anywhere. My problem is trying to read the deviceID into a .NET string.
The output below is just a lot of question marks so I am doing something wrong here.
Any help would be greatly appreciated!
public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken)
{
Console.WriteLine("Converting device ID...");
NSString s = NSString.FromData(deviceToken, NSStringEncoding.UTF8);
Console.WriteLine("DEVICE ID IS: " + s);
s = NSString.FromData(deviceToken, NSStringEncoding.ASCIIStringEncoding);
Console.WriteLine("DEVICE ID IS: " + s);
s = NSString.FromData(deviceToken, NSStringEncoding.Unicode);
Console.WriteLine("DEVICE ID IS: " + s);
}
There is an operator for implicit conversion in MonoTouch.
So you just do:
NSString s = NSString.FromData(deviceToken, NSStringEncoding.UTF8);
string csstring = s;
// done
Alternatively, you can use NSString's ToString() method:
NSString s = NSString.FromData(deviceToken, NSStringEncoding.UTF8);
string csstring = s.ToString();
Here's related documentation.
Here's a great article showing how to do push notifications with MonoTouch:
http://weblogs.thinktecture.com/cweyer/2010/12/implementing-push-notifications-for-ios-with-c-monotouch-using-the-cloud-urban-airship.html
Here's the snippet that does what you want:
var str = (NSString)Runtime.GetNSObject (
Messaging.intptr_objc_msgSend (deviceToken.Handle, new Selector("description").Handle));
var deviceTokenString = str.ToString ().Replace ("<", "").Replace (">", "").Replace (" ", "");
I've managed to implement this, and I used the following code:
NSString newDeviceToken = new NSString(MonoTouch.ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr_IntPtr(new MonoTouch.ObjCRuntime.Class("NSString").Handle, new MonoTouch.ObjCRuntime.Selector("stringWithFormat:").Handle, strFormat.Handle, deviceToken.Handle));
string token = newDeviceToken.ToString();
This will provide you with a string which reads
< 64characterlonghexstring >
You can use a Regex to get rid of the spaces and the "<" ">" character as required.
I found the following project very useful in getting a Monotouch implementation of push notifications working:
C# Apple Push Notification Service - it provides client side code as well as server side code.

Resources