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

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd