基于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

相关文章

批量导入txt数据到的redis过程

《批量导入txt数据到的redis过程》用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以Product*匹配的Key操作,提高了数据清理效率... 目录批量导入txt数据到Redisjs把redis命令按一条 一行写到txt中管道命令运行redis客户端成功了批量删除k

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

C语言中%zu的用法解读

《C语言中%zu的用法解读》size_t是无符号整数类型,用于表示对象大小或内存操作结果,%zu是C99标准中专为size_t设计的printf占位符,避免因类型不匹配导致错误,使用%u或%d可能引发... 目录size_t 类型与 %zu 占位符%zu 的用途替代占位符的风险兼容性说明其他相关占位符验证示

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

Python进行JSON和Excel文件转换处理指南

《Python进行JSON和Excel文件转换处理指南》在数据交换与系统集成中,JSON与Excel是两种极为常见的数据格式,本文将介绍如何使用Python实现将JSON转换为格式化的Excel文件,... 目录将 jsON 导入为格式化 Excel将 Excel 导出为结构化 JSON处理嵌套 JSON:

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We