最近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: '微软雅黑';"> 操作 <button class="btn-style" οnclick="Book()">订阅</button></span>
<br> 来自于服务器的信息:
<br> <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: '微软雅黑';"> 变量信息</span>
<ol>
<div align="center">
<table width="700px" border="1" cellspacing="0">
<tbody>
<tr>
<th width="10%" scope="row"> 序号</th>
<th width="30%"> 通道</th>
<th width="30%"> 变量名</th>
<th > 值</th>
</tr>
<tr> <th scope="row"> 1</th>
<td> 0</td>
<td> tag0</td>
<td> <label id="C0tag0" >??</label></td>
</tr>
<tr> <th scope="row"> 2</th>
<td> 0</td>
<td> tag1</td>
<td> <label id="C0tag1" >??</label></td>
</tr>
<tr> <th scope="row"> 3</th>
<td> 1</td>
<td> tag0</td>
<td> <label id="C1tag0" >??</label></td>
</tr>
<tr> <th scope="row"> 4</th>
<td> 1</td>
<td> tag1</td>
<td> <label id="C1tag1" >??</label></td>
</tr>
</tbody>
</table> </div></ol>
</div>
</body>
</html>
5、小结
PLC-Recorder的转发功能也是应很多网友的要求开发,配合已经实现的后台功能(关闭界面,缩小为右下角图标),希望能为大家的后续开发工作提供便利。
2020年7月8日
录波软件、客户端完整源码及协议格式下载链接。
今天的文章java读取plc数据_java读取plc数据「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/77125.html