首页 > 其他分享 >MASA MAUI Plugin IOS蓝牙低功耗(三)蓝牙扫描

MASA MAUI Plugin IOS蓝牙低功耗(三)蓝牙扫描

时间:2022-10-11 13:58:28浏览次数:106  
标签:return MASA iOS peripheral 低功耗 蓝牙 static public

项目背景

MAUI的出现,赋予了广大Net开发者开发多平台应用的能力,MAUI 是Xamarin.Forms演变而来,但是相比Xamarin性能更好,可扩展性更强,结构更简单。但是MAUI对于平台相关的实现并不完整。所以MASA团队开展了一个实验性项目,意在对微软MAUI的补充和扩展,
项目地址https://github.com/BlazorComponent/MASA.Blazor/tree/main/src/Masa.Blazor.Maui.Plugin

每个功能都有单独的demo演示项目,考虑到app安装文件体积(虽然MAUI已经集成裁剪功能,但是该功能对于代码本身有影响),届时每一个功能都会以单独的nuget包的形式提供,方便测试,现在项目才刚刚开始,但是相信很快就会有可以交付的内容啦。

前言

本系列文章面向移动开发小白,从零开始进行平台相关功能开发,演示如何参考平台的官方文档使用MAUI技术来开发相应功能。

介绍

之前两篇文章我们实现了安卓蓝牙BLE的相关功能,本文我们将IOS的BLE功能实现一下。
,考虑到Swift语法对于c#开发人员更友好,本文示例代码参考Swift,相关代码来自苹果开发者官网
https://developer.apple.com/documentation

开发步骤

修改项目

Masa.Blazor.Maui.Plugin.Bluetooth项目中的Platforms->iOS文件夹下,添加一个部分类MasaMauiBluetoothService,在安卓中有BluetoothManager,在ios中对应的是CBCentralManager,但是不同有安卓还有个适配器Adapter的概念,在ios中关于设备扫描、连接和管理外围设备的对象,都是通过CBCentralManager直接管理的,我们看一下他的初始化方法

init(
    delegate: CBCentralManagerDelegate?,
    queue: DispatchQueue?,
    options: [String : Any]? = nil
)

delegate:接收中心事件的委托。相当于我们在安装中实现的DevicesCallback

queue:用于调度中心角色事件的调度队列。如果该值为 nil,则中央管理器将使用主队列分派中心角色事件。这个我们可以简单的理解为和安卓的UI线程或者后台线程对应,更详尽的说明请参考
https://developer.apple.com/documentation/dispatch/dispatchqueue

options:配置信息,我们这里只用到了ShowPowerAlert,代表蓝牙设备如果不可用,给用户提示信息。就好比你用了不符合标准的数据线,iphone会给你提示是一个意思。

 public static partial class MasaMauiBluetoothService
    {
        private static BluetoothDelegate _delegate = new();
        public static CBCentralManager _manager = new CBCentralManager(_delegate, DispatchQueue.DefaultGlobalQueue, new CBCentralInitOptions
        {
            ShowPowerAlert = true,
        });
        private sealed class BluetoothDelegate : CBCentralManagerDelegate
        {
            private readonly EventWaitHandle _eventWaitHandle = new(false, EventResetMode.AutoReset);

            public List<BluetoothDevice> Devices { get; } = new();

            public void WaitOne()
            {
                Task.Run(async () =>
                {
                    await Task.Delay(5000);
                    _eventWaitHandle.Set();
                });

                _eventWaitHandle.WaitOne();
            }
            public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral,
                NSDictionary advertisementData,
                NSNumber RSSI)
            {
                System.Diagnostics.Debug.WriteLine("OnScanResult");
                if (!Devices.Contains(peripheral))
                {
                    Devices.Add(peripheral);
                }
            }
            [Preserve]
            public override void UpdatedState(CBCentralManager central)
            {              
            }
        }
    }

我们将MasaMauiBluetoothService修改为静态类,
我们自定义的BluetoothDelegate 继承自CBCentralManagerDelegate,篇幅问题我们这里先只重写DiscoveredPeripheralUpdatedState,我们这次的演示不需要实现UpdatedState,但是这里的重写必须先放上去,否则调试过程会出现下面的报错

ObjCRuntime.ObjCException: 'Objective-C exception thrown. Name: NSInvalidArgumentException Reason: -[Masa_Blazor_Maui_Plugin_Bluetooth_MasaMauiBluetoothService_BluetoothDelegate centralManagerDidUpdateState:]: unrecognized selector sent to instance 0x284bfe200

另外有一点需要特别注意,这个UpdatedState方法我没有实现的代码,那么我就需要添加一个[Preserve],这样是为了防止链接器 在生成nuget包的时候把这个方法帮我优化掉。

在这里插入图片描述

实现发现附近设备功能,_eventWaitHandle和安卓一样,我这里只是实现了一个异步转同步方便直接通过Devices拿到结果,如果小伙伴不喜欢后期我会添加不阻塞的方式。
这里之所以可以Devices.ContainsDevices.Add是因为我们在BluetoothDevice类中实现了隐式转换
如下是iOS目录下BluetoothDevice.ios.cs的部分代码

    partial class BluetoothDevice
    {
        ...
        private BluetoothDevice(CBPeripheral peripheral)
        {
            _peripheral = peripheral;
        }

        public static implicit operator BluetoothDevice(CBPeripheral peripheral)
        {
            return peripheral == null ? null : new BluetoothDevice(peripheral);
        }

        public static implicit operator CBPeripheral(BluetoothDevice device)
        {
            return device._peripheral;
        }
        ...

ios扫描外围设备是通过scanForPeripherals
我们继续在MasaMauiBluetoothService添加一个扫描附件设备的方法,我们看一下Swift的文档

func scanForPeripherals(
    withServices serviceUUIDs: [CBUUID]?,
    options: [String : Any]? = nil
)

serviceUUIDs:代表需要过滤的服务UUID,类似安卓的scanFilter对象。
option:提供扫描的选项,我们这里用到了AllowDuplicatesKey,该值指定扫描是否应在不重复筛选的情况下运行
我们参照实现以下我们的PlatformScanForDevices方法

        private static async Task<IReadOnlyCollection<BluetoothDevice>> PlatformScanForDevices()
        {
            if (!_manager.IsScanning)
            {
                _manager.ScanForPeripherals(new CBUUID[] { }, new PeripheralScanningOptions
                {
                    AllowDuplicatesKey = true
                });

                await Task.Run(() => { _delegate.WaitOne(); });

                _manager.StopScan();
                _discoveredDevices = _delegate.Devices.AsReadOnly();
            }


            return _discoveredDevices;
        }

通过 _cbCentralManager.IsScanning来判断是否处于扫描状态,如果没有,那就就通过ScanForPeripherals扫描外围设备,扫描5秒之后(BluetoothDelegate 内部控制)通过StopScan停止扫描,并通过 _discoveredDevices 保存结果。
我们还需实现PlatformIsEnabledIsEnabledPlatformCheckAndRequestBluetoothPermission方法,用来在扫描之前检查蓝牙是否可用并且已经经过用户授权

        public static bool PlatformIsEnabledIsEnabled()
        {
            return _manager.State == CBManagerState.PoweredOn;
        }
        public static async Task<PermissionStatus> PlatformCheckAndRequestBluetoothPermission()
        {
            PermissionStatus status = await Permissions.CheckStatusAsync<BluetoothPermissions>();

            if (status == PermissionStatus.Granted)
                return status;

            if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
            {
                // Prompt the user to turn on in settings
                // On iOS once a permission has been denied it may not be requested again from the application
                return status;
            }

            status = await Permissions.RequestAsync<BluetoothPermissions>();
               
            return status;
        }
        private class BluetoothPermissions : Permissions.BasePlatformPermission
        {
            protected override Func<IEnumerable<string>> RequiredInfoPlistKeys
                =>
                    () => new string[] { "NSBluetoothAlwaysUsageDescription", "NSBluetoothPeripheralUsageDescription" };

            public override Task<PermissionStatus> CheckStatusAsync()
            {
                EnsureDeclared(); 
                return Task.FromResult(GetBleStatus());
            }
			
            private PermissionStatus GetBleStatus() //Todo:Needs to be replenished
            {
                var status = _cbCentralManager.State;
                return status switch
                {
                    CBManagerState.PoweredOn=> PermissionStatus.Granted,
                    CBManagerState.Unauthorized => PermissionStatus.Denied,
                    CBManagerState.Resetting => PermissionStatus.Restricted,
                    _ => PermissionStatus.Unknown,
                };
            }
        }

PlatformIsEnabledIsEnabled方法中通过 _cbCentralManager.State == CBManagerState.PoweredOn 来判断蓝牙是否可用。该状态一共有如下枚举,从字面意思很好理解
Unknown, //手机没有识别到蓝牙
Resetting, //手机蓝牙已断开连接
Unsupported, //手机蓝牙功能没有权限
Unauthorized, //手机蓝牙功能没有权限
PoweredOff,//手机蓝牙功能关闭
PoweredOn //蓝牙开启且可用

权限检查这里和安卓有一些区别,在重写的RequiredInfoPlistKeys方法中指定了需要检查的蓝牙权限,BasePlatformPermissionEnsureDeclared方法用来检查是否在Info.plist文件添加了需要的权限,GetBleStatus方法通过 _cbCentralManager 的状态,来检查授权情况。

我们在Masa.Blazor.Maui.Plugin.Bluetooth的根目录添加部分类MasaMauiBluetoothService.cs,向使用者提供ScanForDevicesAsync等方法,方法内部通过PlatformScanForDevices来调用具体平台的实现。

    public static partial class MasaMauiBluetoothService
    {
        private static IReadOnlyCollection<BluetoothDevice> _discoveredDevices;
        public static Task<IReadOnlyCollection<BluetoothDevice>> ScanForDevicesAsync()
        {
            return PlatformScanForDevices();
        }
        
        public static bool IsEnabled()
        {
            return PlatformIsEnabledIsEnabled();
        }

        public static async Task<PermissionStatus> CheckAndRequestBluetoothPermission()
        {
            return await PlatformCheckAndRequestBluetoothPermission();
        }
    }

使用

右键Masa.Blazor.Maui.Plugin.Bluetooth项目,点击打包,生成一个nuget包,在Masa.Blazor.Maui.Plugin.BlueToothSample项目中离线安装即可,代码的使用与安卓完全一样,只是权限配置方式不同
Masa.Blazor.Maui.Plugin.BlueToothSample项目的Platforms->iOS->Info.plist中添加蓝牙相关权限

	<key>NSBluetoothAlwaysUsageDescription</key>
	<string>App required to access Bluetooth</string>
	<key>NSBluetoothPeripheralUsageDescription</key>
	<string>App required to access Bluetooth</string>

NSBluetoothAlwaysUsageDescription对应iOS 13以上版本,对于iOS 13之前的版本,需要将NSBluetoothAlwaysUsageDescriptionNSBluetoothPeripheralUsageDescription同时添加。

蓝牙扫描的效果和安卓机是完全一样的,这里就不展示了。前文详情[https://www.cnblogs.com/MASA/p/16714453.html]

iOS调试及错误排查

目前在windows的vs环境调试MAUI的ios程序,是不需要mac电脑支持的,数据线连上后会显示一个本地设备,但是你仍然需要一个开发者账号,vs会调用apple开发者api自动帮你配置好需要的证书。

在这里插入图片描述

1、如果没有显示检查Xamarin->iOS设置,热重启是否开启

在这里插入图片描述

2、调试过程如果提示类似
Could not find executable for C:\Users\xxx\AppData\Local\Temp\hbjayi2h.ydn
找不到文件的情况,右键选择清理项目即可,如果无法解决手动删除bin和obj目录重试

3、调试过程如果app无故退出,排查一下考虑APP的启动和调试断点时间,iOS要求所有方法必须在17秒之内返回,否则iOS系统将停止该应用

4、调试过程出现Deploy Error: An Lockdown error occurred. The error code was "MuxError"的错误,请检查你的数据线,重新插拔或者更换原装线。

本文到此结束。

如果你对我们MASA感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

WeChat:MasaStackTechOps
QQ:7424099

标签:return,MASA,iOS,peripheral,低功耗,蓝牙,static,public
From: https://www.cnblogs.com/MASA/p/16776151.html

相关文章

  • #冲刺创作新星# #跟着小白一起学鸿蒙# [八] 蓝牙应用
    蓝牙简介蓝牙(Bluetooth)是一个短距离无线通信标准,用于在手机、计算机和其他电子设备之间通信。在Linux中权威的蓝牙协议栈实现是BlueZ。其本身自带了很多有用的工具,如blu......
  • 使用蓝牙内部32K调整精度
    蓝牙使用外部32K精度比较高,约为20ppm左右。使用内部32K误差约为百分之二,使用内部32768误差约为800ppm(万年历)使用外部32K需要消耗一颗晶振的物料,同时芯片的相应GPIO会被占......
  • TWS耳机蓝牙建连过程_HCI版本
    TWS耳机信息:EncoAir2手机信息:onePlus8 ColorOSV12.1其他准备工作:手机进入开发者模式,打开本地日志开关。可参考上一篇文章 https://www.cnblogs.com/YangARTuan/p/1......
  • 蓝牙应用的权限申请
    这是一个困扰了我一天的问题,出现的情况是:之前在D10.3下做蓝牙应用的程序,在低版本的安卓下是可以正常使用的。但D10.3不能支持高版本的安卓系统,所以就更换了D11进行测试。在......
  • TUF GAMING B550M-PLUS WIFI II 主板wifi和蓝牙问题
    华硕b550m无线网卡驱动装不上华硕b550重炮手wifi2驱动怎么打 前言:最近跟了自己十年的笔记本终于退役了。趁着活动组了一个台式电脑,碰到一些问题,百度没有解决,因此来记......
  • AX210导致蓝牙键盘卡顿,连续输入解决办法
    现象:导致蓝牙键盘卡顿,连键,蓝牙音箱卡顿解决方案:打开“设备管理器”,展开“蓝牙”,右击“英特尔(R)无线Bluetooth(R)”,选择“属性”,切换到“详细信息”选项卡,选择“设备实例......
  • Blazor组件自做九 : 使用JS隔离制作蓝牙打印组件(通用跨平台隔空打印小票/标签方案)
    各位,好久不见,这段时间事情太多了,一直没空更新文章,sosososorry.如果我告诉您网站能以安全和隐私保护的方式与附近的蓝牙设备进行通信,您会怎么想?如此一来,心率监测器、会......
  • 记一次开发蓝牙协议栈的应用方面的过程
    项目需要,需要开发一款蓝牙soc产品,选择了一款名为CMT4522的蓝牙soc,就是一个M0内核加上内部集成了蓝牙协议栈。网上找过这个相关资料,没找到,但有相似的产品,如奉加微的PHY6212,......
  • CH573F蓝牙从机(peripheral)例程讲解(二)
    在上一篇外设例程讲解中讲述了蓝牙从机的收发接口,这样可以快速的上手,那么接下来就讲解另一个重要设置,从机的广播。在peripheral例程中,一直是以50ms的周期进行广播,使用手机......
  • 解决蓝牙耳机突然声音失真的问题
    【问题描述】:我的蓝牙耳机是realmeBudsQ2,买了大概半年多,之前连电脑的都挺好,但是某一天我弄了手机-电脑共享屏幕之后电脑再连蓝牙耳机就出现了不时声音失真的情况。【......