Я - мое приложение Xamarin.iOS, я рекламирую UUID службы и одновременно сканирую ВСЕ UUID службы с помощью BLE.Вот мой код:
[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
никогда не вызывается.Я знаю, что реклама работает, потому что я могу обнаружить UUID службы в версии для Android этого же приложения (другая реализация).Но я не могу обнаружить сервис на другом устройстве iOS.Что я делаю не так?
РЕДАКТИРОВАТЬ
Поскольку я обнаружил, что мне действительно нужно подключиться к устройству, прежде чем обнаруживать его службы, я написал класс менеджера, чтобы помочьподключите и найдите 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 за раз.Он находит другие сервисы (например, для аккумулятора), но никогда не сервис, который я рекламирую.