找不到服务

时间:2019-02-28 19:01:16

标签: ios xamarin.ios bluetooth-lowenergy

在我的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。它会找到其他服务(例如电池),但找不到我要宣传的服务。

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,而不是您自己的广告。