2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > c#如何实现RTU远程数据采集功能及RTU在水利工程中的运用

c#如何实现RTU远程数据采集功能及RTU在水利工程中的运用

时间:2019-11-01 20:08:30

相关推荐

c#如何实现RTU远程数据采集功能及RTU在水利工程中的运用

好久没有动手去写博客了,近两年时间忙碌着工作,未曾回过头细数做过的路程,感觉有点思想颓废了,之前一直从事的水利工程类开发及实施工作,考虑各方面情况下,从开始转行投入到电力智能电网的软件开发工作,一转行就到了如今,虽行业上跨度自我感觉挺大的,不过对于一个从事软件开发工作者来说,从了解客户需求、工程实施,甚至是编码开发到后期维护都是大同小异的.

讲了这么多的废话,现在开始今天的主题了,在水利工程中,C#如何实现RTU对现场设备数据进行采集及控制功能。如果您对RTU不了解的话,先可以看下相关资料/view/430166.htm 。

如果您是现场实施人员,你需要了解RTU硬件接口、基本通讯协议、布线安装、调试维护等信息,一般购买RTU时,厂家会提供资料。

如果您是开发人员,你需要了解RTU支持哪些通讯协议(Modbus/TCP/485/232/串口...),及开发时所需的资料(开发包,调试工具),这部分资料厂家也会在购买时提供。

RTU设备:

设备是我朋友公司自己做的,跟南瑞、基康RTU产品一样,呵呵.反正都差不多啦!

工程名称:

谷城县潭口水库水雨情遥测系统

【湖北一个地方性水库的项目,这里RTU主要作用是RTU通过458连接现场所有测点传感器、雨量计、水位计、闸门等设备,通过RTU的GPRS模块,将数据实时转发到互联网的指定IP的指定端口上,接收端通过以太网(TCP/IP协议)将数据传输到监控室,上位机软件接收数据及根据数据实现各种业务】

关于RTU通讯协议:

RTU的通讯协议基于MODBUS协议,至于协议说明在后面的博客里面会提及到,我这里只讲一点数据帧格式,因为后面的接收数据的部分解析会用到。

具体的帧格式如下:

modbus RTU 地址域 功能码 数据 差错校验

modbus TCP 目的地址 协议id 长度 单元号 功能码 数据

简单的说 tcp是由RTU加工而来的,而RTU则是另外一种概念,不包含在modbus协议内,是工控行业对监控设备的简称。

下面开始着重讲C#实现数据采集通讯模块的编码了。在上位机获取指定IP的指定端口数据之前,需要先了解Socket协议,基于TCP/IP的通讯方式,本文就不提了,以后有时间会在专栏中详细说道。实时获取数据,采用多线程非阻塞形式,一个连接连接对应一个线程,数据获取后,实时显示到指定的控件上及输出到监控窗口位置,最后立即释放该子线程资源,并等待下次连接。

程序采用MVC三层来写的,主要是考虑后期功能扩展及可移植性、通用性等情况。下面开始编码:

1、编写客户端操作类:该类主要实现连接断开服务器,收发数据及数据业务逻辑处理等。

using System;

using System.Collections.Generic;

using System.Text;

using .Sockets;

using ;

namespace RTU

{

public class Client : IDisposable

{

#region DataMember & Ctor

private TcpClient tcpclient;

private NetworkStream netstream;

public string IP;

public string Port;

private byte[] readBytes;

private object tcpClientLock = new object();//锁TcpClient;Dispose之后就不允许EndRead,远程连接断开以后,就不允许再调用Dispose

private bool closed = false;//包括本地主动断开和远程断开

//在事务处理结束后才触发下列事件

public event DlgNoParam ConnectFailEvent;

public event DlgOneParam<string> NewClientEvent;

public event DlgOneParam<string> RecvMsgEvent;

public event DlgOneParam<string> RemoteDisconnectEvent;

public event DlgNoParam LocalDisconnectEvent;

/// <summary>

/// 服务的client的构造函数

/// </summary>

/// <param name="tcpclient"></param>

public Client(TcpClient tcpclient)

{

this.tcpclient = tcpclient;

readBytes = new byte[tcpclient.ReceiveBufferSize];//接收数据的缓冲区大小,如果太小一段数据会多次接收完成

netstream = tcpclient.GetStream();//如果远程客户端断开一样可以获得netstream

}

/// <summary>

/// 客户端client的构造函数

/// </summary>

public Client()

{

}

#endregion

#region 数据收发

/// <summary>

/// 在建立连接的情况下发送消息

/// </summary>

/// <param name="msg"></param>

/// <returns></returns>

public bool SendMsg(string msg)

{

bool result = false;

try

{

if (!string.IsNullOrEmpty(msg))

{

byte[] buff = Encoding.Default.GetBytes(msg);

netstream.Write(buff, 0, buff.Length);

result = true;

}

}

catch

{

throw;

}

return result;

}

/// <summary>

/// 服务的接收到客户端连接后最先做的操作之一,客户端接收数据线程起始

/// </summary>

/// <returns></returns>

public bool BeginRead()

{

bool result = false;

try

{

IP = (tcpclient.Client.RemoteEndPoint as IPEndPoint).Address.ToString();

Port = (tcpclient.Client.RemoteEndPoint as IPEndPoint).Port.ToString();

if (NewClientEvent != null)

{

NewClientEvent(IP + " " + Port);

}

netstream.BeginRead(readBytes, 0, readBytes.Length, EndRead, null);//如果远程客户端断开这句话一样可以执行

result = true;

}

catch

{

throw;

}

return result;

}

/// <summary>

/// 有互斥资源

/// 接收数据,远程连接断开,远程程序关闭,本地连接断开,都会按顺序调用进来;因为连接关闭后不再调用BeginRead

/// 服务器listener.stop时不会进入这个函数,客户端照样通讯,服务端只是不能接收新连接而已

/// </summary>

/// <param name="ar"></param>

private void EndRead(IAsyncResult ar)

{

lock (tcpClientLock)

{

if (!closed)//如果本地主动断开就不会进入

{

try

{

int count = netstream.EndRead(ar);

if (count > 0)

{

//string recvStr = Encoding.Default.GetString(readBytes, 0, count);

string recvStr="";

List<String> list_str = new List<String>();

//把接受的数据转换成16进制字符串

for (int i = 0; i < count; i++)

{

String strtemp = Convert.ToString(readBytes[i], 16);

while (strtemp.Length < 2)

{

strtemp = "0" + strtemp;

}

if (String.IsNullOrEmpty(recvStr))

{

recvStr += strtemp;

}

else

{

recvStr += " " + strtemp;

}

list_str.Add(strtemp);

}

recvStr = DateTime.Now.ToString("HH:mm:ss") + " [" + IP + " " + Port + "] :\r\n" + recvStr + "\r\n\r\n";

if (RecvMsgEvent != null)

{

RecvMsgEvent(recvStr);

}

readBytes = new byte[tcpclient.ReceiveBufferSize];

netstream.BeginRead(readBytes, 0, readBytes.Length, EndRead, null);

}

else//远程客户端主动断开

{

LocalClientClose();

}

}

catch (Exception ex)

{

if (ex.Message.Contains("无法从传输连接中读取数据: 远程主机强迫关闭了一个现有的连接"))

{

LocalClientClose();

}

else

{

throw;

}

}

}

}

}

#endregion

#region 客户端连接和关闭

/// <summary>

/// 客户端的client连接服务器

/// </summary>

/// <param name="ip"></param>

/// <param name="port"></param>

/// <returns></returns>

public bool Connect(string ip, string port)

{

bool result = false;

try

{

ip = ip.Trim();

port = port.Trim();

if (!string.IsNullOrEmpty(ip) && !string.IsNullOrEmpty(port))

{

IPAddress ipAddress = IPAddress.Parse(ip);

IPEndPoint point = new IPEndPoint(ipAddress, int.Parse(port));

tcpclient = new TcpClient(AddressFamily.InterNetwork);

readBytes = new byte[tcpclient.ReceiveBufferSize];

tcpclient.Connect(point);

netstream = tcpclient.GetStream();

closed = false;

result = true;

}

}

catch (Exception ex)

{

if (ex.Message.Contains("由于目标机器积极拒绝,无法连接"))

{

if (ConnectFailEvent != null)

{

ConnectFailEvent();

}

}

else

{

}

}

return result;

}

/// <summary>

/// 远程连接断开后(点关闭断开,程序退出断开)本地连接处理

/// </summary>

private void LocalClientClose()

{

closed = true;

DisposeEx();

if (RemoteDisconnectEvent != null)

{

string param = IP + " " + Port;

if (RemoteDisconnectEvent != null)

{

RemoteDisconnectEvent(param);

}

}

}

/// <summary>

/// 有互斥资源

/// 在已连接条件下关闭本地连接和资源释放时调用

/// </summary>

public void Dispose()

{

lock (tcpClientLock)

{

if (!closed)

{

closed = true;

DisposeEx();

if (LocalDisconnectEvent != null)

{

LocalDisconnectEvent();

}

}

}

}

/// <summary>

/// 由Dispose和LocalClientClose调用

/// </summary>

private void DisposeEx()

{

if (netstream != null)

{

netstream.Dispose();

netstream = null;

}

if (tcpclient != null)

{

tcpclient.Close();

tcpclient = null;

}

}

#endregion

}

}

2、实现服务器操作类:服务器连接接收及监听端口数据、客户端操作。

using System;

using System.Collections.Generic;

using System.Text;

using .Sockets;

using System.Threading;

using ;

namespace RTU

{

public class Server : IDisposable

{

#region DataMember

private TcpListener listener;

private Client currentClient;

private object listenerLock = new object();//锁TcpListener;在BeginAcceptClient,EndAcceptClient,ServerClose这3函数使用;ServerClose之后不能再调用BeginAcceptClient,EndAcceptClient

private bool serverStart = false;

private EventWaitHandle eventWaitHdl = new EventWaitHandle(false, EventResetMode.ManualReset);

//在事务处理结束后才触发下列事件

public event DlgNoParam ServerStartEvent;

public event DlgNoParam ServerCloseEvent;

public event DlgOneParam<string> NewClientEvent;

public event DlgOneParam<string> RecvMsgEvent;

public event DlgNoParam LocalDisconnectEvent;

public event DlgOneParam<string> RemoteDisconnectEvent;

#endregion

#region 服务器监听启动关闭和连接接收

public bool Start(string ip, int port)

{

bool result = false;

try

{

if (!string.IsNullOrEmpty(ip) && port > 1024)

{

IPAddress ipAddr = IPAddress.Parse(ip);

IPEndPoint point = new IPEndPoint(ipAddr, port);

listener = new TcpListener(point);

listener.Start();

result = true;

serverStart = true;

if (ServerStartEvent != null)

{

ServerStartEvent();

}

}

}

catch

{

throw;

}

return result;

}

/// <summary>

/// 有互斥资源

/// 接收连接主入口,监听启动后调用和每次接收到连接后调用

/// </summary>

public void BeginAcceptClient()

{

try

{

while (true)

{

eventWaitHdl.Reset();

lock (listenerLock)

{

if (serverStart)

{

listener.BeginAcceptTcpClient(EndAcceptClient, null);

}

else

{

return;

}

}

eventWaitHdl.WaitOne();

}

}

catch

{

throw;

}

}

/// <summary>

/// 有互斥资源

/// 接收到客户端连接时调用到,关闭listener时也调用到

/// </summary>

/// <param name="ar"></param>

private void EndAcceptClient(IAsyncResult ar)

{

try

{

TcpClient tcpclient = null;

lock (listenerLock)

{

if (serverStart)

{

tcpclient = listener.EndAcceptTcpClient(ar);//在这句话之前或者client.BeginRead之前断掉远程客户端都没事,tcpclient都不为null

}

}

eventWaitHdl.Set();

if (tcpclient != null)//listener.stop后tcpclient == null,不会进入下面代码

{

InitClient(tcpclient);

}

}

catch

{

throw;

}

}

/// <summary>

/// 有互斥资源

/// 在listener监听状态下关闭listener时调用,资源释放时调用

/// </summary>

/// <returns></returns>

public bool ServerClose()

{

bool result = false;

try

{

lock (listenerLock)

{

if (serverStart)

{

serverStart = false;

listener.Stop();

result = true;

if (ServerCloseEvent != null)

{

ServerCloseEvent();

}

}

}

}

catch

{

throw;

}

return result;

}

#endregion

#region Client相关操作

private void InitClient(TcpClient tcpclient)

{

currentClient = new Client(tcpclient);

ClientDlgSubscribe(true);

currentClient.BeginRead();//即使在这之前服务器断开,该函数返回值也等于1

}

/// <summary>

/// InitClient,CurrentClient_LocalDisconnectEvent,Client_DisconnectEvent调用到

/// 在事务处理结束后调用到

/// </summary>

/// <param name="add"></param>

public void ClientDlgSubscribe(bool add)

{

if (add)

{

currentClient.NewClientEvent += new DlgOneParam<string>(Client_NewClientEvent);

currentClient.RecvMsgEvent += new DlgOneParam<string>(Client_RecvMsgEvent);

currentClient.RemoteDisconnectEvent += new DlgOneParam<string>(Client_DisconnectEvent);

currentClient.LocalDisconnectEvent += new DlgNoParam(CurrentClient_LocalDisconnectEvent);

}

else

{

currentClient.NewClientEvent -= new DlgOneParam<string>(Client_NewClientEvent);

currentClient.RecvMsgEvent -= new DlgOneParam<string>(Client_RecvMsgEvent);

currentClient.RemoteDisconnectEvent -= new DlgOneParam<string>(Client_DisconnectEvent);

currentClient.LocalDisconnectEvent -= new DlgNoParam(CurrentClient_LocalDisconnectEvent);

}

}

public bool SendMsg(string msg)

{

bool result = false;

try

{

if (currentClient.SendMsg(msg))

{

result = true;

}

}

catch

{

throw;

}

return result;

}

/// <summary>

/// 客户端连接情况下断开连接时调用,释放资源时调用

/// </summary>

/// <returns></returns>

public bool DisconnectClient()

{

bool result = false;

try

{

if (currentClient != null)

{

currentClient.Dispose();

result = true;

}

}

catch

{

throw;

}

return result;

}

public void Client_NewClientEvent(string param)

{

if (NewClientEvent != null)

{

NewClientEvent(param);

}

}

public void Client_RecvMsgEvent(string param)

{

if (RecvMsgEvent != null)

{

RecvMsgEvent(param);

}

}

public void Client_DisconnectEvent(string param)

{

ClientDlgSubscribe(false);

if (RemoteDisconnectEvent != null)

{

RemoteDisconnectEvent(param);

}

}

public void CurrentClient_LocalDisconnectEvent()

{

ClientDlgSubscribe(false);

if (LocalDisconnectEvent != null)

{

LocalDisconnectEvent();

}

}

#endregion

#region 资源释放

public void Dispose()

{

ServerClose();

DisconnectClient();

}

#endregion

}

}

3、客户端及服务器操作类已经完成了,现在开始实现基本的业务处理模块了。软件的实时监控界面如下:

【启动服务器】开启或者关闭服务器端口监听及数据处理功能,获取到的测站数据按照报文格式解析,分别显示到界面的各个Textbox里面,并在监控输出地方打印相关信息,这部分有专门的函数做业务处理,

void fun_startorstopser()

{

try

{

if (button1.Text.Trim().Equals("停止服务器"))

{

trueorfalst = false; try

{

socketclient.Shutdown(SocketShutdown.Both);

}

catch (Exception ex)

{

RTU.SyslogErr.WriteErr(ex);

}

socketclient.Close();

socketclient = null;

threadcon.Abort();

button1.Text = "启动服务器";

isopen = false;

textPRO.Enabled = true;

}

else

{

trueorfalst = true;

string ip = GetLocalIp();

IPAddress ip0 = IPAddress.Parse(ip);

IPEndPoint endPoint = new IPEndPoint(ip0, int.Parse(textPRO.Text.Trim()));

socketclient = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

RTU.SyslogErr.WriteErr("----------操作记录:启动服务器");

//将服务端与端口绑定

socketclient.Bind(endPoint);

//设定允许的排队队列大小

socketclient.Listen(300);

//线程列表中加入包含Accept的线程

//ThreadPool.QueueUserWorkItem(new WaitCallback(wacthprot));

//int a = 0, b = 0;

//ThreadPool.GetMaxThreads(out a, out b);

//MessageBox.Show(a.ToString() + "//" + b.ToString());

threadcon = new Thread(new ThreadStart(wacthprot));

threadcon.IsBackground = true;

threadcon.Start();

textPRO.Enabled = false;

button1.Text = "停止服务器";

//信息输出类

RTU.SyslogErr.WriteErr("----------服务器名:" + .Dns.GetHostName());

RTU.SyslogErr.WriteErr("----------服务器IP:" + ip);

RTU.SyslogErr.WriteErr("----------在" + textPRO.Text.Trim() + "端口启动服务 等待客户端连接!");

isopen = true;

}

}

catch (Exception ex)

{

txt_ljinfo.AppendText("异常:端口启动或关闭异常-" + ex.Message + "\n");

RTU.SyslogErr.WriteErr(ex);

}

}

RTU数据接收方式是定时上报模式,采用五分钟上传一次数据作为实时监控数据显示,整点上传数据做业务数据处理、比如日雨量数据显示、水位数据显示、报表输出及数据存储、数据查询与转发等功能。

数据报文格式是:

7E FF 01 0E 3F 12 08 25 11 00 00 F0 00 08 00 14 00 00 00 00 00 00 00 00 00 00 00 00 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 01 7A 14 33 00 00 00 00 00 00 00 00 00 00 00 03 DC FA

关于这段报文格式的说明,我还是把它截图下来具体说明下:

关于RTU远程数据采集功能的说明到这一步已经完成了,现在简单说明下雨水情监控系统相关说明,一套完整项目的雨水情监控系统,包含现场硬件设备之前,软件部分主要是包括雨水情监控系统跟WEB信息发布系统及LED实时屏显示软件部分。下面分开简单说明下各个系统的基本功能模块:

雨水情监控系统:

1、数据测控模块:

包含RTU数据采集及RTU控制功能功能,数据采集中包含数据解析、数据存储、数据显示等;

2、数据查询模块

包含日雨量数据、时段雨量数据、日水位数据、时段水位数据、RTU供电电压数据、水位对应溢洪道数据、水位对应库容数据、水文参数数据等;

3、数据曲线模块:

包含日/旬/月/年雨量、日/旬/月/年水位、及库容变化曲线等;

4、数据报表模块:

包含日/旬/月/年雨量数据报告导出、日/旬/月/年水位数据报告导出、支持自定义表格字段及自定义数据条件报表数据导出等;

5、RTU通讯检测模块:

包含RTU数据报文分析、网络通讯异常提醒用户、端口定时自检功能、故障记录日志数据记录等功能;

6、其他模块:

包含系统配置管理、用户管理、系统运行日志管理、权限管理、挂机锁等等;

关于数据传输:

采用GPRS无线网络传输方案:GPRS无线网络为主信道,利用现有固定IP的服务器.通过中国移动公网GPRS无线网络上传水位数据.雨量数据在本地服务器数据内,可直接读取。GSM短信为备用信道,在服务器上加装GSM短信模块。在信号较弱的时候,数据采集仪自动切换到GSM短信备用信道上传数据。预警及警报发布直接采用该信道下发预警、警报等信息。

WEB信息发布系统:

该系统的功能模块包含雨水情监测系统的模块功能,比如:数据查询、数据曲线、数据报表等等,同时也具备如下功能模块:

1、预警预报模块:

针对小型水库点多、面广、分布分散的特点,在地理信息系统平台上建设一个具有地图浏览、图上查询、报表输出、预警预报等基本功能的信息管理系统,在满足防汛、预警等基本功能的基础上构建信息查询统计、抗洪能力预估、防汛警报发布于一体的小型水库自动监控、预警平台。

2、抗洪能力预估模块:

根据工程现场降雨径流关系及当前影响雨量,自动计算各水库的可拦截水量,评估水库可承受的降雨量,对抗洪能力进行预估.做到雨前防汛心中有数。

LED实时屏显示软件:

该软件主要实时显示各个测点水位、雨量、库容、流量等等实时数据,并且具备自动快速响应预警功能,界面如图:

关于雨水情项目的一个实施基本情况说明已经完成了,如果您对这个项目感兴趣,欢迎留言或者QQ探讨交流!这个项目是的时候入行时候做的!现在偶然也会更新完善下这个项目软件部分的功能,关于更新的部分这里就不记录了,写这文章目的我想积累并记录下点滴经验,方便自己,方便他人!

————————————————

版权声明:本文为CSDN博主「囧囧虾克」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:/pan869823184/article/details/9156553

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。