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

最近PLC-Recoder推出了V1.6.0版本,其最大的变化是增加了数据转发功能,所有能采集到的数据,都可以通过WebSocket和Json转发出去,为不熟悉PLC底层技术,且需要数据采集的朋友们提供了一个统一的接口。下面将介绍几种客户端创建方法。

1、服务器端设置

1)配置好通道(需要采集数据的设备)和所有需要采集的变量,开启录波测试。如果没有实际的设备,也可以启动录波仿真功能,模拟录波,为所有的变量提供数据。
参考文章:
《欧姆龙、松下、基恩士PLC进行连续数据采集、时序和故障追踪的方法》、
《西门子PLC进行连续数据采集、时序和故障追踪的方法》、
《三菱PLC进行连续数据采集、时序和故障追踪的方法》
2)配置服务器参数,启动服务器:
通过菜单“转发”->“配置…”,打开配置窗口设置端口号和服务器识别码,点击“应用退出”。然后通过“启动服务器”和“停止服务器”来切换服务器的状态。启动后,软件标题中将出现“[转发中]”的字符。

2、用C#标准库实现客户端

界面设计:基于PLC-Recorder数据转发功能的WebSocket客户端设计(高级语言及HTML,通讯内容为Json格式)_第1张图片
设置有连接、关闭、变量查询、订阅等按钮,下面介绍重要实现:
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;
                    /// 
                    /// 客户端信息的Json对象
                    /// 
                    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 { }
        }
        /// 
        /// 建立连接后,马上依据现有变量配置进行订阅,并开始数值的更新。
        /// 
        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的客户端有一个优势:在服务器异常、网络中断后,都会自动尝试重连,用户不需要考虑重连的问题。
比如,服务器重启后,客户端会自动恢复连接,然后服务器发出需要重新订阅变量的通知,客户端只需要进行响应,重发订阅信息(见下图),就可以完成所有的工作,继续进行数据刷新了。
基于PLC-Recorder数据转发功能的WebSocket客户端设计(高级语言及HTML,通讯内容为Json格式)_第2张图片

4、HTML客户端源代码

HTML天生支持WebSocket和Json,因此,可以方便地实现客户端:
基于PLC-Recorder数据转发功能的WebSocket客户端设计(高级语言及HTML,通讯内容为Json格式)_第3张图片




"utf-8">
<span class="token function">SocketClientDemoWeb</span><span class="token punctuation">(</span>www.hiddenmap.cn提供<span class="token punctuation">)</span>


	


"margin: 0px; font-style: normal; font-family: '微软雅黑';" >
	

"left"> "font-weight: bold; font-size: 24px; font-family: '微软雅黑';";操作 ; ;
 ; ;来自于服务器的信息:
 ; ;"font-family: '微软雅黑'; font-size: small">

"left" font-family= "微软雅黑"> "font-weight: bold; font-size: 24px; font-family: '微软雅黑';";变量信息
    "center"> "700px" border="1" cellspacing="0">
    "10%" scope="row";序号 "30%"> ;通道 "30%"> ;变量名  ;
    "row"> ;1  ;0  ;tag0  ;
    "row"> ;2  ;0  ;tag1  ;
    "row"> ;3  ;1  ;tag0  ;
    "row"> ;4  ;1  ;tag1  ;

5、小结

PLC-Recorder的转发功能也是应很多网友的要求开发,配合已经实现的后台功能(关闭界面,缩小为右下角图标),希望能为大家的后续开发工作提供便利。

					2020年7月8日

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

你可能感兴趣的:(工控,编程)