前言
本文主要是演示一個例子,服務器后台程序從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/
安卓客戶端,主要是訂閱器的實現和同步客戶端的實現
