从云服务器访问本地PLC
本节内容主要介绍从云服务器上进行读写本地PLC功能,首先我们来想想看为什么会有这个需求?首先一般的场景都是本地有很多的PLC或是仪表设备,然后我们实现的功能是在云服务器上的程序获取到本地PLC的数据,
或是发送命令写入数据到本地的PLC里,在这里我们可以使用的一个方案是,云服务器上创建一个MQTTServer,然后本地写个小软件,小软件从PLC采集数据,发布给MQTTServer,然后订阅一个用于写入这个PLC的主题,
例如 "PLCA/WriteInt16"
, 参数payload可以定义为 { "address": "D100", "value": 100 }
,服务器端只要发布该主题,本地的小软件就收到了写入的主题的数据,然后调用PLC上的
plc.Write("D100", (short)100);
就可以写入PLC了。但是本章节介绍另一个路子,使用DTU转发来实现,好处就是可以丛云服务器上体验到本地访问PLC一样的体验。
使用DEMO快速体验
快速体验需要V12.1.0
及以上版本的demo程序,请确保程序的版本是匹配的,然后场景是本地有个三菱PLC,我们现在需要从云服务器读写本地的三菱PLC。那么第一步就是在云服务器上线运行Demo程序,打开三菱的界面,
然后选择DTU
管道,然后修改DTU参数信息,其中的ID
用来标记不同的连接设备的,密码
是用来做二次验证不正确的连接的。具体截图如下所示:然后点击连接
按钮后等待DTU的连接操作

你得云服务器的端口号可能通信受限制的,需要登录云服务器的后台管理系统,然后安全组策略里,放行10000端口就OK了。
云服务器侧我们已经弄好了,然后本地的PC上也要运行这个DEMO程序,这时候本地的PC需要同时连接着本地的三菱PLC,然后另一个网卡连接着外网,这时候我们打开V12.1.0
及以上版本的demo程序,
打开TCP调式
界面,进入如下的输入操作

先确认云服务器的DEMO程序已经点击连接按钮

然后点击打开本地的DTU转发功能。

这时候我们就可以在云服务器上读写PLC数据,就像本地读写一样的体验

我们再来看下本地的调试界面发生了什么事情。

使用第三方时自定义注册包
实际中在设备现场是一个专门的DTU模块,一个带4G联网功能的小盒子,这时候需要配置连接到我们的云服务器的IP地址和端口,然后配置自定义的注册包,这个注册包就是下图的内容

我们看到注册包里 48534C00193132333435363738393031313233343536C0A8000110270100
包含了一些ID信息,密码信息,连接暗号之类的,具体的规则如下:

当然手动拼接这样的数据包我也觉得麻烦,都是一串数字,输入多了很容易看错,所以可以在DEMO
程序里直接计算生成操作。

使用写代码来实现
上面的云服务器的功能是直接使用DEMO测试的,云服务器侧的代码可以从DEMO上简单的看到示例代码,如下所示:

然后把上面的代码复制出来,新建一个控制台项目,后面再加几行代码,如下所示:
class Program
{
static void Main( string[] args )
{
HslCommunication.Profinet.Melsec.MelsecMcNet plc = new HslCommunication.Profinet.Melsec.MelsecMcNet( );
plc.NetworkNumber = 0;
plc.NetworkStationNumber = 0;
plc.TargetIOStation = 1023;
plc.EnableWriteBitToWordRegister = false;
plc.ByteTransform.IsStringReverseByteWord = false;
NetworkAlienClient dtuServer = new NetworkAlienClient( );
dtuServer.IsResponseAck = true;
dtuServer.SetPassword( Encoding.ASCII.GetBytes( "123456" ) );
dtuServer.OnClientConnected += ( PipeDtuNet session ) =>
{
if (session.DTU == "12345678901")
{
OperateResult connect = plc.SetDtuPipe( session );
if (connect.IsSuccess)
{
Console.WriteLine( "connect success" );
}
}
};
dtuServer.ServerStart( 10000 );
plc.SetDtuPipe( new PipeDtuNet( ) ); // 在连接上来之前,先给一个默认的空的DTU会话
while(true)
{
HslHelper.ThreadSleep( 1000 );
OperateResult<short> read = plc.ReadInt16( "D0" );
if (read.IsSuccess)
{
Console.WriteLine( DateTime.Now.ToString( ) + " Read Value: " + read.Content );
}
else
{
Console.WriteLine( DateTime.Now.ToString( ) + " Read failed: " + read.Message );
}
}
}
}
这个就是我们运行在云服务器的代码程序了,然后启动这个程序,本地的也运行TCP转DTU
界面,这时候,我们就看到了采集到正确的数据

我们看到上面的运行结果里,一开始运行的时候,DTU还没连接上来,过了几秒钟后,连接上来就自动成功了。如果后面网络异常的话,会继续采集失败,等待DTU连接上来才能成功。
如果有两个设备需要采集
如果我们还有个modbusrtu的设备需要这么采集呢?我们看到有个DTU ID
信息,可以用来标记不同的设备,我们再增加一个ID为 12345678902 的ModbusRtu设备,
服务器端的代码就需要修改下了,如下所示
class Program
{
static void Main( string[] args )
{
HslCommunication.Profinet.Melsec.MelsecMcNet plc = new HslCommunication.Profinet.Melsec.MelsecMcNet( );
plc.NetworkNumber = 0;
plc.NetworkStationNumber = 0;
plc.TargetIOStation = 1023;
plc.EnableWriteBitToWordRegister = false;
plc.ByteTransform.IsStringReverseByteWord = false;
HslCommunication.ModBus.ModbusRtu modbus = new HslCommunication.ModBus.ModbusRtu( );
modbus.AddressStartWithZero = true;
modbus.IsStringReverse = false;
modbus.DataFormat = HslCommunication.Core.DataFormat.CDAB;
modbus.Station = 1;
modbus.Crc16CheckEnable = true;
modbus.IsClearCacheBeforeRead = false;
modbus.StationCheckMacth = true;
NetworkAlienClient dtuServer = new NetworkAlienClient( );
dtuServer.IsResponseAck = true;
dtuServer.SetPassword( Encoding.ASCII.GetBytes( "123456" ) );
dtuServer.OnClientConnected += ( PipeDtuNet session ) =>
{
if (session.DTU == "12345678901")
{
OperateResult connect = plc.SetDtuPipe( session );
if (connect.IsSuccess)
{
Console.WriteLine( "Melsec connect success" );
}
}
else if (session.DTU == "12345678902") // 这里的ID和上面的
{
OperateResult connect = modbus.SetDtuPipe( session );
if (connect.IsSuccess)
{
Console.WriteLine( "Modbus connect success" );
}
}
};
dtuServer.ServerStart( 10000 );
plc.SetDtuPipe( new PipeDtuNet( ) ); // 在连接上来之前,先给一个默认的空的DTU会话
modbus.SetDtuPipe( new PipeDtuNet( ) ); // 在连接上来之前,先给一个默认的空的DTU会话
// 这时候,我们就需要创建两个线程来访问两个PLC了
Thread thread1 = new Thread( new ParameterizedThreadStart( ThreadReadMelsec ) );
thread1.IsBackground = true;
thread1.Start( plc );
Thread thread2 = new Thread( new ParameterizedThreadStart( ThreadReadModbus ) );
thread2.IsBackground = true;
thread2.Start( modbus );
// Console.ReadLine( ); 在某些情况下这行代码仍然会退出
while(true)
{
HslHelper.ThreadSleep( 1000 );
}
}
private static void ThreadReadMelsec( object obj )
{
if (obj is HslCommunication.Profinet.Melsec.MelsecMcNet plc)
{
while (true)
{
HslHelper.ThreadSleep( 1000 );
OperateResult<short> read = plc.ReadInt16( "D0" );
if (read.IsSuccess)
{
Console.WriteLine( DateTime.Now.ToString( ) + " Melsec Read[D0] Value: " + read.Content );
}
else
{
Console.WriteLine( DateTime.Now.ToString( ) + " Modbus Read[D0] failed: " + read.Message );
}
}
}
}
private static void ThreadReadModbus( object obj )
{
if (obj is HslCommunication.ModBus.ModbusRtu modbus)
{
while (true)
{
HslHelper.ThreadSleep( 1000 );
OperateResult<short> read = modbus.ReadInt16( "100" );
if (read.IsSuccess)
{
Console.WriteLine( DateTime.Now.ToString( ) + " Modbus Read[100] Value: " + read.Content );
}
else
{
Console.WriteLine( DateTime.Now.ToString( ) + " Modbus Read[100] failed: " + read.Message );
}
}
}
}
}
我们看到有更多的设备也没有关系的,都是可以直接连接操作的。