将 串口通讯数据 使用C# WebSocket送到Cocos Creator 3D游戏

背景:虚实结合的模拟训练器,需要从串口接收单片机采集的传感器数据,然后到网页版的Cocos网页h5界面。

从单片机上传到串口,可以用C#的串口类,MScomm32.ocx,串口文件等等;

串口测试工具,有串口助手,串口精灵等

如果使用unity或者vsC# VC++、LabWindowsCVI,读取串口数据都不是问题。

可惜,Cocos的JavaScript(JS),无法直接访问串口等硬件,好像最好的办法就是搭建一个服务器!

开工:

第一步:使用c#搭建一个网页服务器

1、首先要在NuGet导入“Fleck”包,需 .NET Framework 4.5及以上。

这里使用的是vs2017, .NET Framework 4.6

运行VS2017 打开工程,依次点击"工具"->"NuGet包管理器"->"管理解决方案的NuGet包"

如果,“已安装”显示有 fleck,恭喜你,已经准备好。

否则,到“浏览”栏,输入 “fleck”,你应该看到这个

双击第一个 Fleck,然后“安装”,成功即可。

参考:NuGet添加本地包(Package) - Hejin.Wong - 博客园

2、建立一个c# 的控制台WebSocket服务器

不啰嗦,免费版的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<IWebSocketConnection>();
            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#串口编程(转) - 廖先生 - 博客园

3、建立Js网页客户端

新建一个 index.txt文件,改名为index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body >
        <div style="margin-left: 650px;">
            <form id="Form"> 
                <input type="input" name="" id="SendInfo" value="" />
                <button type="submit">提交</button>
            </form>
    
            <div id="hello" style="border: dashed 1px black;height: 500px;width: 500px;margin-top: 10px;">
            
            </div>
        </div>
        
    </body>
    <script type="text/javascript">
        var test=function(){
            
            var print=document.getElementById('hello');
            var form = document.getElementById('Form');
            var input = document.getElementById('SendInfo');
            print.innerHTML += "connecting to server ..<br/>";        
            window.ws = new WebSocket('ws://localhost:9898/');      //监听webscoket服务端口
        
            //监听消息状态
            ws.onmessage=function(eve){
                print.innerHTML+=eve.data+'<br/>'
            }
            
            //监听链接状态
            ws.onopen=function(){
                print.innerHTML+='connection open <br/>'
            }
            
            //监听关闭状态
            ws.onclose = function () {
                print.innerHTML += 'connection closed<br/>';
            }
 
               //向服务器端发送消息
               form.addEventListener('submit',function(e){
                   e.preventDefault();
                   var val="客户端:"+input.value;
                   ws.send(val);
                   input.value="";
               })
               
        }
        
        window.οnlοad=test();
    </script>

保存,浏览器运行。360浏览器、IE、Edge均测试通过。

分别在,服务和客户端输入Server、Client等文字,如上图所示。So far so good.

 第二步:使用c# 里打开串口读写

1、引用串口类

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<byte> listReceive = new List<byte>();
        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);

            }
        }

        /// 打印接收的信息
        /// </summary>
        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);
            }
        }
    }

}

2、在主程序里创建串口对象

 //下面是webSocket服务器主程序
        static void Main(string[] args)
        {
            SerialPortHere.mySerial _myserial1 = new SerialPortHere.mySerial();
           
            _myserial1.Start();//创建串口对象并启动
 

3、运行串口调试程序和c#服务器程序

      这里使用了虚拟串口工具,生成成对串口 com1<--> com5

     其中COM1在机器上是物理存在的。当然完全虚拟也可以,比如COM2-》COM3.

在串口调试助手上按照自定义的串口通讯协议发送 AA 01 10 01 C3 3C, C#服务器程序应该本地打印出来,如图所示。这就快成功了。

第三步:将串口数据转发到web端

既然c#程序可以读取串口数据,也可以读取按键的输入,那么,串口数据转发顺理成章了。

 将服务主程序的死循环改写一下,主要实现按键提示,不按键一直循环。

当有串口数据变化时,输出到网口


       

//下面是webSocket服务器主程序
        static void Main(string[] args)
        {
            SerialPortHere.mySerial _myserial1 = new SerialPortHere.mySerial();
           
            _myserial1.Start();//创建串口对象并启动
           
            var allScokets = new List<IWebSocketConnection>();
            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网页游戏场景

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

        );

        */   

     }

}

网页预览场景,打开调试功能:

哇,串口数据已经传到游戏中啦!

一路顺风!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值