一、本文介绍在web环境下unity与usb串口进行通信的代码
本篇使用本地服务器作为unity与串口的中介,unity发送数据到服务器,服务器发送给串口收到响应并解析返回给uinty。
使用websocket协议。
注:
1.我的硬件是检测磁阻液位,用到四字节十六进制浮点数,所以这里会直接转换到十进制。
2.我的硬件会返回9字节响应,所以我会限制响应长度,可以进行适当更改
重要:
1.再开始前一定要改为.NET 4.x,或者.NET Framework,因为.NET standard 2.0或者2.1不包括需要用到的using System.IO.Ports;更换路径为File/Build Setting/Player Settings.../Other Settings/Api Compatibility Level
二、准备文件:
1.使用c#写服务端,需要打开nuget包进行安装websocket,路径:工具/NuGet包管理器/管理解决方案的NuGet程序包/浏览
2.unity端:下载NativeWebSocket,地址:https://github.com/endel/NativeWebSocket
注意:下载了.unitypackage包后拖入到unity中,他会存放到与Assets同级的目录下,打开文件夹将他全部拖到Assets目录就可以了(我不知道不拖行不行,反正我这么做的可以正常使用)
三、服务端代码:
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using WebSocketSharp.Server;
using System.Threading;
using WebSocketSharp;
namespace webforeverusb
{
class Program
{
static void Main(string[] args)
{
var wss = new WebSocketServer("ws://localhost:8080");
wss.AddWebSocketService<SerialService>("/serial");
wss.Start();
Console.WriteLine("WebSocket 服务器正在运行...");
Console.ReadLine(); // 保持控制台运行
wss.Stop(); // 优雅关闭服务器
}
}
class SerialService : WebSocketBehavior
{
private SerialPort sp = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
private bool isRunning = false;
protected override void OnOpen()
{
try
{
sp.Open();
Console.WriteLine("串口已打开");
// StartReading(); // 移除持续读取,仅通过命令触发
}
catch (Exception ex)
{
Console.WriteLine($"串口打开失败: {ex.Message}");
}
Console.WriteLine("客户端已连接");
}
protected override void OnMessage(MessageEventArgs e)
{
string command = e.Data;
Console.WriteLine($"收到客户端命令: {command}");
try
{
if (sp.IsOpen)
{
// 将命令转换为字节数组并写入串口
byte[] commandBytes = HexStringToBytes(command);
sp.Write(commandBytes, 0, commandBytes.Length);
Console.WriteLine("命令已写入串口");
// 等待并读取串口响应(9 字节)
byte[] response = new byte[9];
int bytesRead = 0;
int timeoutMs = 1000; // 超时时间 1 秒
DateTime startTime = DateTime.Now;
while (bytesRead < 9 && sp.IsOpen)
{
if (sp.BytesToRead > 0)
{
bytesRead += sp.Read(response, bytesRead, 9 - bytesRead);
Console.WriteLine($"已读取 {bytesRead}/9 字节");
}
if ((DateTime.Now - startTime).TotalMilliseconds > timeoutMs)
{
Console.WriteLine("读取响应超时");
Send("错误: 读取响应超时");
return;
}
Thread.Sleep(10);
}
// 验证响应格式
string responseHex = BitConverter.ToString(response).Replace("-", " ");
Console.WriteLine($"串口响应: {responseHex}");
if (response.Length == 9 && response[0] == 0x01 && response[1] == 0x04)
{
// 提取第 3 到第 6 字节(4 字节数据)
byte[] floatBytes = new byte[4];
Array.Copy(response, 3, floatBytes, 0, 4);
// 转换为浮点数
float floatValue = ConvertHexToFloat(floatBytes);
Console.WriteLine($"解析得到的浮点数: {floatValue}");
// 将浮点数值发送给客户端
Send(floatValue.ToString());
}
else
{
Console.WriteLine("响应格式错误");
Send("错误: 响应格式错误");
}
}
else
{
Console.WriteLine("串口未打开");
Send("错误: 串口未打开");
}
}
catch (Exception ex)
{
Console.WriteLine($"串口通信失败: {ex.Message}");
Send($"错误: {ex.Message}");
}
}
// 将十六进制字符串转换为字节数组
private byte[] HexStringToBytes(string hexString)
{
hexString = hexString.Replace(" ", "");
byte[] bytes = new byte[hexString.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return bytes;
}
// 将 4 个字节转换为 IEEE 754 单精度浮点数
private float ConvertHexToFloat(byte[] bytes)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes); // 翻转字节顺序(大端转小端)
}
return BitConverter.ToSingle(bytes, 0);
}
protected override void OnClose(CloseEventArgs e)
{
isRunning = false;
if (sp != null && sp.IsOpen)
{
sp.Close();
sp.Dispose();
Console.WriteLine("串口已关闭");
}
Console.WriteLine("客户端断开连接");
}
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
{
Console.WriteLine($"WebSocket 错误: {e.Message}");
}
}
}
四、unity端:
cs
using UnityEngine;
using NativeWebSocket;
using System.Threading.Tasks;
public class WebSocketClient : MonoBehaviour
{
private WebSocket websocket;
public float pavalue; // 存储解析后的浮点数值
async void Start()
{
#if UNITY_WEBGL && !UNITY_EDITOR
websocket = new WebSocket("ws://localhost:8080/serial");
websocket.OnOpen += () =>
{
Debug.Log("连接成功,状态: " + websocket.State);
SendCommand("01 04 00 02 00 02 D0 0B"); // 发送新命令
};
websocket.OnMessage += (bytes) =>
{
string message = System.Text.Encoding.UTF8.GetString(bytes);
Debug.Log($"收到数据: {message}");
// 尝试解析为浮点数
if (float.TryParse(message, out float value))
{
pavalue = value;
Debug.Log($"解析得到的浮点数: {pavalue}");
}
else
{
Debug.LogWarning("无法解析为浮点数: " + message);
}
};
websocket.OnError += (e) =>
{
Debug.LogError($"错误: {e}");
};
websocket.OnClose += (e) =>
{
Debug.Log("连接关闭");
};
await websocket.Connect();
#else
Debug.LogWarning("WebSocket 仅在 WebGL 构建中运行");
#endif
}
public void SendCommand(string command)
{
if (websocket != null && websocket.State == WebSocketState.Open)
{
websocket.SendText(command);
Debug.Log($"发送命令: {command}");
}
else
{
Debug.LogError("WebSocket 未连接,无法发送命令");
}
}
float timer = 0f;
void Update()
{
timer += Time.deltaTime;
if (timer >= 1f)
{
SendCommand("01 04 00 02 00 02 D0 0B");
timer = 0f;
}
}
async void OnDestroy()
{
#if UNITY_WEBGL && !UNITY_EDITOR
if (websocket != null)
{
await websocket.Close();
}
#endif
}
}
五、结尾:
