基于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格式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/chengjl8/article/details/107203844
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/797876

相关文章

Springboot3+将ID转为JSON字符串的详细配置方案

《Springboot3+将ID转为JSON字符串的详细配置方案》:本文主要介绍纯后端实现Long/BigIntegerID转为JSON字符串的详细配置方案,s基于SpringBoot3+和Spr... 目录1. 添加依赖2. 全局 Jackson 配置3. 精准控制(可选)4. OpenAPI (Spri

MybatisPlus service接口功能介绍

《MybatisPlusservice接口功能介绍》:本文主要介绍MybatisPlusservice接口功能介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录Service接口基本用法进阶用法总结:Lambda方法Service接口基本用法MyBATisP

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

MYSQL查询结果实现发送给客户端

《MYSQL查询结果实现发送给客户端》:本文主要介绍MYSQL查询结果实现发送给客户端方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql取数据和发数据的流程(边读边发)Sending to clientSending DataLRU(Least Rec

使用SpringBoot整合Sharding Sphere实现数据脱敏的示例

《使用SpringBoot整合ShardingSphere实现数据脱敏的示例》ApacheShardingSphere数据脱敏模块,通过SQL拦截与改写实现敏感信息加密存储,解决手动处理繁琐及系统改... 目录痛点一:痛点二:脱敏配置Quick Start——Spring 显示配置:1.引入依赖2.创建脱敏

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

html 滚动条滚动过快会留下边框线的解决方案

《html滚动条滚动过快会留下边框线的解决方案》:本文主要介绍了html滚动条滚动过快会留下边框线的解决方案,解决方法很简单,详细内容请阅读本文,希望能对你有所帮助... 滚动条滚动过快时,会留下边框线但其实大部分时候是这样的,没有多出边框线的滚动条滚动过快时留下边框线的问题通常与滚动条样式和滚动行

C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式

《C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式》Markdown凭借简洁的语法、优良的可读性,以及对版本控制系统的高度兼容性,逐渐成为最受欢迎的文档格式... 目录为什么要将文档转换为 Markdown 格式使用工具将 Word 文档转换为 Markdown(.