背景:虚实结合的模拟训练器,需要从串口接收单片机采集的传感器数据,然后到网页版的Cocos网页h5界面。
从单片机上传到串口,可以用C#的串口类,MScomm32.ocx,串口文件等等;
串口测试工具,有串口助手,串口精灵等
如果使用unity或者vsC# VC++、LabWindowsCVI,读取串口数据都不是问题。
可惜,Cocos的JavaScript(JS),无法直接访问串口等硬件,好像最好的办法就是搭建一个服务器!
开工:
这里使用的是vs2017, .NET Framework 4.6
运行VS2017 打开工程,依次点击"工具"->"NuGet包管理器"->"管理解决方案的NuGet包"
如果,“已安装”显示有 fleck,恭喜你,已经准备好。
否则,到“浏览”栏,输入 “fleck”,你应该看到这个
双击第一个 Fleck,然后“安装”,成功即可。
参考:NuGet添加本地包(Package) - Hejin.Wong - 博客园
不啰嗦,免费版的VS2017,上图:
框架是指.net框架,4以及4以下的.NET框架可以在xp上运行,4以上可以在win7/8/10上运行,鉴于当前大多数操作系统都是win7或win10,可选择4.5版本。
现在都到win11了,就取默认4.6.1吧。
把下面代码覆盖自动生成的代码:
using Fleck;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebScoket
{
class Program
{
static void Main(string[] args)
{
var allScokets = new List
var server = new WebSocketServer("ws://127.0.0.1:9898"); //创建webscoket服务端实例
server.Start(scoket => {
scoket.OnOpen = () =>
{
Console.WriteLine("Open");
allScokets.Add(scoket);
};
scoket.OnClose = () =>
{
Console.WriteLine("Close");
allScokets.Remove(scoket);
};
scoket.OnMessage = message => {
Console.WriteLine(message);
allScokets.ToList().ForEach(s => s.Send(message));
};
});
var input = Console.ReadLine();
while (input != "exit")
{
foreach (var socket in allScokets.ToList())
{
socket.Send("服务端:" + input);
}
input = Console.ReadLine();
}
}
}
}
保存,运行:
参考:[1]C# 实现WebSocket通信 - 没事儿写个bug - 博客园
[]2c#串口编程(转) - 廖先生 - 博客园2[]2c#串口编程(转) - 廖先生 - 博客园
新建一个 index.txt文件,改名为index.html
保存,浏览器运行。360浏览器、IE、Edge均测试通过。
分别在,服务和客户端输入Server、Client等文字,如上图所示。So far so good.
C#有一个封装良好的串口类 System.IO.Ports.SerialPort;
它的关键参数有 串口号,波特率,数据位长,是否校验,低速设备习惯上的都用“9600,n,8,1”,至于串口编号,则用设备管理器观察一下串口,一般台式机会有一个COM1,使用U转串的话,则串口号有可能随插口不同而变化,可以用通讯超时自动查找策略自动配置。
这里采用协议帧格式,FIFO读取模式
将下列串口操作代码复制同一个文件里,新建的命名空间 namespace SerialPortHere 。
using Fleck;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace SerialPortHere
{
partial class mySerial
{
public string portName = "COM1";//串口名
public int baudRate = 9600;//波特率
public Parity parity = Parity.None;//效验位
public int dataBits = 8;//数据位
public StopBits stopBits = StopBits.One;//停止位
SerialPort sp = null;
Thread dataReceiveThread;
//发送的消息
string message = "";
public List
char[] strchar = new char[100];//接收的字符信息转换为字符数组信息
string str;
//接收的数据
public int btButton1, btButton2, swSwitch1, swSwitch2, adAd1;
public bool serialChaned = false;
public void Start()
{
OpenPort();
dataReceiveThread = new Thread(new ThreadStart(DataReceiveFunction));
dataReceiveThread.Start();
}
void Update()
{
}
public void OpenPort()
{
//创建串口
sp = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
sp.ReadTimeout = 400;
try
{
sp.Open();
}
catch (Exception ex)
{
Logger(ex.Message);
}
}
private void Logger(string message)
{
throw new NotImplementedException();
}
void OnApplicationQuit()
{
ClosePort();
}
public void ClosePort()
{
try
{
sp.Close();
dataReceiveThread.Abort();
}
catch (Exception ex)
{
Logger(ex.Message);
}
}
/// 打印接收的信息
///
void PrintData()
{
for (int i = 0; i < listReceive.Count; i++)
{
strchar[i] = (char)(listReceive[i]);
str = new string(strchar);
}
Logger(str);
}
//
void DataReceiveFunction()
{
//#region 按单个字节发送处理信息,不能接收中文
//while (sp != null && sp.IsOpen)
//{
// Thread.Sleep(1);
// try
// {
// byte addr = Convert.ToByte(sp.ReadByte());
// sp.DiscardInBuffer();
// listReceive.Add(addr);
// PrintData();
// }
// catch
// {
// //listReceive.Clear();
// }
//}
//#endregion
// 按字节数组发送处理信息,信息缺失
byte[] buffer = new byte[1024];
int bytes = 0;
byte[] CommandBuffer = new byte[50];//存储串口命令,其中10为串口数据帧长度,根据协议调整
byte[] CommandBufferRevert = new byte[50];
int i, j, CommandLength = 6;
while (true)
{
if (sp != null && sp.IsOpen)
{
try
{
bytes = sp.Read(buffer, 0, buffer.Length);//接收字节
if (bytes == 0)
{
continue;
}
else
{
//Console.WriteLine("Get:"+ buffer+"" );//监控串口输入
//string strbytes = Encoding.Default.GetString(buffer);
//Logger(strbytes);
//Debug.Log(strbytes);
for (j = 0; j < bytes; j++)
{
for (i = 0; i < CommandLength - 1; i++) CommandBuffer[i] = CommandBuffer[i + 1]; //FIFO 向左移动一个位置
CommandBuffer[CommandLength - 1] = buffer[j]; //填充新的数据
//消息帧格式0xAA bs adH AdL C3 3C
if (CommandBuffer[0] == 0xAA && CommandBuffer[CommandLength - 1] == 0x3C)//收到一帧数据,开始解析命令
{
if ((CommandBuffer[1] & 0x10) != 0) btButton1 = 1;//按钮1
else btButton1 = 0;
if ((CommandBuffer[1] & 0x40) != 0) btButton2 = 1;//按钮2
else btButton2 = 0;
//......若干 位 开关量
if ((CommandBuffer[1] & 0x02) != 0) swSwitch1 = 1;//钮子开关1
else swSwitch1 = 0;
if ((CommandBuffer[1] & 0x01) != 0) swSwitch2 = 1;//钮子开关2
else swSwitch1 = 0;
//若干个模拟量 unsigned short...
CommandBufferRevert[2] = CommandBuffer[3];//下位机上传数据高字节在前Big,PC机处理低字节在前little,所以要交换位置
CommandBufferRevert[3] = CommandBuffer[2];
adAd1 = BitConverter.ToInt16(CommandBufferRevert, 2);//
//CRC 校验暂时不用,后期换成异或校验和的方式
Console.WriteLine("Bt1:" + btButton1 + " Bt2:" + btButton2 + " Sw1:" + swSwitch1 + " Sw2:" + swSwitch2 + " Ad1:" + adAd1 + "");
serialChaned = true;
}
}
//for (i = 0; i < bytes; i++) buffer[i] = 0;//简单清零策略。更好的是循环队列或者FIFO
//Debug.Log("SerialSpeedMoveV "+SerialSpeedMoveV);
}
}
catch (Exception ex)
{
if (ex.GetType() != typeof(ThreadAbortException))
{
}
}
}
Thread.Sleep(10);
}
}
//发送数据
public void WriteData(string dataStr)
{
if (sp.IsOpen)
{
sp.Write(dataStr);
}
}
}
}
//下面是webSocket服务器主程序
static void Main(string[] args)
{
SerialPortHere.mySerial _myserial1 = new SerialPortHere.mySerial();
_myserial1.Start();//创建串口对象并启动
这里使用了虚拟串口工具,生成成对串口 com1<--> com5
其中COM1在机器上是物理存在的。当然完全虚拟也可以,比如COM2-》COM3.
在串口调试助手上按照自定义的串口通讯协议发送 AA 01 10 01 C3 3C, C#服务器程序应该本地打印出来,如图所示。这就快成功了。
既然c#程序可以读取串口数据,也可以读取按键的输入,那么,串口数据转发顺理成章了。
将服务主程序的死循环改写一下,主要实现按键提示,不按键一直循环。
当有串口数据变化时,输出到网口
//下面是webSocket服务器主程序
static void Main(string[] args)
{
SerialPortHere.mySerial _myserial1 = new SerialPortHere.mySerial();
_myserial1.Start();//创建串口对象并启动
var allScokets = new List
var server = new WebSocketServer("ws://127.0.0.1:9898"); //创建webscoket服务端实例
server.Start(scoket => {
scoket.OnOpen = () =>
{
Console.WriteLine("Open");
allScokets.Add(scoket);
};
scoket.OnClose = () =>
{
Console.WriteLine("Close");
allScokets.Remove(scoket);
};
scoket.OnMessage = message => {
Console.WriteLine(message);
allScokets.ToList().ForEach(s => s.Send(message));
};
});
Console.WriteLine("Press ESC to exit...");//提示ESC 退出
while (true)
{
ConsoleKey InputKey;
//若有键按下,且是 ESC 键,则退出循环
if (Console.KeyAvailable)
{
InputKey = Console.ReadKey(true).Key;
if (InputKey == ConsoleKey.Escape)
{
_myserial1.ClosePort();
break;
}
else
foreach (var socket in allScokets.ToList())
{
socket.Send("服务端:" + InputKey);
}
}
if (_myserial1.serialChaned)
{
foreach (var socket in allScokets.ToList())
{
socket.Send("服务端:button1: " + _myserial1.btButton1);
socket.Send("服务端:button2: " + _myserial1.btButton2);
socket.Send("服务端:switch1: " + _myserial1.swSwitch1);
socket.Send("服务端:switch2: " + _myserial1.swSwitch2);
socket.Send("服务端:ADC1: " + _myserial1.adAd1);
}
_myserial1.serialChaned = false;
}
//input = Console.ReadLine();
}
}
运行服务器程序,刷新网页 index.html
当服务器获取整帧数据时,上传到网页端。当ESC按键可以退出服务器程序。
对了,退出前别忘了把串口关掉:
if (InputKey == ConsoleKey.Escape)
{
_myserial1.ClosePort();
break;
}
还有,服务器起来后,需要刷新网页,先后顺序不能错。
Cocos Creator 3.4.2
建立一个场景,随便放一个组件,比如button1
建立一个typescrpit文件,命名为WebSocketSerial.js
将该文件挂在button1上
然后用下列文本替换原文件
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
/**
* Predefined variables
* Name = WebSocketSerial
* DateTime = Thu Apr 28 2022 20:19:03 GMT+0800 (中国标准时间)
* Author = liuzhongren81
* FileBasename = WebSocketSerial.ts
* FileBasenameNoExtension = WebSocketSerial
* URL = db://assets/Scripts/WebSocketSerial.ts
* ManualUrl = https://docs.cocos.com/creator/3.4/manual/zh/
*
*/
@ccclass('WebSocketSerial')
export class WebSocketSerial extends Component {
// [1]
// dummy = '';
ws= new WebSocket("ws://localhost:9898/")
// [2]
// @property
// serializableDummy = 0;
// serializableDummy = 0;
//let ws= new WebSocket("ws://localhost:9898/");
start () {
//let self =this;
//let ws= new WebSocket("ws://localhost:9898/")
this.ws.onopen=function(enent){
console.log("连接成功")
}
// [3]
}
update (deltaTime: number) {
this.ws.onmessage=function(enent)
{
console.log("Msg:"+enent.data);
console.log("get message:"+enent.data );
}
this.ws.οnerrοr=function(enent)
{
console.log("Send text fired an error");
}
this.ws.onclose=function(enent)
{
console.log("WebSocket closed");
}
/* 这个超时处理总报错,原因未知
setTimeout(function(){
if(this.ws.readyState==WebSocket.OPEN){
console.log("WebSocket start send message");
}
else{
console.log("WebSocket instance wasn't ready");
}
},
3000
);
*/
}
}
网页预览场景,打开调试功能:
哇,串口数据已经传到游戏中啦!
一路顺风!