基于PLC-Recorder数据转发功能的WebSocket客户端设计(高级语言及HTML,通讯内容为Json格式)

最近PLC-Recoder推出了V1.6.0版本,其最大的变化是增加了数据转发功能,所有能采集到的数据,都可以通过WebSocket和Json转发出去,为不熟悉PLC底层技术,且需要数据采集的朋友们提供了一个统一的接口。下面将介绍几种客户端创建方法。

1、服务器端设置

1)配置好通道(需要采集数据的设备)和所有需要采集的变量,开启录波测试。如果没有实际的设备,也可以启动录波仿真功能,模拟录波,为所有的变量提供数据。
参考文章:
欧姆龙、松下、基恩士PLC进行连续数据采集、时序和故障追踪的方法》、
西门子PLC进行连续数据采集、时序和故障追踪的方法》、
三菱PLC进行连续数据采集、时序和故障追踪的方法
2)配置服务器参数,启动服务器:
通过菜单“转发”->“配置…”,打开配置窗口设置端口号和服务器识别码,点击“应用退出”。然后通过“启动服务器”和“停止服务器”来切换服务器的状态。启动后,软件标题中将出现“[转发中]”的字符。

2、用C#标准库实现客户端

界面设计:在这里插入图片描述
设置有连接、关闭、变量查询、订阅等按钮,下面介绍重要实现:
1)主要引用:WebSocket4Net、Newtonsoft.Json及其依赖项;
2)连接命令

private void btConnect_Click(object sender, EventArgs e)
        {
            string address ="ws://"+ tbIP.Text + ":" + tbPort.Text;
            try
            {
                wsClient = new WebSocket(address);
                
                wsClient.MessageReceived += WebSocket_OnWebSocketMessageReceived;
                wsClient.Opened += WebSocket_OnClientConnected;
                wsClient.Closed += WsClient_Closed;
                wsClient.Open();
                btClose.Enabled = true;
                btConnect.Enabled = false;//避免多次创建连接
            }
            catch (Exception ex)
            {
                MessageBox.Show("Start Failed : " + ex.Message);
            }
        }

关闭程序:

 private void btClose_Click(object sender, EventArgs e)
        {
            wsClient.Close();
            btClose.Enabled = false;
            btConnect.Enabled = true;
        }

变量查询程序:

private void btGetInfo_Click(object sender, EventArgs e)
        {
            if (!connected) { return; }
            QUERY qe = new QUERY();
            qe.ID = tbID.Text; ;
            string payload = JsonConvert.SerializeObject(qe);
            wsClient.Send(payload);
            tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "发送查询信息:" + payload + Environment.NewLine);
        }

订阅变量程序

private void btBook_Click(object sender, EventArgs e)
        {
            if (!connected) { return; }

            BOOK book = new BOOK();
            if (!double.TryParse(tbCycle.Text, out book.CYCLE))
            {
                MessageBox.Show("更新周期需要是数字!");
                return;
            }

            book.ID = tbID.Text;

            foreach (chanel c in listChanels)
            {
                foreach (tag t in c.tags)
                {
                    if (t.selected)
                    {
                        tagInfoForBook tag = new tagInfoForBook();
                        tag.TNAME = t.name;
                        tag.CID = t.chanelid;
                        book.listTIB.Add(tag);
                    }
                }
            }
            book.COUNT = book.listTIB.Count;
            string payload = JsonConvert.SerializeObject(book);
            wsClient.Send(payload);
            tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "发送订阅信息:" + payload + Environment.NewLine);
        }

连接建立和电文处理事件:

private void WebSocket_OnWebSocketMessageReceived(object sender,MessageReceivedEventArgs  message)
        {
            try
            {
                Invoke(new Action(() =>
                {
                    string msg = message.Message;
                    /// <summary>
                    /// 客户端信息的Json对象
                    /// </summary>
                    JObject payloadGetJobjectNow;
                    if (infoUpdateEnable) { tbMessage.AppendText("[RAW] "+DateTime.Now.ToString() + " " + msg + Environment.NewLine); }
                        
                    payloadGetJobjectNow =(JObject) JsonConvert.DeserializeObject(msg);
                    FS = getFSFromPayload(payloadGetJobjectNow);
                    switch (FS)
                    {

                        case 10://验证结果
                            if (payloadGetJobjectNow["RESULT"].ToString() == "1")
                            {
                                tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "验证成功!" + Environment.NewLine);
                            }
                            else
                            {
                                tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "验证失败! " + payloadGetJobjectNow["REASON"].ToString()+ Environment.NewLine);
                            }
                            break;
                        case 20://读取设备信息
                            if (payloadGetJobjectNow.ContainsKey("listChanel") && payloadGetJobjectNow.ContainsKey("listTagInfo"))
                            {
                                JArray jaChanel = (JArray)payloadGetJobjectNow["listChanel"];
                                JArray jaTag = (JArray)payloadGetJobjectNow["listTagInfo"];
                                JObject jobect;
                                int CIDn = 0;
                                chanel c;
                                if (jaChanel.Count > 0)
                                {
                                    List<chanel> listChanelTemp = new List<chanel>();
                                    for(int i = 0; i < jaChanel.Count; i++)
                                    {
                                        c = new chanel();
                                        listChanelTemp.Add(c);
                                    }
                                    for (int i = 0; i < jaChanel.Count; i++)
                                    {
                                        jobect =(JObject) jaChanel[i];
                                        CIDn = 0;
                                        if (jobect.ContainsKey("CID"))
                                        {
                                           if( int.TryParse(jobect["CID"].ToString(),out CIDn))
                                            {
                                                chanel cTemp = listChanelTemp[CIDn];
                                                cTemp.TNAME = jobect["TNAME"].ToString();
                                                cTemp.BIGTYPE = jobect["BIGTYPE"].ToString();
                                                cTemp.DEVICETYPE = jobect["DEVICETYPE"].ToString();
                                                double.TryParse(jobect["CYCLE"].ToString(), out cTemp.CYCLE);
                                            }
                                        }
                                    }
                                    for (int i = 0; i < jaTag.Count; i++)
                                    {
                                        CIDn = 0;
                                        jobect = (JObject)jaTag[i];
                                        if (jobect.ContainsKey("CID"))
                                        {
                                            if (int.TryParse(jobect["CID"].ToString(), out CIDn))
                                            {
                                                c = listChanelTemp[CIDn];
                                                tag t = new tag();
                                                c.tags.Add(t);
                                                t.chanelid = CIDn;
                                                t.name = jobect["TNAME"].ToString();
                                                t.type= jobect["TYPE"].ToString();
                                                t.comment= jobect["COMMENT"].ToString();
                                            }
                                        }
                                    }
                                    listChanels = listChanelTemp;
                                    dgvUpdate();
                                    valueUpdateEnable = false;
                                }
                            }
                            break;
                        case 30://全新订阅
                        case 31://增量订阅
                            if (payloadGetJobjectNow["RESULT"].ToString() == "1")
                            {
                                tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "订阅成功!" + Environment.NewLine);
                                valueUpdateEnable = true;
                            }
                            else if (payloadGetJobjectNow["RESULT"].ToString() == "3")
                            {
                                tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "服务器请求重新订阅!" + Environment.NewLine);
                                btBook_Click(null, null);
                            }
                            else
                            {
                                tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "订阅失败! " + payloadGetJobjectNow["REASON"].ToString() + Environment.NewLine);
                            }
                            break;
                        case 40://单次更新,更新所有的值
                        case 41://仅更新变化的变量
                            if (valueUpdateEnable)
                            {
                                JArray ja = (JArray)payloadGetJobjectNow["listTV"];
                                int CID = 0;
                                String tagName = "";
                                tag tagTemp = null;
                                foreach (JObject jt in ja)
                                {
                                    if (int.TryParse(jt["CID"].ToString(), out CID))
                                    {
                                        tagName = jt["TNAME"].ToString();
                                        foreach (tag t in listChanels[CID].tags)
                                        {
                                            if (t.name == tagName)
                                            {
                                                tagTemp = t;
                                                if (double.TryParse(jt["VALUE"].ToString(), out tagTemp.value))
                                                { }
                                                break;
                                            }
                                        }
                                    }
                                }
                                dgvValueUpdate();
                            }
                            break;
                        default:
                            tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + msg + Environment.NewLine);
                            break;
                    }
                }));
            }
            catch { }
        }
        /// <summary>
        /// 建立连接后,马上依据现有变量配置进行订阅,并开始数值的更新。
        /// </summary>
        private void WebSocket_OnClientConnected(object  sender,EventArgs e)
        {
            try
            {
                Invoke(new Action(() =>
                {
                    tbMessage.AppendText("[DEALED] " + DateTime.Now.ToString() + " " + "连接成功!" + Environment.NewLine);
                    connected = true;
                    btBook_Click(null, null);
                }));
            }
            catch { }
        }

完整源码及协议格式下载链接

3、用HSL库的WebSocket组件构建客户端

利用商业组件库HSL(HslCommunication),可以更加方便地客户端构建(本服务器就是用HSL库来实现),引用该组件后,就不需要再引用WebSocket4Net。主要代码与第2章相同,但是HSL的客户端有一个优势:在服务器异常、网络中断后,都会自动尝试重连,用户不需要考虑重连的问题。
比如,服务器重启后,客户端会自动恢复连接,然后服务器发出需要重新订阅变量的通知,客户端只需要进行响应,重发订阅信息(见下图),就可以完成所有的工作,继续进行数据刷新了。
在这里插入图片描述

4、HTML客户端源代码

HTML天生支持WebSocket和Json,因此,可以方便地实现客户端:
在这里插入图片描述

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SocketClientDemoWeb(www.hiddenmap.cn提供)</title>

<script>
var _hmt = _hmt || [];
    var wsIsOpened=false;
    var ws;
   function Book(){//订阅变量
			if (wsIsOpened)
			{
				var JsonBook={"FC":30,"COUNT":4,"CYCLE":300,"ID":"abc123","listTIB":[{"CID":0,"TNAME":"tag0"},{"CID":0,"TNAME":"tag1"},{"CID":1,"TNAME":"tag0"},{"CID":1,"TNAME":"tag1"}]};//
				var JsonBookString=JSON.stringify(JsonBook);
				ws.send(JsonBookString);
			}
   }
</script>
	
</head>

<body style="margin: 0px; font-style: normal; font-family: '微软雅黑';" >
	<script>
    window.οnlοad=function bodyloaded(){ 
        if ("WebSocket" in window){
				ws= new WebSocket("ws://127.0.0.1:1883");//服务器地址         
				ws.onopen = function(){
                wsIsOpened=true;                
            }
            ws.onmessage = function (evt){
                var received_msg = evt.data;
                var count1=0;
                var count2=0;
                if(received_msg.length>0)
                {
					document.getElementById("message_in").innerHTML=received_msg;//完整显示收到的信息内容。
					var jObject=JSON.parse(received_msg);//解析为JSON对象。
					var FS=jObject["FS"];//查询信息携带的功能码。
					if(FS==40 || FS==41)
					{
						var tagCount=jObject["COUNT"];
						var listTV=jObject["listTV"];//变量数组
						for(i=0;i<listTV.length;i++){
							var chanelID=listTV[i]["CID"];
							var tagName=listTV[i]["TNAME"];
							var tagType=listTV[i]["TYPE"];
							var tagValue=listTV[i]["VALUE"];
							if(tagType=="Bool"){
								if(tagValue==1){
								    tagValue="true";}
									else{
									tagValue="false";}
							}//根据数据类型进行数据的呈现。
							
							//根据自己的变量进行解析,放在不同的位置进行呈现。
							if(chanelID==0){
								if(tagName=="tag0"){
									document.getElementById("C0tag0").innerHTML=tagValue;
								}else{
								document.getElementById("C0tag1").innerHTML=tagValue;}
							}else{
								if(tagName=="tag0"){
									document.getElementById("C1tag0").innerHTML=tagValue;
								}else{
									document.getElementById("C1tag1").innerHTML=tagValue;
								}
							}
						}
					}
                }
            }          
        }      
    }
    </script>

<div align="left">

  <span style="font-weight: bold; font-size: 24px; font-family: '微软雅黑';">&nbsp;操作&nbsp;&nbsp;<button class="btn-style" οnclick="Book()">订阅</button></span>
	<br>&nbsp;&nbsp;来自于服务器的信息:
	<br>&nbsp;&nbsp;<span style="font-family: '微软雅黑'; font-size: small"><label id="message_in" >-</label></span>
</div>
	<hr>
<div align="left" font-family= "微软雅黑"> 
  <span style="font-weight: bold; font-size: 24px; font-family: '微软雅黑';">&nbsp;变量信息</span>
    <ol>
    <div align="center">
    <table width="700px" border="1" cellspacing="0">
      <tbody>
    <tr>
      <th width="10%" scope="row">&nbsp;序号</th>
      <th width="30%">&nbsp;通道</th>
	  <th width="30%">&nbsp;变量名</th>
      <th >&nbsp;值</th>
    </tr>
    <tr> <th scope="row">&nbsp;1</th>
		<td>&nbsp;0</td>
        <td>&nbsp;tag0</td> 
		<td>&nbsp;<label id="C0tag0" >??</label></td> 		
		</tr>
   <tr> <th scope="row">&nbsp;2</th>
		<td>&nbsp;0</td>
        <td>&nbsp;tag1</td> 
		<td>&nbsp;<label id="C0tag1" >??</label></td> 		
		</tr>
    <tr> <th scope="row">&nbsp;3</th>
		<td>&nbsp;1</td>
        <td>&nbsp;tag0</td> 
		<td>&nbsp;<label id="C1tag0" >??</label></td> 		
		</tr>
   <tr> <th scope="row">&nbsp;4</th>
		<td>&nbsp;1</td>
        <td>&nbsp;tag1</td> 
		<td>&nbsp;<label id="C1tag1" >??</label></td> 		
		</tr>
   </tbody>
  </table> </div></ol>
</div>
</body>
</html>

5、小结

PLC-Recorder的转发功能也是应很多网友的要求开发,配合已经实现的后台功能(关闭界面,缩小为右下角图标),希望能为大家的后续开发工作提供便利。

					2020年7月8日

录波软件、客户端完整源码及协议格式下载链接

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
js-audio-recorder 是一个使用 JavaScript 实现的录音库,它提供了一个 `exportWAV` 方法可以将录音数据转换为 WAV 格式的音频文件。如果需要截取录音数据,可以在 `exportWAV` 方法中进行处理。 下面是一个简单的示例代: ```javascript // 创建录音对象 var recorder = new Recorder({ sampleBits: 16, // 采样位数 sampleRate: 44100 // 采样率 }); // 开始录音 recorder.start(); // 停止录音 recorder.stop(); // 导出录音数据 recorder.exportWAV(function(blob) { var reader = new FileReader(); reader.onload = function() { var data = new DataView(this.result); var offset = 44; // WAV 文件头偏移量 var length = data.byteLength - offset; // 音频数据长度 var start = offset + 100; // 截取开始位置 var end = offset + 1000; // 截取结束位置 var buffer = new ArrayBuffer(length); var view = new DataView(buffer); for (var i = 0; i < length; i++) { view.setInt8(i, data.getInt8(i + offset)); } var slicedBuffer = buffer.slice(start, end); // 截取录音数据 var slicedBlob = new Blob([slicedBuffer], { type: 'audio/wav' }); var url = URL.createObjectURL(slicedBlob); // 播放截取后的录音数据 var audio = new Audio(url); audio.play(); }; reader.readAsArrayBuffer(blob); }); ``` 上述代中,我们首先通过 `Recorder` 构造函数创建一个录音对象,然后调用 `start` 方法开始录音,`stop` 方法停止录音。接着,通过 `exportWAV` 方法导出录音数据,将录音数据转换为 DataView 对象,并根据 WAV 文件格式的头部信息计算出录音数据的偏移量和长度。接下来,我们可以通过指定开始和结束位置,使用 ArrayBuffer 的 `slice` 方法截取录音数据,并创建一个 Blob 对象,最后通过 URL.createObjectURL 方法生成一个 URL,用于播放截取后的录音数据。 需要注意的是,WAV 文件格式的头部信息通常占用了 44 字节,所以在截取录音数据时需要将偏移量设置为 44。另外,截取录音数据的开始位置和结束位置需要根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值