C#实现笔记本自带蓝牙与汇承HC-08(BLE)蓝牙模块通讯_电脑和hc08蓝牙模块远程通信-程序员宅基地

技术标签: c#  蓝牙  

问题背景

因最近新项目需要与蓝牙仪器通讯,起初以为很简单,搜索一下C#蓝牙都是调用InTheHand.Net这个第三方库,但是采购的时HC-08模块,低功耗模块。发现无法使用这个库,网上资料也很少,客服给了安卓手机调试软件,但对Windows桌面也不是很懂,但是给了个win10应用商店,(最后我发现这个应用里管这叫BLE模块,汗…也是涨姿势了)。本着既然手机和安卓都能通讯的原则,我想肯定笔记本能与蓝牙模块通讯上,就开始头秃般的研究了…
客服给的说明

感谢

我喜欢把感谢放在前面,互联网就是应该互相学习帮助!感谢这几位大师和微软的库,基本解决了当前的问题,附出来供大家参考少走弯路。

BLE介绍

具体不介绍了,总结下就是对安卓等手机便携端很友好,电脑端可能没人去弄,有兴趣看看
感谢SamSamCai
BLE入门https://blog.csdn.net/ccslff/article/details/51599148

参考代码

Jerrt-J C# BLE蓝牙开发之使用Windows.Devices.Bluetooth获取小米体重秤的体重(特别感谢)
【https://blog.csdn.net/flj135792468/article/details/115480703】
code_long C# ble 4.0 低能耗 蓝牙交互 (特别特别感谢)
【https://blog.csdn.net/code_long/article/details/105636398】
美渊科技 Win10 平台C#与低功耗蓝牙BLE设备通信案例(特别感谢)
【https://blog.csdn.net/shengfakun1234/article/details/110928783】
鞪林 windows BLE编程 net winform 连接蓝牙4.0
【https://www.cnblogs.com/webtojs/p/9675956.html】
A_DUO C#控制台使用低功耗蓝牙BLE进行多设备通信小结(解决了一些细节问题)
【https://blog.csdn.net/A_DUO/article/details/92841314】

参考业务逻辑

艾希红红 C# ble 低功耗蓝牙读写 C#蓝牙(桌面应用)
【https://www.jianshu.com/p/0151c2bacb66】
肖邦kaka 蓝牙BLE(BlueTooth BLE)入门及爬坑指南
【https://www.jianshu.com/p/d991f0fdec63】
WCH_SoftGroup Windows端用于开发低功耗蓝牙项目的DLL
【https://blog.csdn.net/WCH_TechGroup/article/details/103778978】(一)
【https://blog.csdn.net/WCH_TechGroup/article/details/103892909】(二)
丶麦芽 低功耗蓝牙Ble的详细使用流程
【https://www.jianshu.com/p/3d4bfdc72384】

参考库和例程

感谢微软相关库
【https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth?view=winrt-19041】WIN10蓝牙库
【https://docs.microsoft.com/zh-cn/windows/uwp/devices-sensors/gatt-client】蓝牙业务逻辑
【https://docs.microsoft.com/en-us/windows/win32/api/bluetoothleapis/nf-bluetoothleapis-bluetoothgattgetcharacteristics】C++头
【https://github.com/microsoft/Windows-universal-samples/tree/b1cb20f191d3fd99ce89df50c5b7d1a6e2382c01/Samples/BluetoothLE】官方例程
【https://github.com/kaka10xiaobang/BlueToothBLE】kaka10xiaobang的例子

程序思路

参考了以上的这些资料,低功耗蓝牙大致连接思路就有了。我们就按照我的调试程序倒推。
程序图

  1. 搜索蓝牙,我们选中我要通讯的蓝牙
  2. 匹配低功耗蓝牙(具体机制我也没研究 抱歉)
  3. 获取服务,选择需要使用的服务(比如HC-08模块ffe0那个就是读写用的)
  4. 获取特征,选择需要使用的特征(比如HC-08模块ffe1那个就是读写用的这两个估计都差不多应该是遵循协议走的)
  5. 获取操作,具体下面会细讲,获取之后你就可以知道你这个模块能读还是能写,可以进行操作了
  6. 最后才是发送和接收数据

大体就是这么个流程

难点介绍

借鉴别人的代码总是不会一帆风顺。

问题1平台和windows sdk 的组件

软件开发环境是WIN10+vs2019+Framework 4.7.2
因为使用到了windows sdk ,必须是win10下才能正常运行,根据之前大伙的方法,部分出现如下图问题缺SDK

我重新虚拟机安装的win10+vs2017没有出现此问题,但我的笔记本WIN10教育+VS2019出现了此问题,我直接把C盘的.winmd拷贝出来直接浏览,就能添加添加引用
这里也要修改下别忘了

 <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
 <TargetPlatformVersion>10.0</TargetPlatformVersion>
 <FileAlignment>512</FileAlignment>
 <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>

卸载项目
添加

问题2优化速度和更改同步

感谢 美渊科技,基本就是按照他的《Win10 平台C#与低功耗蓝牙BLE设备通信案例》来的
修改发现设备慢

     public void StartBleDeviceWatcher()
        {
    
            watcher = new BluetoothLEAdvertisementWatcher();

            watcher.ScanningMode = BluetoothLEScanningMode.Active;

            // only activate the watcher when we're recieving values >= -80
            watcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;

            // stop watching if the value drops below -90 (user walked away)
            watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;

            // register callback for when we see an advertisements
            watcher.Received += OnAdvertisementReceived;

            // wait 5 seconds to make sure the device is really out of range
            watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
            watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);

            // starting watching for advertisements
            watcher.Start();
            string msg = "自动发现设备中..";

            this.MessAgeChanged(MsgType.NotifyTxt, msg);
        }

        private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
        {
    
            BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress).Completed = async (asyncInfo, asyncStatus) =>
            {
    
                if (asyncStatus == AsyncStatus.Completed)
                {
    
                    if (asyncInfo.GetResults() == null)
                    {
    
                        //this.MessAgeChanged(MsgType.NotifyTxt, "没有得到结果集");
                    }
                    else
                    {
    
                        BluetoothLEDevice currentDevice = asyncInfo.GetResults();

                        Boolean contain = false;
                        foreach (BluetoothLEDevice device in DeviceList)//过滤重复的设备
                        {
    
                            if (device.DeviceId == currentDevice.DeviceId)
                            {
    
                                contain = true;
                            }
                        }
                        if (!contain)
                        {
    
                            byte[] _Bytes1 = BitConverter.GetBytes(currentDevice.BluetoothAddress);
                            Array.Reverse(_Bytes1);

                            this.DeviceList.Add(currentDevice);
                            this.MessAgeChanged(MsgType.NotifyTxt, "发现设备:" + currentDevice.Name + "  address:" + BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower());
                            this.DeviceWatcherChanged(MsgType.BleDevice, currentDevice);
                        }
                    }

                }
            };
        }

修改获取服务码和特征码同步,另外为了方便加了筛选HC-08的码,如果要更换模块需要对应修改

  /// <summary>
        /// 获取蓝牙服务
        /// </summary>
        public async void FindService()
        {
    
            this.CurrentDevice.GetGattServicesAsync().Completed = async (asyncInfo, asyncStatus) =>
            {
    
                if (asyncStatus == AsyncStatus.Completed)
                {
    
                    var services = asyncInfo.GetResults().Services;
                    this.MessAgeChanged(MsgType.NotifyTxt, "GattServices size=" + services.Count);
                    foreach (GattDeviceService ser in services)
                    {
    
                        this.GattDeviceServiceAdded(ser);
                    }

                }
            };
        }

        /// <summary>
        /// 获取特性
        /// </summary>
       // [Obsolete]
        public async void FindCharacteristic(GattDeviceService gattDeviceService)
        {
    
            gattDeviceService.GetCharacteristicsAsync().Completed = async (asyncInfo, asyncStatus) =>
            {
    
                // gattDeviceService.GetCharacteristicsAsync();
                if (asyncStatus == AsyncStatus.Completed)
                {
    
                    var character = asyncInfo.GetResults().Characteristics;
                    this.MessAgeChanged(MsgType.NotifyTxt, "GattCharacteristics size=" + character.Count);
                    foreach (GattCharacteristic c in character)
                    {
    
                        this.CharacteristicAdded(c);
                    }
                    this.MessAgeChanged(MsgType.NotifyTxt, "获取特征码收集完毕");

                }
            };
        }

对应窗体事件修改

  /// <summary>
        /// 获取蓝牙服务列表
        /// </summary>
        private void BleCore_GattDeviceServiceAdded(Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService gattDeviceService)
        {
    
            RunAsync(() =>
            {
    
                try
                {
    
                    this.cmbServer.Items.Add(gattDeviceService.Uuid.ToString());
                    //提高效率 匹配到HC08服务码
                    if (cmbServer.SelectedIndex == -1)
                    {
    
                        if (gattDeviceService.Uuid.ToString().Substring(4, 4) == "ffe0")
                        {
    
                            cmbServer.SelectedIndex = cmbServer.Items.IndexOf(gattDeviceService.Uuid.ToString());
                        }
                    }
                    this.GattDeviceServices.Add(gattDeviceService);
                    this.btnFeatures.Enabled = true;
                }
                catch
                {
     }
            });
        }

        /// <summary>
        /// 获取特征列表
        /// </summary>
        private void BleCore_CharacteristicAdded(Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic gattCharacteristic)
        {
    
            RunAsync(() =>
            {
    
                try
                {
    
                    this.cmbFeatures.Items.Add(gattCharacteristic.Uuid);
                    //同理提高效率 匹配到HC08特征码
                    if (cmbFeatures.SelectedIndex == -1)
                    {
    
                        if (gattCharacteristic.Uuid.ToString().Substring(4, 4) == "ffe1")
                        {
    
                            cmbFeatures.SelectedIndex = cmbFeatures.Items.IndexOf(gattCharacteristic.Uuid);
                        }
                    }
                    this.GattCharacteristics.Add(gattCharacteristic);
                    this.btnOpteron.Enabled = true;
                }
                catch
                {
     }
            });
        }

问题3蓝牙模块获取操作

这一步自己研究了许多资料,发现不同的模块获取的内容不同,之前几位博主没有很详细的介绍操作获取,其实这反映了这个蓝牙模块的功能(用户能对它进行什么样的操作),期初只能粗暴加断点调试出来。

        /// <summary>
        /// 获取操作
        /// </summary>
        /// <returns></returns>
        public async Task SetOpteron(GattCharacteristic gattCharacteristic)
        {
    
            byte[] _Bytes1 = BitConverter.GetBytes(this.CurrentDevice.BluetoothAddress);
            Array.Reverse(_Bytes1);
            this.CurrentDeviceMAC = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();

            string msg = "正在连接设备<" + this.CurrentDeviceMAC + ">..";
            this.MessAgeChanged(MsgType.NotifyTxt, msg);

            if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Write)
            {
    
                this.CurrentWriteCharacteristic = gattCharacteristic;
            }
            if (gattCharacteristic.CharacteristicProperties == GattCharacteristicProperties.Notify)
            {
    
                this.CurrentNotifyCharacteristic = gattCharacteristic;
            }
            if ((uint)gattCharacteristic.CharacteristicProperties == 26)
            {
    

            }
            //这里是难点 返回一个enum枚举
            if (gattCharacteristic.CharacteristicProperties == (GattCharacteristicProperties.Write | GattCharacteristicProperties.Notify))
            {
    
                this.CurrentWriteCharacteristic = gattCharacteristic;
                this.CurrentNotifyCharacteristic = gattCharacteristic;
                this.CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;
                this.CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;
                this.CurrentDevice.ConnectionStatusChanged += this.CurrentDevice_ConnectionStatusChanged;
                await this .EnableNotifications(CurrentNotifyCharacteristic);
            }
            if (gattCharacteristic.CharacteristicProperties == (GattCharacteristicProperties.Notify | GattCharacteristicProperties.Read | GattCharacteristicProperties.WriteWithoutResponse))
            {
    
                this.CurrentWriteCharacteristic = gattCharacteristic;

                this.CurrentNotifyCharacteristic = gattCharacteristic;
                this.CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;
                this.CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;
                await this.EnableNotifications(CurrentNotifyCharacteristic);
            }
        }

加断点我们看到,汇承-08模块获取到的操作是Read |Notify |WriteWithoutResponse,并不是单单的Write 获取操作断点
所以实际应用要在这里进行修改。

问题4

剩下因为时间原因,没有解决一部分的警告和数据richTextBox上显示,所以你点击数据收发,功能没有做。

分享测试程序

开发不易,程序给大家参考 源代码就斗胆支持下博主。

调试程序

https://download.csdn.net/download/Vishera/18593913
欢迎指导

源代码

https://download.csdn.net/download/Vishera/18595325

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Vishera/article/details/116592725

智能推荐

(转载)java synchronized详解-程序员宅基地

文章浏览阅读422次。java synchronized详解记下来,很重要。Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块..._java make synchronized invalid

Android 多种投屏神器(Vysor,Total Control,scrcpy )-程序员宅基地

文章浏览阅读1.9w次,点赞6次,收藏32次。一,Vysor1,Vysor特点及优点特点或者说优点:极强的跨平台性能,Mac、Windows、Linux系统上都可以用;免费;无需Android系统Root 即可玩转电脑控制Android 设备;2,官网https://www.vysor.io/3,使用3.1,Win客户端(建议使用)直接下载windows客户端安装,运行就可以使用,使用也很简单,可以通过局域网和数据线连接Android设备,然后实现投屏;3.2,Chrome浏览器3.2.1,从官网Download进入_total control

Java反射机制_object o3 = m; object o4 = n; system.out.println(o-程序员宅基地

文章浏览阅读144次。概念:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。类的加载机制:new 对象()1.JVM加载对象.class文件1.1JVM在硬盘找对象.class文件并读取到内存中1.2JVM自动创建..._object o3 = m; object o4 = n; system.out.println(o3 == o4)

NDK各个版本,待后续更新_android-ndk-r10e和ndk 29差别-程序员宅基地

文章浏览阅读414次。ndk_r15c (July 2017)Windows 32-bit :https://dl.google.com/android/repository/android-ndk-r15c-windows-x86.zipWindows 64-bit :https://dl.google.com/android/repository/android-ndk-r15c-windows-x86_64.zipMac OS X :https://dl.google.com/android/repositor_android-ndk-r10e和ndk 29差别

Linux常用命令—grep_linux命令+grep-程序员宅基地

文章浏览阅读4.1k次,点赞3次,收藏16次。简介grep命令(Global Regular Expression Print)是 Linux系统中一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来 。grep 是linux中最为常用的三大文本(awk,sed,grep)处理工具之一,所以有必要掌握其用法。grep家族总共有三个成员构成:grep、egrep、fgrep。使用格式grep [选项] ..._linux命令+grep

关于the selection cannot be run on any server错误的问题,如何快速的解决。-程序员宅基地

文章浏览阅读3.9w次,点赞29次,收藏121次。最近在导入外来项目时,遇到了一个难题,就是出现了图中的错误。the selection cannot be run on any server(无法在任何服务器上运行所选内容)这个错误的原因在于Dynamic Web Module 的版本与server不匹配。Dynamic Web Module的版本可以通过右键项目名-&gt;properties-&gt;Project Facets可以..._the selection cannot be run on any server

随便推点

Cookie,会话,令牌-程序员宅基地

文章浏览阅读804次。会话cookieAre you new to web-development, feeling confused with different Web Storage elements? 您是Web开发的新手,对不同的Web存储元素感到困惑吗? If yes, then you are at the right place This article will give you a brief e..._会话和令牌

Linux查找文本中指定字符的小技巧_linux查找指定字符后的数据-程序员宅基地

文章浏览阅读3.6k次。在Linux的vi编辑器中,如果要查看指定文件中的某项内容,由于内容过于庞大,可以打开vi编辑器后再打一个【/】,括号中间的字符,然后输入你要查找的字符这样就可以找到你需要的字符了,方便我们查看大容量的日志文件。_linux查找指定字符后的数据

编译chromium 总结_<includepath>$(includepath);$(dxsdk_dir)include</i-程序员宅基地

文章浏览阅读2.5k次。编译chromium 总结http://www.chromium.org/developers/how-tos/build-instructions-windows这是官网的详细地址,但我只用他的说明还不够 可以参考这篇文章http://blog.sina.com.cn/s/blog_41608ead0101578b.htmlwin7+vs2010+vs2010SP1+DIR_$(includepath);$(dxsdk_dir)include

Struts-笔记-2-程序员宅基地

文章浏览阅读61次。2 .搭建Struts开发环境 2.1 搭建环境l 导入jar 包。 Add Library 导入jar包 l 建立一个配置文件:struts-config.xml &lt...

【杭电oj】1872 - 稳定排序(结构体排序)_wygoj-程序员宅基地

文章浏览阅读695次。稳定排序Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4632 Accepted Submission(s): 1802Problem Description大家都知道,快速排序是不稳定的排序方法。_wygoj

Java中 GC是什么_gc钱包是干嘛的?-程序员宅基地

文章浏览阅读2.3w次,点赞9次,收藏17次。Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代 码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。电脑的内存大小的不变的,当我们使用对象的时候,如使用New关键字的时候,就会在内存中生产一个对象,但是我们在使用JAVA开发的时候,当一个对象使用完_gc钱包是干嘛的?

推荐文章

热门文章

相关标签