开源纯C#工控网关+组态软件(四)上下位机通讯原理

一、   网关的功能:承上启下

最近有点忙,更新慢了。感谢园友们给予的支持,现在github上已经有。目标是最好的开源组态,看来又近一步^^

之前有提到网关是物联网的关键环节,它的作用就是承上启下。

下位机有下位机的语言,上位机有上位机的思路。网关就是一个翻译,把下位机的语言转成通用语,再告诉上位机该怎么做。

这个翻译的过程,应该保证:

  1. 实时性。如果太慢,上下位机明显不合拍,就会出问题。

  2. 精确性。信号不能频繁丢失、丢步、跳步;不能有太大误差;也不会带入太多干扰和噪音。

  3. 稳定性。如发生故障,如通讯断开,要能自动重连;要足够强壮,不会动辄崩溃;发生意外崩溃,要能自动重启;要有容错机制和错误日志;等等。

要实现这些指标,还是很有挑战的。

二、   通讯蓝本:OPC规范

网关看上去是个很玄奥的东西,网上找来很多相关资料,发现都集中到一个点:OPC规范。

OPC就如一盏明灯,照亮了前进的方向。OPC规范是大家手笔,集中了业界专家的智慧,站在巨人的肩膀上,可以少走很多弯路。

OPC规范定义了数据的采集、归档、报警以及完整的接口示范。

OPC数据采集规范,包含了这样一些重要概念:

  1. 数据读写方式包括下位机批量推送数据、上位机单独读写数据。

  2. 可以异步读写,也可以同步读写。

  3. 数据包括元数据(数据的属性,如数据类型、长度、名称等)和过程数据(ID、数值、时间戳、质量戳)。

  4. 包含驱动(Driver)-组(Group)-变量(Item)的三级架构。可以对变量按需分组,有的组只读,有的组需要1秒刷新一次,有的只要5分钟就可以,要加以区别以提高效率。

  5. 要能判断驱动程序是否断线、要提供断线或关闭的事件供应用程序处理。

  

总之,信息量很丰富,启发很大,我的网关程序很大程度上都参考了OPC规范。

但是OPC有它固有的缺陷:依赖微软的COM组件技术,不能跨平台,同时COM是一种过时的技术,在不同主机上通讯的配置十分繁琐且不安全。

因此,我改造实现了自己的类OPC服务器。基本通讯过程是:批量轮询下位机-与上个周期的数据比对-提取变化的数据-批量推送给上位机。

三、   与下位机通讯:批量轮询

  • 下位机的特点-为什么要采用轮询

下位机通讯的特点:

  1. 下位机很多采用主-从模式。即主机发送的信息可以传送到各个从机或指定的从机,而各个从机的信息只能发送给主机。主机采用查询方式接收发送数据,从机采用中断方式接收发送数据。这种模式天然适合轮询。

  2. 下位机多数只有一个通信口,有些还是串口,天然不适合推送模式。

  3. 下位机很多是单片机,订阅-发布模式往往逻辑较为复杂,程序编写难度大,对芯片及存储要求也必然提高。因此采用这种模式的下位机目前极少。

轮询就是网关作为主机,定期请求下位机的数据。如果实现批量请求,减少往返,轮询的效率并不低。几千个变量轮询周期500毫秒(西门子),无压力。

轮询是以组(Group)为单位的。Group都继承自IGroup 接口:  

 public interface IGroup : IDisposable
{       
bool IsActive { get; set; }  
 
   short ID { get; }    
   
   int UpdateRate { get; set; }    
   
   float DeadBand { get; set; }  
     
   string Name { get; set; }IDriver Parent { get; }IDataServer Server { get; }IEnumerable<ITag> Items { get; }  

   bool AddItems(IList<TagMetaData> items);    
   
   bool AddTags(IEnumerable<ITag> tags);    
   
   bool RemoveItems(params ITag[] items);      
   
   bool SetActiveState(bool active, params short[] items);ITag FindItemByAddress(DeviceAddress addr);HistoryData[] BatchRead(DataSource source, bool isSync, params ITag[] itemArray);        int BatchWrite(SortedDictionary<ITag, object> items, bool isSync = true);ItemData<int> ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache);ItemData<short> ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache);ItemData<byte> ReadByte(DeviceAddress address, DataSource source = DataSource.Cache);ItemData<float> ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache);ItemData<bool> ReadBool(DeviceAddress address, DataSource source = DataSource.Cache);ItemData<string> ReadString(DeviceAddress address, DataSource source = DataSource.Cache);        int WriteInt32(DeviceAddress address, int value);        int WriteInt16(DeviceAddress address, short value);        int WriteFloat(DeviceAddress address, float value);        int WriteString(DeviceAddress address, string value);        int WriteBit(DeviceAddress address, bool value);        int WriteBits(DeviceAddress address, byte value);        event DataChangeEventHandler DataChange; }


 其中,UpdateRate就是轮询周期。DeadBand是死区。死区代表敏感度,设的小敏感度高,但也带来更多的噪声。

每个Group的变量可支持单独读写(如各ReadXXX,WriteXXX方法),也支持批量推送(DataChange事件)。对下位机的轮询,都是以组为单位,每个组在激活状态下按照自己的轮询周期,采集、推送数据,互不干扰。

每个Group包含特性相似的一组变量:有相同的轮询周期、激活属性(需要轮询或无需轮询)、读写属性(均为只读、读/写或只写),需要的话可以同时使能或同时屏蔽。

因为部分变量无需随时监控,可以将其划入一组,不刷新(轮询);有些变量变化很快,需要高频扫描;有些变化很慢也不需要时时查看,可以几分钟轮询一次。将变量有效分组可以提高对重点监控变量的读写效率,减少对下位机资源的占用。

网关如果有多个客户端相连,各自需要的数据又不尽相同,由网关统一定期轮询,再批量推送给客户端是很高效的。

就比如开超市,南来北往的客人(客户端)需求各异,但超市(网关)来统一采购(轮询),不用客户跟各个批发市场(下位机)直接订货,集中来我这购物(订阅Tag)就行。

  • 未来的扩展

虽然当前主流PLC不都支持订阅-推送模式,但这是历史潮流。AB Controllogix、新的西门子S71200-1500,都支持标签地址,也就是直接推送变化的标签(Tag)数据。

未来考虑制定一个新的接口支持这一模式。

四、   与上位机通讯:订阅-推送

  •  上位机的特点-为什么要采用订阅-推送

上位机通信的特点主要为:

  1. 要及时、准确了解下位机的消息。无论是监控画面、还是报警、提示人工操作这些,越实时越好,越准确越好。如果采用请求-响应模式,请求的周期决定实时性不会太好。请求频繁网关压力大,反之实时性差。

  2. 一个网关可能要拖好几个上位机,工段多的,可能要开十几个显示屏同时监控。因此,每个上位机都向网关请求数据,流量会陡然上升,网络会阻塞,网关压力会很大。

  3. 大部分上位机需要的数据不会经常变化,尤其是一些开关量。如果每次反复请求,浪费资源,浪费时间。

  4. 如果采用对各变量单独请求数据,势必造成大量不同时段请求-响应过程交错发生,难以整合,也难以批量读写,效率极低。

因此,向订阅客户只推送变化的数据,无疑是一种高效的办法。只要数据发生变化,马上推送给客户端,既保证了实时性,又保证了推送数据的最小化。

就像打报修电话,送修单位(下位机)不一定时时刻刻有人上门;电话打过去,总台(网关)记下号码,有人上门(下位机有数据变化)了通知(推送)您。没人你也不用几秒钟打一个催(轮询),你烦大家烦。

  • 如何实现订阅-推送

推送是只推送变化的变量。要知道哪些变量变化了,必须要缓存上一次变量的数据加以比对。因此,需要缓存每次轮询的数据。

网关在对下位机的轮询过程中,将订阅的变量都扫描了一遍;这些数据都存在内存的变量缓存:ICache:

public interface ICache : IReaderWriter
{      
  int Size { get; set; }      
         
  int ByteCount { get; }Array Cache { get; }      
         
  int GetOffset(DeviceAddress start, DeviceAddress end); }


 首先,ICache 继承了IReaderWriter,也就是缓存类也具有可读写性,以方便比对。同时还有一个Cache属性,这就是内存区域:映射、储存下位机的变量。

因为下位机可能有很多个,存储地址也是不连续的;但通过对下位机地址DeviceAddress的排序,最终下位机地址映射到一块连续的内存地址,通过DeviceAddress的CacheIndex(缓存索引)相关联。

每次轮询,即调用IReaderWriter的ReadBytes方法依次读入下位机变量区域;读入的值与Cache中缓存的数据比对; 所有变化的部分加入一个ChangedList表,存储变化的CacheIndex。

这些功能在PLCGroup定时器内的Poll函数实现。      

 protected void timer_Timer(object sender, EventArgs e)
{         
   if (_isActive){  
    lock (sync){            
        Poll();            
        if (_changedList.Count > 0)Update();}}
    else
    return;}

比较之后,如发现ChangedList的数量大于0,说明有变量数值更新,执行Update方法,根据CacheIndex找到所有变化的变量,通过IGroup 接口的DataChange事件打包推送出去。

具体的订阅-推送过程,是利用套接字(Socket)在DAService类实现的。套接字顾名思义,就是一条电话线:各客户端向网关服务器发送请求,建立一个长连接:只要客户不挂电话,就一直连着。客户始终监听电话;只要服务器数据有变化,立马有话务员及时告知,客户响应。在这里我实现了一个自定义的TLV(Type-Length-Value)协议,将变化的数据打包发送,客户端拆包,分解出变量的ID、实时值、时间戳等信息,并转换为图元的动画,后文再详细阐述。

  • 上下位机通讯流程图:

 五、   下面的计划

 

写一系列帖子,把架构、原理讲清楚。大致如下:

  • 网关层接口概述

  • 上下位机通讯原理

  • 如何实现一个设备驱动

  • 如何设计图元

  • VS插件模块及原理

  • 归档模块及文件格式

  • 如何进行功能扩展

  • 组态变量表达式实现

github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275

相关文章: 

  • .NET十年回顾

  • 开源纯C#工控网关+组态软件

  • 开源纯C#工控网关+组态软件(三)加入一个新驱动:西门子S7

原文地址:http://www.cnblogs.com/evilcat/p/7743970.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323117.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java】继承与多态

1、继承 由于一个对象功能的单一性&#xff0c;我们想对其进行扩展&#xff0c;但想保留其属性和功能&#xff0c;我们就必须用到继承。 java继承的特性&#xff1a; 单继承&#xff1a;java里的对象不同于C&#xff0c;只能进行单继承&#xff0c;即一个对象只能有一个父亲 …

C++描述 国王给骑士发放金币 ||

C描述 国王给骑士发放金币 || 国王将金币作为工资&#xff0c;发放给忠诚的骑士。第一天&#xff0c;骑士收到一枚金币&#xff1b;之后两天&#xff08;第二天和第三天&#xff09;&#xff0c;每天收到两枚金币&#xff1b;之后三天&#xff08;第四、五、六天&#xff09;&a…

jzoj3519-灵能矩阵【LCM,树形dp】

正题 题目大意 一棵树&#xff0c;每个叶子节点有权值&#xff0c;每个点的权值是它这棵子树中的所有叶子节点权值之和。可以减少叶子节点的值&#xff0c;要求减少最少的值使得对于每个点&#xff0c;它的所有子节点的权值都相等。 解题思路 如果将叶子节点的深度优先访问顺…

微软Tech Summit 2017,等你来打Call

2017年10月31至11月3日&#xff0c;由微软举办的Tech Summit 2017技术暨生态大会将在北京盛大举办&#xff0c;要在北京连开四天。今年的技术大会看头十足&#xff0c;不仅有大咖级人物带来十二大主题课程&#xff0c;更有三天四场的主题之夜。微软技术大会最早是由微软技术教育…

C++描述杭电OJ 2021.发工资 ||

C描述杭电OJ 2021.发工资 || Problem Description 财务处的小胡老师最近就在考虑一个问题&#xff1a;如果每个老师的工资额都知道&#xff0c;最少需要准备多少张人民币&#xff0c;才能在给每位老师发工资的时候都不用老师找零呢&#xff1f; 这里假设老师的工资都是正整数…

ABP从入门到精通(5):.扩展国际化语言资源

ABP的有些组件使用的该组件自带的语言包资源&#xff0c;所以在有些时候会因为我们当前使用的语言对应的语言包不全&#xff0c;而造成日志一直记录WARN。ABP给我们提供了扩展语言包资源的接口&#xff0c;可以解决这个问题。 以下示例代码适用于ABP .net core版本。我要为名为…

欢乐纪中某B组赛【2018.12.22】

前言 全暴力第9了解一下&#xff0c; 成绩 RankRankRank是有算别人的 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC9992017myself2017myself2017myself71.471.471.436.436.436.43030305559992017xxy2017xxy2017xxy71.471.471.436.436.436.43030305551111112017zyc…

【Mysql】mysql基本操作

创建 创建拥有三个字段的表单 create table qq(id int primary key auto_increment ,username varchar(100) ,password varchar(100) )DEFAULT CHARSETutf8mb4;关于编码问题&#xff0c;如果显示问号&#xff0c;则可以在后面加个utf8 CREATE TABLE IF NOT EXISTS father_m…

用数组遍历二叉树

#include<bits/stdc.h> using namespace std;void fun(char bt[],char c) {int i0;while(bt[i]!\n){if(bt[i]!c) i;else break;}int nstrlen(bt);if(in){cout<<"没有该结点"<<endl;}else if(i0){cout<<"该节点为根&#xff0c;没有双亲…

CoreCLR源码探索(八) JIT的工作原理(详解篇)

在上一篇 我们对CoreCLR中的JIT有了一个基础的了解,这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/master/src/jit下, 要对一个的函数的JIT过程进行详细分析, 最好的办法是查看JitDump. 查看JitDump需要自己编译一个Debug版本的…

P2607-[ZJOI2008]骑士【基环树,树形dp】

正题 题目大意 每个骑士有一个不可以同时上场的骑士&#xff0c;和一个战斗力。求最大战斗力。 解题思路 类似没有上司的舞会 其实就是在基环树森林&#xff0c;我们可以利用二次树形dp的方法。 先找到环&#xff0c;然后强行将环断开进行一次dp&#xff0c;然后强行连上进行…

【php】正则无法截取\反斜杠的解决方法

今天想将上次的图片上传功能优化一下&#xff0c;遇到一个问题&#xff0c;获取临时文件的名字时&#xff0c;由于名字中不能有\&#xff0c;所以必须对获取的文件名进行剪切&#xff0c;本人没有系统学习过php&#xff0c;所以有些知识点还是得重新学习。 获取到得字符串&…

Java GUI界面

package pac1;import java.awt.*; import java.awt.event.*; import javax.swing.*;public class Jlogin extends JFrame implements ActionListener{JButton oknew JButton("确定");JButton cancelnew JButton("取消");JTextField userNameTextnew JTextF…

HighChart模拟点击series的name显示隐藏

一、需求 HighChart模拟点击series的name显示隐藏&#xff0c;批量操作 二、代码实现 function toggleHighChart(show) {var charts Highcharts.charts;for(var i in charts){var chart charts[i];var series chart.options.series;for(var j in series){if(series[j].in){…

ZOJ1041-Transmitters【差积,计算几何】

正题 题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId41 luogu也可以捞一把: https://www.luogu.org/problemnew/show/SP898 题目大意 一个雷达&#xff0c;可以扫半圆&#xff0c;给半径&#xff0c;可以转动雷达。给若干个点&#xff0c;求最多可以…

学习Identity Server 4的预备知识

我要使用asp.net core 2.0 web api 搭建一个基础框架并立即应用于一个实际的项目中去.这里需要使用identity server 4 做单点登陆.下面就简单学习一下相关的预备知识. 基于Token的安全验证体系 这个比较简单, 简单来说就是为了证明我们有访问权限, 我们首先需要获得一个token. …

【FTP】发布FTP服务器

FTP是用来传输文件的一种协议&#xff0c;类似于http 这次主要是用以下两个软件来完成局域网文件的传输 FileZillaServer.zip&#xff1a;开启FTP服务 FlashFXP54Setup.exe&#xff1a;更加便捷的传输上传文件 链接 链接&#xff1a;https://pan.baidu.com/s/1zTMXUO4EPD9l7_LH…

C/C++输入输出流

istream中的类&#xff08;如cin&#xff09;提供了一些面向行的类成员函数&#xff1a;getline()和get()。这两个函数都读取一行输入&#xff0c;直到达到换行符。不同的是&#xff0c;getline()将丢弃换行符&#xff0c;而get()将换行符保留在输入序列中。 目录 一、字符串…

帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

转载自 帮你彻底搞懂JS中的prototype、__proto__与constructor&#xff08;图解&#xff09;   作为一名前端工程师&#xff0c;必须搞懂JS中的prototype、__proto__与constructor属性&#xff0c;相信很多初学者对这些属性存在许多困惑&#xff0c;容易把它们混淆&#xff…

ssl1715-计算面积【差积】

正题 题目大意 给一个平行四边形的3个点&#xff0c;求最大面积。 解题思路 明显答案就是差积*2的绝对值。 codecodecode #include<cstdio> #include<algorithm> #include<cmath> #define db double using namespace std; int n; struct node{db x,y; }p[…