在我的Xamarin.iOS应用中,我正在发布服务UUID,并同时使用BLE扫描所有服务UUID。这是我的代码:
[Register("AppDelegate")]
public class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
private CBCentralManager _cbCentralManager;
private CBPeripheralManager _cbPeripheralManager;
private System.Threading.Timer _timer = null;
// class-level declarations
public override UIWindow Window
{
get;
set;
}
public event EventHandler<string> Log;
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new MobileDemo.App());
if (_cbCentralManager == null)
{
_cbCentralManager = new CBCentralManager();
}
if(_cbPeripheralManager == null)
{
_cbPeripheralManager = new CBPeripheralManager();
}
_cbPeripheralManager.AdvertisingStarted -= CBPeripheralManager_AdvertisingStarted;
_cbPeripheralManager.AdvertisingStarted += CBPeripheralManager_AdvertisingStarted;
_cbCentralManager.DiscoveredPeripheral -= CBCentralManager_DiscoveredPeripheral;
_cbCentralManager.DiscoveredPeripheral += CBCentralManager_DiscoveredPeripheral;
_cbCentralManager.UpdatedState -= CBCentralManager_UpdatedState;
_cbCentralManager.UpdatedState += CBCentralManager_UpdatedState;
return base.FinishedLaunching(application, launchOptions);
}
private void CBPeripheralManager_AdvertisingStarted(object sender, NSErrorEventArgs e)
{
Log?.Invoke(null, $"advertising started: {e?.Error?.ToString()}");
}
private void CBCentralManager_UpdatedState(object sender, EventArgs e)
{
Log?.Invoke(null, $"CB update state: {_cbCentralManager.State}");
if (_cbCentralManager.State == CBCentralManagerState.PoweredOn)
{
_timer = null;
_timer = new System.Threading.Timer((obj) =>
{
Scan();
},
null, 1000, 5000);
// Wait for 2 seconds before start advertising, or it won't work sometimes
System.Threading.Timer _advertise = null;
_advertise = new System.Threading.Timer((obj) =>
{
StartAdvertisingOptions advOptions = new StartAdvertisingOptions
{
ServicesUUID = new CBUUID[] { CBUUID.FromString("12345678-1111-1111-1111-000000000000")}
};
_cbPeripheralManager.StartAdvertising(advOptions);
_advertise.Dispose();
_advertise = null;
},
null, 2000, 0);
}
else
{
_cbCentralManager.StopScan();
_cbPeripheralManager.StopAdvertising();
}
}
private async void Scan()
{
Log?.Invoke(null, $"Scanning...");
_cbCentralManager.ScanForPeripherals(new CBUUID[0]); // Do NOT pass null to this method. It won't work. Pass empty array instead
await Task.Delay(2000);
Log?.Invoke(null, $"Stoping scan...");
_cbCentralManager.StopScan();
}
private void CBCentralManager_DiscoveredPeripheral(object sender, CBDiscoveredPeripheralEventArgs e)
{
Log?.Invoke(null, $"Peripheral discovered");
GetService(e.Peripheral);
}
private async Task WaitForTaskWithTimeout(Task task, int timeout)
{
await Task.WhenAny(task, Task.Delay(timeout));
if (!task.IsCompleted)
{
throw new TimeoutException();
}
}
public async Task GetService(CBPeripheral peripheral)
{
var service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
return;
}
var taskCompletion = new TaskCompletionSource<bool>();
var task = taskCompletion.Task;
EventHandler<NSErrorEventArgs> handler = (s, e) =>
{
service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
taskCompletion.SetResult(true);
}
else
{
Log?.Invoke(null, $"no service");
}
};
try
{
peripheral.DiscoveredService += handler;
peripheral.DiscoverServices();
await this.WaitForTaskWithTimeout(task, 2000);
service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
}
else
{
Log?.Invoke(null, $"no service");
}
}
finally
{
peripheral.DiscoveredService -= handler;
}
}
public CBService GetServiceIfDiscovered(CBPeripheral peripheral)
{
return peripheral.Services?.FirstOrDefault();
}
}
我可以发现外围设备,但是我的DiscoveredService
处理函数从未被调用。我知道广告正在工作,因为我可以在同一应用(不同实现)的Android版本上发现服务UUID。但是我无法在其他iOS设备上发现该服务。我在做什么错了?
编辑
由于我发现自己实际上必须先连接到该设备,然后才能发现其服务,所以我编写了一个manager类来帮助连接和发现我正在从另一台设备发布的服务UUID:
public class BleServiceManager
{
private readonly CBCentralManager _cbCentralManager;
private bool _isGettingService;
private bool _isConnectingToPeripheral;
private Queue<CBPeripheral> _disconnectedPeripherals = new Queue<CBPeripheral>();
private Queue<CBPeripheral> _connectedPeripherals = new Queue<CBPeripheral>();
public event EventHandler<string> Log;
public event EventHandler<string> FoundMyService;
public BleServiceManager(CBCentralManager cbCentralManager)
{
_cbCentralManager = cbCentralManager;
}
public void FindServiceForPeripheral(CBPeripheral peripheral)
{
if (peripheral.State == CBPeripheralState.Disconnected)
{
_disconnectedPeripherals.Enqueue(peripheral);
if (!_isConnectingToPeripheral)
{
ConnectToNextPeripheral();
}
}
}
private void ConnectToNextPeripheral()
{
if (_disconnectedPeripherals.Any())
{
_isConnectingToPeripheral = true;
var p = _disconnectedPeripherals.Dequeue();
if (p.State == CBPeripheralState.Disconnected)
{
ConnectTo(p);
}
else
{
_isConnectingToPeripheral = false;
}
}
else
{
_isConnectingToPeripheral = false;
}
}
private async Task ConnectTo(CBPeripheral peripheral)
{
var taskCompletion = new TaskCompletionSource<bool>();
var task = taskCompletion.Task;
EventHandler<CBPeripheralEventArgs> connectedHandler = (s, e) =>
{
if (e.Peripheral?.State == CBPeripheralState.Connected && peripheral.Identifier?.ToString() == e.Peripheral.Identifier?.ToString())
{
_connectedPeripherals.Enqueue(peripheral);
taskCompletion.SetResult(true);
}
};
try
{
_cbCentralManager.ConnectedPeripheral += connectedHandler;
_cbCentralManager.ConnectPeripheral(peripheral);
await this.WaitForTaskWithTimeout(task, 2000);
Log?.Invoke(null, $"Bluetooth device connected = {peripheral.Name}");
if (!_isGettingService)
{
DiscoverServicesOnNextConnectedPeripheral();
}
}
catch (TimeoutException e)
{
Disconnect(peripheral);
}
finally
{
_cbCentralManager.ConnectedPeripheral -= connectedHandler;
ConnectToNextPeripheral();
}
}
private void DiscoverServicesOnNextConnectedPeripheral()
{
if (_connectedPeripherals.Any())
{
_isGettingService = true;
var p = _connectedPeripherals.Dequeue();
GetService(p);
}
else
{
_isGettingService = false;
}
}
private async Task GetService(CBPeripheral peripheral)
{
var service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
Disconnect(peripheral);
FoundMyService?.Invoke(null, service.UUID.Uuid);
DiscoverServicesOnNextConnectedPeripheral();
return;
}
var taskCompletion = new TaskCompletionSource<bool>();
var task = taskCompletion.Task;
EventHandler<NSErrorEventArgs> handler = (s, e) =>
{
service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
FoundMyService?.Invoke(null, service.UUID.Uuid);
taskCompletion.SetResult(true);
}
else
{
Log?.Invoke(null, $"no service");
}
};
try
{
peripheral.DiscoveredService += handler;
peripheral.DiscoverServices();
await this.WaitForTaskWithTimeout(task, 10000);
service = this.GetServiceIfDiscovered(peripheral);
if (service != null)
{
Log?.Invoke(null, $"service found");
FoundMyService?.Invoke(null, service.UUID.Uuid);
}
else
{
Log?.Invoke(null, $"no service");
}
}
catch(TimeoutException e)
{
}
finally
{
peripheral.DiscoveredService -= handler;
Disconnect(peripheral);
DiscoverServicesOnNextConnectedPeripheral();
}
}
private CBService GetServiceIfDiscovered(CBPeripheral peripheral)
{
return peripheral.Services?.FirstOrDefault(x => x.UUID?.Uuid?.StartsWith("12345678") == true); // the service uuid that I am advertising starts with 12345678
}
private void Disconnect(CBPeripheral peripheral)
{
_cbCentralManager.CancelPeripheralConnection(peripheral);
}
private async Task WaitForTaskWithTimeout(Task task, int timeout)
{
await Task.WhenAny(task, Task.Delay(timeout));
if (!task.IsCompleted)
{
throw new TimeoutException();
}
}
}
我一发现外围设备,便立即从FindServiceForPeripheral
呼叫AppDelegate
。它是事件驱动的,因此GetService
一次不会被调用超过1。它会找到其他服务(例如电池),但找不到我要宣传的服务。
答案 0 :(得分:0)
实际上比我想的要简单得多。公布服务UUID:
StartAdvertisingOptions advOptions = new StartAdvertisingOptions
{
ServicesUUID = new CBUUID[] { CBUUID.FromString("yourUuidHere") }
};
_cbPeripheralManager.StartAdvertising(advOptions);
要在另一台设备上发现相同的UUID,请执行以下操作:
_cbCentralManager.DiscoveredPeripheral += CBCentralManager_DiscoveredPeripheral;
private void CBCentralManager_DiscoveredPeripheral(object sender, CBDiscoveredPeripheralEventArgs e)
{
var key = new NSString("kCBAdvDataServiceUUIDs");
foreach(var x in e.AdvertisementData.Keys)
{
if(x is NSString && (NSString)x == key)
{
var y = e.AdvertisementData.ValueForKey((NSString)x);
if(y.ToString().Contains("yourUuidHere"))
{
// found it
}
}
}
}
我猜测DiscoverServices()
的呼叫是要找到GATT services,而不是您自己的广告。