我如何使用Google制作OPC2WEB客户端

我是一名过程控制工程师,对编程有点喜欢:在Google和Stack Overflow的帮助下,我用HTML和javascript制作了多个计算器,用php制作了电报机器人,甚至在工作中做了一些C#编程。这次虽然看起来很简单,但任务却更加有趣和困难:“我想在浏览器中查看设备的当前速度”。首先,我决定尝试寻找现成的软件:当然,它已经发明了很长时间,有现成的甚至免费的SCADA系统都可以用作Web服务器,但是它们对我来说都是非常复杂和困难的,此外,这只是必要的推导速度。所以我想我可以自己尝试做,这就是结果:



后端



在决定自己要做什么之后,我再次打开了搜索引擎,开始寻找自己制作OPC客户的方法。







通过搜索,我发现了免费的OPCDOTNET库。库存档包含控制台客户端的源代码,我在计算机上编译了该代码,并启动了一个简单的OPC模拟器(灰色框)……瞧瞧!我看到控制台中的数字发生了变化。这意味着现在我可以将它们作为对Web请求的响应进行发送。对Google的下一次访问是请求一个简单的Web服务器,在那里我遇到了一个使用HttpListener的示例。我在一个单独的项目中运行了该示例,了解了它的工作原理,并开始将所有这些添加到我的OPC客户端中。经过多次编译尝试,在Stack Overflow上搜索错误后,我仍然设法在浏览器中看到了珍贵的“速度”。这是胜利!但是我立刻意识到,光是速度并不严重,一段时间后,技术人员会希望看到生产线的其他参数,因此,您需要弄清楚如何在不更改程序的情况下添加必要的信号。配置文件可以解决,您可以在其中预先设置我们希望看到的信号,设置服务器侦听端口,更新时间等。我已经具有创建配置文件的经验,因此我像以前一样做到了,而且效果很好。另外,在此过程中,我不得不联系一名程序员的朋友,他建议该怎么做才能传输请求的数据的整个数组,不仅传输那些已更改的值(在OPC客户端的最终示例中,控制台中仅显示已更改的值)。我已经具有创建配置文件的经验,因此我像以前一样做到了,而且效果很好。此外,在此过程中,我不得不联系一位程序员的朋友,他建议该怎么做才能传输请求的数据的整个数组,不仅传输那些已更改的值(在OPC客户端的最终示例中,控制台中仅显示更改的值)。我已经具有创建配置文件的经验,因此我像以前一样做到了,而且效果很好。另外,在此过程中,我不得不联系一名程序员的朋友,他建议该怎么做才能传输请求的数据的整个数组,不仅传输那些已更改的值(在OPC客户端的最终示例中,控制台中仅显示已更改的值)。







进行此类更改后,程序开始根据配置中请求的信号生成HTML表格:通过浏览器联系启动此客​​户端的服务器地址,现在可以在相邻列中看到一个包含信号名称和值的表格。这已经不错了,但是在更新过程中值闪烁了,并且信号本身被愚蠢地一个接一个地定位,尽管它们是以表格的形式构造的。顺便说一句,为了使值每秒自动更新,而不仅仅是在用户刷新页面时,我在返回请求的页面上添加了带有Refresh参数的meta标签。但是我真的希望这些值能够自动更新而无需重新加载页面,因此除了后端之外,现在还必须做前端:用户在服务器上请求一个页面,在其中发生对客户端的请求,然后页面以美观且易于理解的形式生成所有这些内容,您可以在其中随意构造数据,更改颜色,字体和大小-您可以使用此方法进行任何操作。



Frontend



我并没有马上就来:起初,我开始在Google上搜索如何在不重新加载的情况下更新页面上的数据。事实证明,您需要使用AJAX,即通过javascript更改数据,然后通过JSON接收数据。在客户端中,我通过简单的字符串连接来生成JSON,为了通用起见,我决定简单地按顺序计算配置中设置的标签。然后我发现了一个示例,其中通过javascript每秒请求一个JSON字符串,并显示其中的值。更改代码以适合我的需要并运行该页面,我发现一切正常-无需重新加载页面即可更新数据(!)。这是另一场胜利。现在几乎无事可做-正确地在页面上分配接收到的数据,即以可视化的形式进行操作。一开始我决定做一张桌子但后来我意识到该块结构看起来更好,更实用。块可以涂成不同的颜色并调整大小。而且您还需要确保用户可以自己添加和更改结构,我不会为每个新愿望重写HTML文件。结果,我们得到了如下图所示的选项。







您可以在此处添加将小块与一项功能结合在一起的大块。可以根据需要为此类大块命名,可以更改其颜色(按住Shift键的同时单击该块),并可以更改其大小。通过双击一个大块添加具有值的块。您还可以在其中设置自己的名称和度量单位。如果您不小心添加了错误的元素或在错误的位置,则可以将其删除-我在一个小书签中监视了此功能,将其代码完全转移到了页面上。当然,重新创建页面后,整个创建的结构将消失,为了保存它,我发现了诸如本地存储的机会。为了将完成的结构转移到另一台计算机,我从本地存储中导入和导出了屏幕。



唯一的问题仍然是拖放块-我想进行很好的拖放,但是对我来说却是压倒性的。我摆脱了这种情况:如果您在Chrome开发人员面板中打开页面,则可以拖动这些块。这给了我一个想法,通过使用鼠标右键,您可以简单地交换块。现在这样的系统已经很普遍了:要添加新信号,只需将所需的OPC标签添加到配置中并重新启动客户端。添加的标记会自动添加到JSON,并且新值会出现在输出屏幕的底部,只需单击几下即可将其添加到页面上的现有或新块。目前,页面上显示的标签超过60个,其中一半以上不是我添加的,也就是说,添加过程可能不是最简单的,但不需要重写程序和输出页面。您可以测试并查看此页面的代码





由于本文应该像是一个说明,说明像我这样的非程序员如何在搜索引擎的帮助下做一些有用的事情,因此我可能需要添加一些关于我如何精确查找信息的词。就像刚开始的图片一样,在这里说的很对:您想获得什么并向Google询问,如果某处无法解决问题,则请查看错误代码并再次询问。英文搜索有很大帮助-即使仅输入关键字,您也可以在有80%机率的情况下获得指向stackerflow上类似的已解决问题的链接。要搜索现成的示例(您可以从中愚蠢地获取并传输到程序中的代码),可以添加诸如“ example”或俄语“ example”之类的关键字。在habr上发现了几个好主意,也就是说,您可以尝试在请求中插入关键字“ habr”,但是只有当我确定知道在Habré上看到了所需的解决方案时,才使用此功能。通过搜索引擎解决了几乎所有的小任务:“更改div颜色转换单击js”,“使div可调整大小”,“如何编辑网页”……数百种不同查询的变体。也许专家可以在评论中分享他们的建议。



是的,由于我们正在讨论建议,因此我也希望收到您的建设性批评和有用的建议。也许有人想张开嘴,可以在几个小时内提出更多实用的解决方案。也许这篇文章会给别人一些有趣的想法,因为通过这种方式,您可以接受任何JSON请求并基于该请求进行任何可视化结构。拥有一个类似的通用解决方案将非常酷,您可以在其中分发适合您的任何数据,管理简单的可视表单,拖放,调整大小等,以使其美观和实用,但仅此而已。尽管结果很好,但我认为。现在可以从浏览器中观察到客户要求的设备速度,并且添加新的东西将不会很困难。



链接到C#中的客户端代码



或在扰流板下
/*=====================================================================
  File:      OPCCSharp.cs

  Summary:   OPC sample client for C#

-----------------------------------------------------------------------
  This file is part of the Viscom OPC Code Samples.

  Copyright(c) 2001 Viscom (www.viscomvisual.com) All rights reserved.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================================*/

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Configuration;
using OPC.Common;
using OPC.Data;
using System.Net;
using System.Globalization;
using System.Data.SqlClient;
using System.Data;
using System.Net.Sockets;


namespace CSSample
{
    class Tester
    {
        // ***********************************************************	EDIT THIS :
        string serverProgID = ConfigurationManager.AppSettings["opcID"];         // ProgID of OPC server

        private OpcServer theSrv;
        private OpcGroup theGrp;
        private static float[] currentValues;
        private static string responseStringG ="";
        private static HttpListener listener = new HttpListener();

        private static string consoleOut = ConfigurationManager.AppSettings["consoleOutput"];
        private static string answerType = ConfigurationManager.AppSettings["answerType"];
        private static string portNumb = ConfigurationManager.AppSettings["portNumber"];
        private static int timeref = Int32.Parse(ConfigurationManager.AppSettings["refreshTime"]);
        private static string[] tagsNames = ConfigurationManager.AppSettings["tagsNames"].Split(','); // tags from config
        private static string[] ratios = ConfigurationManager.AppSettings["ratios"].Split(',');

        private static string sqlSend = ConfigurationManager.AppSettings["sqlSend"];
        private static string udpSend = ConfigurationManager.AppSettings["udpSend"];
        private static string webSend = ConfigurationManager.AppSettings["webSend"];
        private static string table_name = ConfigurationManager.AppSettings["table"]; //    ;
        private static string column_name = ConfigurationManager.AppSettings["column"];
        private static int sendtags = Int32.Parse(ConfigurationManager.AppSettings["tags2send"]);
        
        private static IPAddress remoteIPAddress = IPAddress.Parse(ConfigurationManager.AppSettings["remoteIP"]); // Ip from config
        private static int remotePort = Convert.ToInt16(ConfigurationManager.AppSettings["remotePort"]); // remote port from config

        public static SqlConnection myConn = new SqlConnection(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString); //   SQL    
        SqlCommand myCommand = new SqlCommand("Command String", myConn);

        public void Work()
        {
            /*	try						// disabled for debugging
                {	*/

            theSrv = new OpcServer();
            theSrv.Connect(serverProgID);
            Thread.Sleep(500);              // we are faster then some servers!

            // add our only working group
            theGrp = theSrv.AddGroup("OPCCSharp-Group", false, timeref);

            string[] tags = ConfigurationManager.AppSettings["tags"].Split(','); // tags from config
            if (sendtags > tags.Length) sendtags = tags.Length;

                var itemDefs = new OPCItemDef[tags.Length];
            for (var i = 0; i < tags.Length; i++)
            {
                itemDefs[i] = new OPCItemDef(tags[i], true, i, VarEnum.VT_EMPTY);
            }

            OPCItemResult[] rItm;
            theGrp.AddItems(itemDefs, out rItm);
            if (rItm == null)
                return;
            if (HRESULTS.Failed(rItm[0].Error) || HRESULTS.Failed(rItm[1].Error))
            {
                Console.WriteLine("OPC Tester: AddItems - some failed"); theGrp.Remove(true); theSrv.Disconnect(); return;

            };

            var handlesSrv = new int[itemDefs.Length];
            for (var i = 0; i < itemDefs.Length; i++)
            {
                handlesSrv[i] = rItm[i].HandleServer;
            }

            currentValues = new Single[itemDefs.Length];

            // asynch read our two items
            theGrp.SetEnable(true);
            theGrp.Active = true;
            theGrp.DataChanged += new DataChangeEventHandler(this.theGrp_DataChange);
            theGrp.ReadCompleted += new ReadCompleteEventHandler(this.theGrp_ReadComplete);


            int CancelID;

            int[] aE;
            theGrp.Read(handlesSrv, 55667788, out CancelID, out aE);

            // some delay for asynch read-complete callback (simplification)
            Thread.Sleep(500);

            while (webSend=="yes")
            {
                HttpListenerContext context = listener.GetContext();
                HttpListenerRequest request = context.Request;
                HttpListenerResponse response = context.Response;
                context.Response.AddHeader("Access-Control-Allow-Origin", "*");


                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseStringG);
                // Get a response stream and write the response to it.
                response.ContentLength64 = buffer.Length;
                System.IO.Stream output = response.OutputStream;
                output.Write(buffer, 0, buffer.Length);
                // You must close the output stream.
                output.Close();
            }
            // disconnect and close
            Console.WriteLine("************************************** hit <return> to close...");
            Console.ReadLine();
            theGrp.ReadCompleted -= new ReadCompleteEventHandler(this.theGrp_ReadComplete);
            theGrp.RemoveItems(handlesSrv, out aE);
            theGrp.Remove(false);
            theSrv.Disconnect();
            theGrp = null;
            theSrv = null;


            /*	}
            catch( Exception e )
                {
                Console.WriteLine( "EXCEPTION : OPC Tester " + e.ToString() );
                return;
                }	*/
        }

        // ------------------------------ events -----------------------------

        public void theGrp_DataChange(object sender, DataChangeEventArgs e)
        {

            foreach (OPCItemState s in e.sts)
            {
                if (HRESULTS.Succeeded(s.Error))
                {
                    if (consoleOut == "yes")
                    {
                        Console.WriteLine(" ih={0} v={1} q={2} t={3}", s.HandleClient, s.DataValue, s.Quality, s.TimeStamp); //      
                    }
                    currentValues[s.HandleClient] = Convert.ToSingle(s.DataValue) * Single.Parse(ratios[s.HandleClient], CultureInfo.InvariantCulture.NumberFormat); //     
                }
                else
                    Console.WriteLine(" ih={0}    ERROR=0x{1:x} !", s.HandleClient, s.Error);
            }
            string responseString = "{";
            if (answerType == "table")
            {
                responseString = "<HTML><head><meta charset=\"UTF-8\"><meta http-equiv=\"Refresh\" content=\"" + timeref / 1000 + "\"/></head>" +
            "<BODY><table border><tr><td>" + string.Join("<br>", tagsNames) + "</td><td >" + string.Join("<br>", currentValues) + "</td></tr></table></BODY></HTML>";
                responseStringG = responseString;
            }
            else
            {
                for (int i = 0; i < currentValues.Length - 1; i++) responseString = responseString + "\"tag" + i + "\":\"" + currentValues[i] + "\", ";
                responseString = responseString + "\"tag" + (currentValues.Length - 1) + "\":\"" + currentValues[currentValues.Length - 1] + "\"}";
                responseStringG = responseString;
            }
            byte[] byteArray = new byte[sendtags * 4];
            Buffer.BlockCopy(currentValues, 0, byteArray, 0, byteArray.Length);
            if (sqlSend == "yes")
            {
                try
                {
                    SqlCommand cmd = new SqlCommand("INSERT INTO " + table_name + " (" + column_name + ") values (@bindata)", myConn);
                    myConn.Open();
                    var param = new SqlParameter("@bindata", SqlDbType.Binary)
                    { Value = byteArray };
                    cmd.Parameters.Add(param);
                    cmd.ExecuteNonQuery();
                    myConn.Close();
                }
                catch (Exception err)
                {
                    Console.WriteLine("SQL-exception: " + err.ToString());
                    return;
                }
            }

            if (udpSend == "yes")  UDPsend(byteArray);
        }

        private static void UDPsend(byte[] datagram)
        {
            //  UdpClient
            UdpClient sender = new UdpClient();

            //  endPoint     
            IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort);

            try
            {

                sender.Send(datagram, datagram.Length, endPoint);
                //Console.WriteLine("Sended", datagram);
            }
            catch (Exception ex)
            {
                Console.WriteLine(" : " + ex.ToString() + "\n  " + ex.Message);
            }
            finally
            {
                //  
                sender.Close();
            }
        }
        public void theGrp_ReadComplete(object sender, ReadCompleteEventArgs e)
        {
            Console.WriteLine("ReadComplete event: gh={0} id={1} me={2} mq={3}", e.groupHandleClient, e.transactionID, e.masterError, e.masterQuality);
            foreach (OPCItemState s in e.sts)
            {
                if (HRESULTS.Succeeded(s.Error))
                {
                    Console.WriteLine(" ih={0} v={1} q={2} t={3}", s.HandleClient, s.DataValue, s.Quality, s.TimeStamp);
                }
                else
                    Console.WriteLine(" ih={0}    ERROR=0x{1:x} !", s.HandleClient, s.Error);
            }
        }

        static void Main(string[] args)
        {
            string url = "http://*";
            string port = portNumb;
            string prefix = String.Format("{0}:{1}/", url, port);
            listener.Prefixes.Add(prefix);
            listener.Start();
            
            Tester tst = new Tester();
            tst.Work();
        }
    }
}

/* add this code to app.exe.config file
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>
  <appSettings>
    <add key="opcID" value="Graybox.Simulator" />
    <add key="tagsNames" value="Line Speed,Any name, " />
    <add key="tags" value="numeric.sin.int16,numeric.sin.int16,numeric.sin.int16" />
    <!-- ratios for tags -->
    <add key="ratios" value="1,0.5,0.1" />
    <add key="portNumber" value="45455" />
    <add key="refreshTime" value="1000" />
    <!-- "yes" or no to show values in console-->
    <add key="consoleOutput" value="yes" />
    <add key="webSend" value="no" /> 
    <!-- "table" or json (actually any other word for json)-->
    <add key="answerType" value="json" />

    <add key="sqlSend" value="no" />
    <add key="table" value="raw_tbl" />
    <add key="column" value="data" />
    
    <add key="udpSend" value="yes" />
    <add key="remotePort" value="3310"/>
    <add key="remoteIP" value="127.0.0.1"/>

    <add key="tags2send" value="2" />
    
  </appSettings>
  
  <connectionStrings>
    <add connectionString="Password=12345;Persist Security Info=True;User ID=user12345;Initial Catalog=amt;Data Source=W7-VS2017" name="connstr" />
  </connectionStrings>
   
</configuration>
     */






All Articles