前言


本文主要是演示一个例子,服务器后台程序从PLC采集数据,并推送给在线客户端显示,以及推送给web端进行实时的显示,还支持远程操作,支持安卓端的同步监视和远程操作,关于HslCommunication的相关资料如下

nuget地址:https://www.nuget.org/packages/HslCommunication/            

github地址:https://github.com/dathlin/HslCommunication                                 如果喜欢可以star或是fork,还可以打赏支持。

联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:

Install-Package HslCommunication

NuGet安装教程  http://www.cnblogs.com/dathlin/p/7705014.html

本项目的源代码地址:https://github.com/dathlin/RemoteMonitor

下面放几张截图:

服务器端的界面:

winform客户端地址:

web端的界面

所有的界面

安卓端

设计逻辑:

服务器端


主要是由数据订阅器,后台循环读取线程,在线管理器,同步网络交互网络组成。如下大致说一下各自负责的功能块:

数据订阅器

        /****************************************************************************************************************
*
* 本模块主要负责进行数据的发布。只要客户端订阅了相关的数据,服务器端进行推送后,客户端就可以收到数据
*
* 因为本订阅器目前只支持字符串的数据订阅,所以在这里需要将byts[]转化成base64编码的数据,相关的知识请自行百度,此处不再说明
*
*****************************************************************************************************************/ private NetPushServer pushServer = null; // 订阅发布核心服务器 private void NetPushServerInitialization( )
{
pushServer = new NetPushServer( );
pushServer.LogNet = LogNet;
pushServer.ServerStart( 23467 );
}

当你想要推送数据的时候,按照下面的方法

pushServer.PushString( "A", "这是测试数据");    // 推送数据,关键字为A

推送的测试数据到时候换成实际的数据即可。

后台循环读取数据

        /***************************************************************************************************************
*
* 以下演示了西门子的读取类,对于三菱和欧姆龙,或是modbustcp来说,逻辑都是一样的,你也可以尝试着换成三菱的类,来加深理解
*
*****************************************************************************************************************/ private SiemensS7Net siemensTcpNet; // 西门子的网络访问器
private bool isReadingPlc = false; // 是否启动的标志,可以用来暂停项目
private int failed = 0; // 连续失败此处,连续三次失败就报警
private Thread threadReadPlc = null; // 后台读取PLC的线程 private void SiemensTcpNetInitialization( )
{
siemensTcpNet = new SiemensS7Net( SiemensPLCS.S1200 ); // 实例化西门子的对象
siemensTcpNet.IpAddress = "192.168.1.195"; // 设置IP地址
siemensTcpNet.LogNet = LogNet; // 设置统一的日志记录器
siemensTcpNet.ConnectTimeOut = 1000; // 超时时间为1秒 // 启动后台读取的线程
threadReadPlc = new Thread( new System.Threading.ThreadStart( ThreadBackgroundReadPlc ) );
threadReadPlc.IsBackground = true;
threadReadPlc.Priority = ThreadPriority.AboveNormal;
threadReadPlc.Start( );
} private Random random = new Random( );
private bool isReadRandom = false; private void ThreadBackgroundReadPlc( )
{ // 此处假设我们读取的是西门子PLC的数据,其实三菱的数据读取原理是一样的,可以仿照西门子的开发 /**************************************************************************************************
*
* 假设一:M100,M101存储了一个温度值,举例,100.5℃数据为1005
* 假设二:M102存储了设备启停信号,0为停止,1为启动
* 假设三:M103-M106存储了一个产量值,举例:12345678
*
**************************************************************************************************/ while (true)
{
if (isReadingPlc)
{ // 这里仅仅演示了西门子的数据读取
// 事实上你也可以改成三菱的,无非解析数据的方式不一致而已,其他数据推送代码都是一样的 HslCommunication.OperateResult<JObject> read = null; //siemensTcpNet.Read( "M100", 7 ); if (isReadRandom)
{
// 当没有测试的设备的时候,此处就演示读取随机数的情况
read = HslCommunication.OperateResult.CreateSuccessResult( new JObject( )
{
{"temp",new JValue(random.Next(2000)/10d) },
{"enable",new JValue(random.Next(100)>10) },
{"product",new JValue(random.Next(10000)) }
} );
}
else
{
HslCommunication.OperateResult<byte[]> tmp = siemensTcpNet.Read( "M100", 7 );
if(tmp.IsSuccess)
{
double temp1 = siemensTcpNet.ByteTransform.TransInt16( tmp.Content, 0 ) / 10.0;
bool machineEnable = tmp.Content[2] != 0x00;
int product = siemensTcpNet.ByteTransform.TransInt32( tmp.Content, 3 ); read = HslCommunication.OperateResult.CreateSuccessResult( new JObject( )
{
{"temp",new JValue(temp1) },
{"enable",new JValue(machineEnable) },
{"product",new JValue(product) }
} );
}
else
{
read = HslCommunication.OperateResult.CreateFailedResult<JObject>( tmp );
}
} if (read.IsSuccess)
{
failed = 0; // 读取失败次数清空
pushServer.PushString( "A", read.Content.ToString() ); // 推送数据,关键字为A
ShowReadContent( read.Content ); // 在主界面进行显示,此处仅仅是测试,实际项目中不建议在服务端显示数据信息 }
else
{
failed++;
ShowFailedMessage( failed ); // 显示出来读取失败的情况
}
} Thread.Sleep( 500 ); // 两次读取的时间间隔
}
} // 只是用来显示连接失败的错误信息
private void ShowFailedMessage(int failed)
{
if(InvokeRequired)
{
Invoke(new Action<int>(ShowFailedMessage), failed);
return;
} textBox1.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff ") + "第" + failed + "次读取失败!" + Environment.NewLine);
} // 读取成功时,显示结果数据
private void ShowReadContent(JObject content)
{
// 本方法是考虑了后台线程调用的情况
if(InvokeRequired)
{
// 如果是后台调用显示UI,那么就使用委托来切换到前台显示
Invoke(new Action<JObject>(ShowReadContent), content);
return;
} // 提取数据
double temp1 = content["temp"].ToObject<double>( );
bool machineEnable = content["enable"].ToObject<bool>( );
int product = content["product"].ToObject<int>( ); // 实际项目的时候应该在此处进行将数据存储到数据库,你可以选择MySql,SQL SERVER,ORACLE等等
// SaveDataSqlServer( temp1 ); // 此处演示写入了SQL 数据库的方式 // 开始显示
label2.Text = temp1.ToString();
label2.BackColor = temp1 > 100d ? Color.Tomato : Color.Transparent; // 如果温度超100℃就把背景改为红色
label3.Text = product.ToString(); // 添加到缓存数据
AddDataHistory( (float)temp1 ); label5.Text = machineEnable ? "运行中" : "未启动";
}

这里面包含了后台的循环读取并且显示的操作,还支持了在没有实际的设备的情况下,直接用模拟数据来测试的情况。

这里为什么直接使用json对象来传送呢,是为了包装成一个统一的模型对象,方便后续的客户端直接进行提取操作。

在线客户端管理模块

这部分的功能需要根据实际情况来确认,如果不需要,可以直接删除,这部分的功能主要是实现在服务器端对所有在线的winform程序的在线情况进行掌控,并可以在客户端刚上线的时候处理一些事情。

        /*****************************************************************************************************
*
* 特别说明:在线网络的模块的代码主要是为了支持服务器对客户端在线的情况进行管理
*
* 当客户端刚上线的时候,服务器也可以发送一些初始数据给客户端
*
*****************************************************************************************************/ private NetComplexServer netComplex; // 在线网络管理核心 private void NetComplexInitialization( )
{
netComplex = new NetComplexServer( ); // 实例化
netComplex.AcceptString += NetComplex_AcceptString; // 绑定字符串接收事件
netComplex.ClientOnline += NetComplex_ClientOnline; // 客户端上线的时候触发
netComplex.ClientOffline += NetComplex_ClientOffline; // 客户端下线的时候触发
netComplex.LogNet = LogNet; // 设置日志
netComplex.ServerStart( 23456 ); // 启动网络服务 } private void NetComplex_ClientOffline( AppSession session, string object2 )
{
// 客户端下线的时候触发方法
RemoveOnLine( session.ClientUniqueID );
} private void NetComplex_ClientOnline( AppSession session )
{
// 回发一条初始化数据的信息
netComplex.Send( session, 2, GetHistory( ) );
// 有客户端上限时触发方法
NetAccount account = new NetAccount( )
{
Guid = session.ClientUniqueID,
Ip = session.IpAddress,
Name = session.LoginAlias,
OnlineTime = DateTime.Now,
}; AddOnLine( account );
} private void NetComplex_AcceptString( AppSession stateone, HslCommunication.NetHandle handle, string data )
{
// 接收到客户端发来的数据时触发 }

  

本例子里还包含了在线的客户端账号信息,支持扩展额外的信息登录

private List<NetAccount> all_accounts = new List<NetAccount>( );
private object obj_lock = new object( ); // 新增一个用户账户到在线客户端
private void AddOnLine( NetAccount item )
{
lock (obj_lock)
{
all_accounts.Add( item );
}
UpdateOnlineClients( );
} // 移除在线账户并返回相应的在线信息
private void RemoveOnLine( string guid )
{
lock (obj_lock)
{
for (int i = 0; i < all_accounts.Count; i++)
{
if (all_accounts[i].Guid == guid)
{
all_accounts.RemoveAt( i );
break;
}
}
}
UpdateOnlineClients( );
} /// <summary>
/// 更新客户端在线信息
/// </summary>
private void UpdateOnlineClients( )
{
if (InvokeRequired && IsHandleCreated)
{
Invoke( new Action( UpdateOnlineClients ) );
return;
} lock (obj_lock)
{
listBox1.DataSource = all_accounts.ToArray( );
}

账号的类为

    /// <summary>
/// 用于在线控制的网络类
/// </summary>
public class NetAccount
{
/// <summary>
/// 唯一ID
/// </summary>
public string Guid { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string Ip { get; set; }
/// <summary>
/// 上线时间
/// </summary>
public DateTime OnlineTime { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } private string GetOnlineTime()
{
TimeSpan ts = DateTime.Now - OnlineTime;
if (ts.TotalSeconds < 60)
{
return ts.Seconds + " 秒";
}
else if(ts.TotalHours < 1)
{
return ts.Minutes + "分" + ts.Seconds + "秒";
}
else if(ts.TotalDays < 1)
{
return ts.Hours + "时" + ts.Minutes + "分";
}
else
{
return ts.Days + "天" + ts.Hours + "时";
}
} /// <summary>
/// 字符串标识形式
/// </summary>
/// <returns></returns>
public override string ToString( )
{
return "[" + Ip + "] : 在线时间 " + GetOnlineTime( );
}
}

  

同步的网络模块

这部分的功能相当于一个接口功能,为了支持远程的客户端或是web端进行对PLC进行读写操作实现的,事实上,当远程的程序点击了启动XX功能之后,远程的代码就通过同步网络将消息都发送给了服务器,服务器再去操作PLC,然后在回发给远程结果。

        /*****************************************************************************************************
*
* 特别说明:同步网络模块,用来支持远程的写入操作,特点是支持是否成功的反馈,这个信号对客户端来说是至关重要的
*
* 不仅仅支持客户端的操作,还支持web端的操作。
*
*****************************************************************************************************/ private NetSimplifyServer netSimplify; // 同步网络访问的服务支持 private void NetSimplifyInitialization( )
{
netSimplify = new NetSimplifyServer( ); // 实例化
netSimplify.ReceiveStringEvent += NetSimplify_ReceiveStringEvent; // 服务器接收字符串信息的时候,触发
netSimplify.LogNet = LogNet; // 设置日志
netSimplify.ServerStart( 23457 ); // 启动服务
} private void NetSimplify_ReceiveStringEvent( AppSession session, HslCommunication.NetHandle handle, string msg )
{
if (handle == 1)
{
string tmp = StartPLC( );
LogNet?.WriteInfo( tmp );
// 远程启动设备
netSimplify.SendMessage( session, handle, tmp );
}
else if (handle == 2)
{
string tmp = StopPLC( );
LogNet?.WriteInfo( tmp );
// 远程停止设备
netSimplify.SendMessage( session, handle, tmp );
}
else
{
netSimplify.SendMessage( session, handle, msg );
}
}

  

客户端实现,

web端的数据推送实现,需要SignalR支持,关于这方面的技术细节,可以参考SignalR官网:https://www.asp.net/signalr

本项目还支持了简单的图表显示,图标的支持来源于百度的echart项目,http://echarts.baidu.com/

安卓客户端,主要是订阅器的实现和同步客户端的实现

使用HslCommunication实现PLC数据的远程客户端监视,以及web端实时监视,远程操作设备示例的更多相关文章

  1. PHP判断客户端是PC web端还是移动手机端方法

    PHP判断客户端是PC web端还是移动手机端方法需要实现:判断手机版的内容加上!c550x260.jpg后缀变成缩略图PHP用正则批量替换Img中src内容,用正则表达式获取图片路径实现缩略图功能 ...

  2. C#读写三菱PLC和西门子PLC数据 使用TCP/IP 协议

    本文将使用一个Github开源的组件库技术来读写三菱PLC和西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能 ...

  3. C# 读写西门子PLC数据,包含S7协议和Fetch/Write协议,s7支持200smart,300PLC,1200PLC,1500PLC

    本文将使用一个gitHub开源的组件技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作 官方 ...

  4. python 读写三菱PLC数据,使用以太网读写Q系列,L系列,Fx系列的PLC数据

    本文将使用一个gitHub开源的组件技术来读写三菱的plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作 gi ...

  5. java android 读写西门子PLC数据,包含S7协议和Fetch/Write协议,s7支持200smart,300PLC,1200PLC,1500PLC

    本文将使用一个gitHub开源的组件技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作 gi ...

  6. C#读写西门子PLC数据

    C#读写西门子PLC数据,包含S7协议和Fetch/Write协议,s7支持200smart,300PLC,1200PLC,1500PLC 本文将使用一个gitHub开源的组件技术来读写西门子plc数 ...

  7. C#读写三菱PLC数据 使用TCP/IP 协议

    本文将使用一个Github开源的组件库技术来读写三菱PLC和西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能 ...

  8. [置顶] Xamarin android 调用Web Api(ListView使用远程数据)

    xamarin android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...

  9. Linux 安装MongoDB 并设置防火墙,使用远程客户端访问

    1. 下载 MongoDB 提供了 linux 各发行版本 64 位的安装包  下载地址:https://www.mongodb.com/download-center#community 2. 安装 ...

随机推荐

  1. MR案例:Reduce-Join

    问题描述:两种类型输入文件:address(地址)和company(公司)进行一对多的关联查询,得到地址名(例如:Beijing)与公司名(例如:Beijing JD.Beijing Red Star ...

  2. The Startup Manager FAQ

    Main Features: 1.  Login Items: Manageable list of applications that are launched automatically ever ...

  3. [BZOJ2733]永无乡

    Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以 ...

  4. [Hdu6315]Naive Operations

    题意:给定一个初始数组b和一个初始值全部为0的数组a,每次操作可以在给定的区间(l,r)内让a[i](l=<i<=r)加一,或者查询区间区间(l,r)中a[i]/b[i](l=<i& ...

  5. Linux crontab命令 定时任务 用法详解以及no crontab for root解决办法

    最近系统服务器进行搬迁,又恰好需要使用定时任务运行程序,而我的程序主要使用PHP写的,然后总结了下定时任务的用法,但是在这里主要写的是关于crontab命令的用法,使用过程中遇到不少问题,例如no c ...

  6. Axis.Labels.CustomSize

    tChart1.Axes.Bottom.Labels.CustomSize = ; //Changes spacing occupied by the axis labels between the ...

  7. 初入spring boot(七 )Spring Data JPA

    Spring Data JPA通过提供基于JPA的Repository极大地减少JPA作为数据访问方案的代码量. 1.定义数据访问层 使用Spring Data JPA建立数据访问层十分简单,只需定义 ...

  8. 第六篇:Spark SQL Catalyst源码分析之Physical Plan

    /** Spark SQL源码分析系列文章*/ 前面几篇文章主要介绍的是spark sql包里的的spark sql执行流程,以及Catalyst包内的SqlParser,Analyzer和Optim ...

  9. Java的历史及发展

    Java之父:詹姆斯·高斯林 (James Gosling) Java自1995诞生,至今已经20多年的历史. Java的名字的来源:Java是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名.Java语 ...

  10. Memcached get 命令

    Memcached get 命令获取存储在 key(键) 中的 value(数据值) ,如果 key 不存在,则返回空. 语法: get 命令的基本语法格式如下: get key 多个 key 使用空 ...