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

本文主要是介绍基于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日

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

这篇关于基于PLC-Recorder数据转发功能的WebSocket客户端设计(高级语言及HTML,通讯内容为Json格式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/797876

相关文章

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Vue3 如何通过json配置生成查询表单

《Vue3如何通过json配置生成查询表单》本文给大家介绍Vue3如何通过json配置生成查询表单,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录功能实现背景项目代码案例功能实现背景通过vue3实现后台管理项目一定含有表格功能,通常离不开表单

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

Vue和React受控组件的区别小结

《Vue和React受控组件的区别小结》本文主要介绍了Vue和React受控组件的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录背景React 的实现vue3 的实现写法一:直接修改事件参数写法二:通过ref引用 DOMVu

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H