unity 使用蓝牙通讯(PC版,非安卓)

BlueTooth in pc with unity

最近接到的需求是在unity里面开发蓝牙功能,其实一开始我并不慌,因为据我所知,unity有丰富的插件可以使用,但是问题随之而来
1.unity里面无法直接与蓝牙通讯(后来找到了开启runtime一类的东西,但是我找了半天也没找到在哪里可以打开)
2.引入dll通过dll与蓝牙通讯,包括去微软的官网下载c++编译,但是编译过程中种种问题.而我对于c++这一套也不精通.也是放弃了
3.github上找到一个封装好的BLE的一个 这是网址 但是在我的测试中发现 会有搜不到我的蓝牙设备的问题.并且即使连上了,发出的消息也没有回应.所以也是放弃了

于是只能通过一个比较笨的办法,将蓝牙通讯在winform中或者是wpf中,然后将unity程序放在winform或者是wpf的容器中,二者通过udp协议连接传输数据.最后的效果如下:

运行界面

那么如果是这种办法,那就变得简单的多了.winform开发不在话下.unity也是我熟悉的.其中winform支持原生开发,也就是说不用下载任何dll与插件,用到的只是windows.winmd,而这个windows系统是自带的.

将winmd类型文件引入后,编写开发工具类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography;
using Windows.Storage.Streams;namespace Bluetooth
{internal class BleCore{private Boolean asyncLock = false;/// <summary>/// 搜索蓝牙设备对象/// </summary>private BluetoothLEAdvertisementWatcher watcher;/// <summary>/// 当前连接的蓝牙设备/// </summary>public BluetoothLEDevice CurrentDevice { get; set; }/// <summary>/// 特性通知类型通知启用/// </summary>private const GattClientCharacteristicConfigurationDescriptorValue CHARACTERISTIC_NOTIFICATION_TYPE = GattClientCharacteristicConfigurationDescriptorValue.Notify;/// <summary>/// 存储检测到的设备/// </summary>private List<BluetoothLEDevice> DeviceList = new List<BluetoothLEDevice>();/// <summary>/// 搜索蓝牙设备委托/// </summary>public delegate void DeviceScanEvent(BluetoothLEDevice bluetoothLEDevice);/// <summary>/// 搜索蓝牙事件/// </summary>public event DeviceScanEvent DeviceScan;/// <summary>/// 提示信息委托/// </summary>public delegate void MessAgeLogEvent(MsgType type, string message, byte[] data = null);/// <summary>/// 提示信息事件/// </summary>public event MessAgeLogEvent MessAgeLog;/// <summary>/// 接收通知委托/// </summary>public delegate void ReceiveNotificationEvent(GattCharacteristic sender, byte[] data);/// <summary>/// 接收通知事件/// </summary>public event ReceiveNotificationEvent ReceiveNotification;/// <summary>/// 获取服务委托/// </summary>public delegate void DeviceFindServiceEvent(DeviceService deviceService);/// <summary>/// 获取服务事件/// </summary>public event DeviceFindServiceEvent DeviceFindService;/// <summary>/// 蓝牙状态委托/// </summary>public delegate void DeviceConnectionStatusEvent(BluetoothConnectionStatus status);/// <summary>/// 蓝牙状态事件/// </summary>public event DeviceConnectionStatusEvent DeviceConnectionStatus;/// <summary>/// 当前连接的蓝牙Mac/// </summary>private string CurrentDeviceMAC { get; set; }public BleCore(){}/// <summary>/// 搜索蓝牙设备/// </summary>public void StartBleDeviceWatcher(){watcher = new BluetoothLEAdvertisementWatcher();watcher.ScanningMode = BluetoothLEScanningMode.Active;//只有在接收到数值大于等于 -80 的情况下才激活监视器watcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;// 如果数值低于 -90(用户离开),则停止监测watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;// 注册回调函数,以便在我们看到广播时触发(执行)相关操作watcher.Received += OnAdvertisementReceived;// 等待 5 秒钟以确保设备确实超出范围watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);// starting watching for advertisementsthis.DeviceList.Clear();watcher.Start();string msg = "开始搜索蓝牙设备...";this.MessAgeLog(MsgType.NotifyTxt, msg);}/// <summary>/// 停止搜索蓝牙/// </summary>public void StopBleDeviceWatcher(){this.watcher.Stop();}private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs){//this.MessAgeChanged(MsgType.NotifyTxt, "发现设备FR_NAME:"+ eventArgs.Advertisement.LocalName + "BT_ADDR: " + eventArgs.BluetoothAddress);BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress).Completed = (asyncInfo, asyncStatus) =>{if (asyncStatus == AsyncStatus.Completed){if (asyncInfo.GetResults() != null){BluetoothLEDevice currentDevice = asyncInfo.GetResults();Boolean contain = false;foreach (BluetoothLEDevice device in DeviceList)//过滤重复的设备{if (device.BluetoothAddress == currentDevice.BluetoothAddress){contain = true;}}if (!contain){byte[] _Bytes1 = BitConverter.GetBytes(currentDevice.BluetoothAddress);Array.Reverse(_Bytes1);this.DeviceList.Add(currentDevice);string str;if (currentDevice.DeviceInformation.Name != ""){str = "发现设备:" + currentDevice.DeviceInformation.Name + " - MAC: \r\n" +BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToUpper() + " - ID: /r/n" + currentDevice.DeviceInformation.Id;}//舍弃没有名字的设备(匿名设备)else{//    str = "发现设备:" + BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToUpper();return;}this.MessAgeLog(MsgType.NotifyTxt, str);this.DeviceScan(currentDevice);}}}};}/// <summary>/// 匹配/// </summary>/// <param name="Device"></param>public void StartMatching(BluetoothLEDevice Device){this.CurrentDevice?.Dispose();this.CurrentDevice = Device;Connect();FindService();}/// <summary>/// 获取蓝牙服务/// </summary>public async void FindService(){this.MessAgeLog(MsgType.NotifyTxt, "开始获取服务列表");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){FindCharacteristic(ser);}}};}/// <summary>/// 获取特性/// </summary>public async void FindCharacteristic(GattDeviceService gattDeviceService){IAsyncOperation<GattCharacteristicsResult> result = gattDeviceService.GetCharacteristicsAsync();result.Completed = async (asyncInfo, asyncStatus) =>{if (asyncStatus == AsyncStatus.Completed){var characters = asyncInfo.GetResults().Characteristics;List<GattCharacteristic> characteristics = new List<GattCharacteristic>();foreach (GattCharacteristic characteristic in characters){characteristics.Add(characteristic);this.MessAgeLog(MsgType.NotifyTxt, "服务UUID:" + gattDeviceService.Uuid.ToString() + " --- 特征UUID:" + characteristic.Uuid.ToString());}DeviceService deviceService = new DeviceService();deviceService.gattDeviceService = gattDeviceService;deviceService.gattCharacteristic = characteristics;this.DeviceFindService(deviceService);}};}/// <summary>/// 获取操作/// </summary>/// <returns></returns>public async Task SetNotify(GattCharacteristic gattCharacteristic){GattCharacteristic CurrentNotifyCharacteristic;if ((gattCharacteristic.CharacteristicProperties & GattCharacteristicProperties.Notify) != 0){CurrentNotifyCharacteristic = gattCharacteristic;CurrentNotifyCharacteristic.ProtectionLevel = GattProtectionLevel.Plain;CurrentNotifyCharacteristic.ValueChanged += Characteristic_ValueChanged;await this.EnableNotifications(CurrentNotifyCharacteristic);}}/// <summary>/// 连接蓝牙/// </summary>/// <returns></returns>private async Task Connect(){byte[] _Bytes1 = BitConverter.GetBytes(this.CurrentDevice.BluetoothAddress);Array.Reverse(_Bytes1);this.CurrentDeviceMAC = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();string msg = "正在连接设备:" + this.CurrentDeviceMAC.ToUpper() + " ...";this.MessAgeLog(MsgType.NotifyTxt, msg);this.CurrentDevice.ConnectionStatusChanged += this.CurrentDevice_ConnectionStatusChanged;}/// <summary>/// 搜索到的蓝牙设备/// </summary>/// <returns></returns>private async Task Matching(string Id){try{BluetoothLEDevice.FromIdAsync(Id).Completed = async (asyncInfo, asyncStatus) =>{if (asyncStatus == AsyncStatus.Completed){BluetoothLEDevice bleDevice = asyncInfo.GetResults();this.DeviceList.Add(bleDevice);this.DeviceScan(bleDevice);this.CurrentDevice = bleDevice;FindService();}};}catch (Exception e){string msg = "没有发现设备" + e.ToString();this.MessAgeLog(MsgType.NotifyTxt, msg);this.StartBleDeviceWatcher();}}/// <summary>/// 主动断开连接/// </summary>/// <returns></returns>public void Dispose(){CurrentDeviceMAC = null;CurrentDevice?.Dispose();CurrentDevice = null;MessAgeLog(MsgType.NotifyTxt, "主动断开连接");}private void CurrentDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args){this.DeviceConnectionStatus(sender.ConnectionStatus);if (sender.ConnectionStatus == BluetoothConnectionStatus.Disconnected && CurrentDeviceMAC != null){string msg = "设备已断开,自动重连...";MessAgeLog(MsgType.NotifyTxt, msg);if (!asyncLock){asyncLock = true;this.CurrentDevice.Dispose();this.CurrentDevice = null;SelectDeviceFromIdAsync(CurrentDeviceMAC);}}else{string msg = "设备已连接!";MessAgeLog(MsgType.NotifyTxt, msg);}}/// <summary>/// 按MAC地址直接组装设备ID查找设备/// </summary>public async Task SelectDeviceFromIdAsync(string MAC){CurrentDeviceMAC = MAC;CurrentDevice = null;BluetoothAdapter.GetDefaultAsync().Completed = async (asyncInfo, asyncStatus) =>{if (asyncStatus == AsyncStatus.Completed){BluetoothAdapter mBluetoothAdapter = asyncInfo.GetResults();byte[] _Bytes1 = BitConverter.GetBytes(mBluetoothAdapter.BluetoothAddress);//ulong转换为byte数组Array.Reverse(_Bytes1);string macAddress = BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower();string Id = "BluetoothLE#BluetoothLE" + macAddress + "-" + MAC;await Matching(Id);}};}/// <summary>/// 设置特征对象为接收通知对象/// </summary>/// <param name="characteristic"></param>/// <returns></returns>public async Task EnableNotifications(GattCharacteristic characteristic){if (CurrentDevice.ConnectionStatus != BluetoothConnectionStatus.Connected){this.MessAgeLog(MsgType.NotifyTxt, "蓝牙未连接!");return;}characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(CHARACTERISTIC_NOTIFICATION_TYPE).Completed = async (asyncInfo, asyncStatus) =>{Console.WriteLine("asyncStatus:" + asyncStatus);if (asyncStatus == AsyncStatus.Completed){GattCommunicationStatus status = asyncInfo.GetResults();asyncLock = false;string msg = "Notify(" + characteristic.Uuid.ToString() + "):" + status;this.MessAgeLog(MsgType.NotifyTxt, msg);}else{Console.WriteLine(asyncInfo.ErrorCode.ToString());}};}/// <summary>/// 接受到蓝牙数据/// </summary>private void Characteristic_ValueChanged(GattCharacteristic characteristic, GattValueChangedEventArgs args){byte[] data;CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out data);string str = System.Text.Encoding.UTF8.GetString(data);this.ReceiveNotification(characteristic, data);this.MessAgeLog(MsgType.BleData, "收到数据(" + characteristic.Uuid.ToString() + "):" + str);}/// <summary>/// 发送数据接口/// </summary>/// <returns></returns>public async Task Write(GattCharacteristic writeCharacteristic, byte[] data){if (writeCharacteristic != null){string str = "发送数据(" + writeCharacteristic.Uuid.ToString() + "):" + BitConverter.ToString(data);this.MessAgeLog(MsgType.BleData, str, data);IAsyncOperation<GattCommunicationStatus> async = writeCharacteristic.WriteValueAsync(CryptographicBuffer.CreateFromByteArray(data), GattWriteOption.WriteWithResponse);async.Completed = async (asyncInfo, asyncStatus) =>{if (asyncStatus == AsyncStatus.Completed){this.MessAgeLog(MsgType.BleData, "数据发送成功!");}else{this.MessAgeLog(MsgType.BleData, "数据发送失败:" + asyncInfo.ErrorCode.ToString());}};}}}public enum MsgType{NotifyTxt,BleData,BleDevice}public class DeviceService{public GattDeviceService gattDeviceService;public List<GattCharacteristic> gattCharacteristic;}
}

然后对于页面的按钮进行赋值,并将unity开发的exe放在合适的位置,其中通讯部分只需要简单的通讯即可,不需要考虑什么分包问题.因为本机通讯很稳定.
winform部分的udp:

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;namespace VincentUDP
{public class UdpServer{private UdpClient udpClient;private IPEndPoint remoteEndPoint;public void Init(){int port = 8888; // 选择一个端口udpClient = new UdpClient();remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port); // 目标IP和端口}public void Send(byte[] message){// byte[] data = Encoding.UTF8.GetBytes(message);udpClient.Send(message, message.Length, remoteEndPoint);}public void Close(){udpClient.Close();}}
}

unity接收部分:

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using UnityEngine;public class RecvUDPMsg : MonoBehaviour
{private UdpClient udpClient;//消息队列private Queue queue = new Queue();private void Start(){// 初始化UDP客户端udpClient = new UdpClient(8888); // 监听的端口StartReceiving();}private void StartReceiving(){udpClient.BeginReceive(ReceiveCallback, null);}private void ReceiveCallback(IAsyncResult ar){IPEndPoint remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);byte[] receivedBytes = udpClient.EndReceive(ar, ref remoteIpEndPoint);// string receivedText = Encoding.UTF8.GetString(receivedBytes);//Debug.Log("Received: " + receivedText);queue.Enqueue(receivedBytes);// 继续监听StartReceiving();}private void OnDestroy(){// 关闭UDP客户端udpClient.Close();}
}

那么最后的运行如下:
最终效果
最后附上下载地址: 点击这里
PS:如果下载不了(非csdn会员)请私信我.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/80659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL中的意向锁 + next-key锁 + 间隙锁

引言 在数据库并发控制中&#xff0c;锁机制是保障数据一致性和隔离性的核心手段。MySQL中意向锁、间隙锁以及next-key锁等复杂锁类型&#xff0c;旨在协调表级锁与行级锁之间的关系&#xff0c;防止数据的脏读、不可重复读和幻读现象&#xff0c;尤其是在可重复读隔离级别下发…

机器学习 数据集

数据集 1. scikit-learn工具介绍1.1 scikit-learn安装1.2 Scikit-learn包含的内容 2 数据集2.1 sklearn玩具数据集介绍2.2 sklearn现实世界数据集介绍2.3 sklearn加载玩具数据集示例1&#xff1a;鸢尾花数据示例2&#xff1a;分析糖尿病数据集 2.4 sklearn获取现实世界数据集示…

Linux-c语言串口程序

c语言串口程序 // C library headers #include <stdio.h> #include <string.h>// Linux headers #include <fcntl.h> // Contains file controls like O_RDWR #include <errno.h> // Error integer and strerror() function #include <termios.h&g…

TCP IP

TCP/IP 通信协议&#xff0c;不是单一协议&#xff0c;是一组协议的集合 TCP IP UDP 1.建立链接 三次握手 第一步&#xff1a;客户端发送一个FIN报文&#xff0c;SEQX,等待服务器回应 第二步&#xff1a;服务器端受到&#xff0c;发送ackx1,seqy, 等待客户端回应 第三步&am…

用uniapp在微信小程序实现画板(电子签名)功能,使用canvas实现功能

效果&#xff1a; 功能&#xff1a;实现重签 退出 保存 等功能 解决的问题: 电子签名画布抖动问题解 注意&#xff1a; 保存的时候上传到自己的服务器地址&#xff0c;后端返回图片地址 代码&#xff1a; <template><view><view class"signature&qu…

机器学习经典算法:用决策树原理优化新能源汽车续航能力

🔥 “用决策树重构新能源车能量大脑!算法推导+代码实战全解,续航暴增15%” 决策树算法就像我们生活中做决策的 “流程指南”,通过层层判断得出最终结论。比如你去超市买水果,站在琳琅满目的货架前,就不自觉地用上了决策树思维。首先,你可能会想 “今天想吃酸的还是甜的…

【Unity中的数学】—— 四元数

一、四元数的定义&#x1f60e; 四元数是一种高阶复数&#xff0c;是一个四维空间的概念&#xff0c;相对于复数的二维空间。它可以表示为 q s i x j y k z q s ix jy kz qsixjykz&#xff0c;其中 s s s、 x x x、 y y y、 z z z 都是实数&#xff0c;并且满足 i …

macOS 15.4.1 Chrome不能访问本地网络

前言 最近使用macmini m4&#xff0c;自带macOS15系统&#xff0c;对于开发者简直是一言难尽&#xff0c;Chrome浏览器的本地网络有bug&#xff0c;可以访问本机&#xff0c;但是不能访问路由器上的其他机器&#xff0c;路由器提供的页面也不能访问&#xff0c;如下是折腾解决…

浏览器刷新结束页面事件,调结束事件的接口(vue)

浏览器刷新的时候&#xff0c;正在进行中的事件结束掉&#xff0c;在刷新浏览器的时候做一些操作。 如果是调接口&#xff0c;就不能使用axios封装的接口&#xff0c;需要使用原生的fetch。 找到公共的文件App.vue 使用window.addEventListener(‘beforeunload’, function (e…

TCP/IP 模型每层的封装格式

TCP/IP 模型是一个四层网络架构&#xff0c;每一层在数据传输时都会对数据进行封装&#xff0c;添加相应的头部&#xff08;和尾部&#xff09;信息。以下是各层的封装格式及关键字段说明&#xff1a; 1. 应用层&#xff08;Application Layer&#xff09; 封装格式&#xff1a…

【行业深度解析】什么是马甲包?

在 Android 应用分发和增长运营的实践中&#xff0c;“马甲包” 是一个常被提及的策略术语。特别是在 Google Play 平台上&#xff0c;许多开发者或运营团队出于营销、风险分摊或生态布局等原因&#xff0c;会选择通过发布“马甲包”来实现多元化的业务拓展。 然而&#xff0c…

谷歌与微软的AI战争:搜索、云服务与生态布局

谷歌与微软的AI战争&#xff1a;搜索、云服务与生态布局 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 谷歌与微软的AI战争&#xff1a;搜索、云服务与生态布局摘要引言技术路线对比1. AI基础设施&#xff1a;算力…

uniapp自定义导航栏搭配插槽

<uni-nav-bar dark :fixed"true" shadow background-color"#007AFF" left-icon"left" left-text"返回" clickLeft"back"><view class"nav-bar-title">{{ navBarTitle }}</view><block v-slo…

无人机飞控算法开发实战:从零到一构建企业级飞控系统

简介 无人机飞控算法是实现稳定飞行和精确控制的核心技术,涉及飞行动力学建模、传感器数据处理、状态估计和控制策略等多个环节。本实战指南将系统讲解四旋翼无人机飞控算法的开发流程,包括飞行动力学模型建立、传感器校准与数据融合、主流控制算法实现(PID、ADRC、EKF)以…

p2p虚拟服务器

ZeroTier Central ✅ 推荐工具&#xff1a;ZeroTier&#xff08;免费、稳定、跨平台&#xff09; ZeroTier 可以帮你把多台设备&#xff08;无论是否跨网&#xff09;加入一个虚拟局域网&#xff0c;彼此间可以像在同一个 LAN 中通信&#xff0c;UDP 视频、文件传输、SSH 等都…

MySQL数据库迁移SQL语句指南

MySQL数据库迁移SQL语句指南 一、基础迁移方法 1. 使用mysqldump进行全量迁移 -- 导出源数据库&#xff08;在命令行执行&#xff09; mysqldump -u [源用户名] -p[源密码] --single-transaction --routines --triggers --events --master-data2 [数据库名] > migration…

画立方体软件开发笔记 js three 投影 参数建模 旋转相机 @tarikjabiri/dxf导出dxf

gitee&#xff1a; njsgcs/njsgcs_3d mainwindow.js:4 Uncaught SyntaxError: The requested module /3dviewport.js does not provide an export named default一定要default吗 2025-05-10 14-27-58 专门写了个代码画立方体 import{ scene,camera,renderer} from ./3dviewp…

【工具】HandBrake使用指南:功能详解与视频转码

HandBrake使用指南&#xff1a;功能详解与视频转码 一、前言 高清视频在当下日益普及&#xff0c;从影视制作到个人拍摄&#xff0c;从社交媒体发布到远程教育&#xff0c;如何高效地压缩、转换和管理视频文件的体积与清晰度&#xff0c;成为内容创作者与技术开发者的核心任务…

Docker容器网络架构深度解析与技术实践指南——基于Linux内核特性的企业级容器网络实现

第1章 容器网络基础架构 1 Linux网络命名空间实现原理 1.1内核级隔离机制深度解析 1.1.1进程隔离的底层实现 通过clone()系统调用创建新进程时&#xff0c;设置CLONE_NEWNET标志位将触发内核执行以下操作&#xff1a; 内核源码示例&#xff08;linux-6.8.0/kernel/fork.c&a…

SAP 交货单行项目含税金额计算报cx_sy_zerodivide处理

业务背景&#xff1a;SAP交货单只有数量&#xff0c;没有金额&#xff0c;所以开发报表从订单的价格按数量计算交货单的金额。 用户反馈近期报表出现异常&#xff1a; ****2012/12/12 清风雅雨 规格变更 Chg 修改开始 ** 修改原因:由于余数为0时&#xff0c;可能会报错溢出。…