最近通過WPF開發項目,為了對WPF知識點進行總結,所以利用業餘時間,開發一個學生信息管理系統【Student Information Management System】。前四篇文章進行了框架搭建和模塊劃分,後臺WebApi介面編寫,以及課程管理模塊,班級管理模塊,學生管理模塊的開發,本文在前四篇... ...
場景模擬
假設你有一批非標設備需要對接,對方提供瞭如下協議文檔:
協議概述
設備作為TCPServer,埠6666
位元組序:Little-Endian,即低地址存放低位
請求回覆
需要你主動發起讀取請求:0x01 02 03 04
設備回覆:0x08 01 41 D6 3D 71 1A 20
參數說明
- 總位元組數
(byte[0])即0x08:用於簡單的校驗
- 運行狀態
(byte[1])即0x01:1為運行;其他為停止
- 設備溫度
(byte[2]-byte[5])即0x41 D6 3D 71:單精度浮點數值26.78
- 電機轉速
(byte[6]-byte[7])即0x1A 20:對應16進位無符號整型,倍率0.01值66.88
驅動開發
我們根據上面的協議,開發驅動。請先瀏覽上一篇驅動簡介
創建驅動項目
-
在解決方案->Drivers文件夾,右鍵添加->新建項目->C#類庫
-
項目名DriverSimTcpClient,放在
iotgateway\Plugins\Drivers
路徑下
-
修改Class1為SimTcpClient
-
雙擊項目,修改配置
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputPath>../../../IoTGateway/bin/Debug/net6.0/drivers</OutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SimpleTCP.Core" Version="1.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\PluginInterface\PluginInterface.csproj" />
</ItemGroup>
</Project>
:::info 說明
OutputPath節點指定了生成項目的文件夾
SimpleTCP.Core是一個TCP客戶端庫(你也可以自己寫)
ProjectReference節點引用了PluginInterface項目
CopyLocalLockFileAssemblies節點可以確保你引用的nuget拷貝到driver文件夾下
:::
編寫項目代碼
using PluginInterface;
using SimpleTCP;
using System;
using System.Text;
namespace DriverSimTcpClient
{
[DriverSupported("SimTcpServerDevice")]
[DriverInfoAttribute("SimTcpClient", "V1.0.0", "Copyright iotgateway© 2022-06-04")]
public class SimTcpClient : IDriver
{
/// <summary>
/// tcp客戶端
/// </summary>
private SimpleTcpClient? client;
/// <summary>
/// 緩存最新的伺服器返回的原始數據
/// </summary>
private byte[] latestRcvData;
#region 配置參數
[ConfigParameter("設備Id")]
public Guid DeviceId { get; set; }
[ConfigParameter("IP地址")]
public string IpAddress { get; set; } = "127.0.0.1";
[ConfigParameter("埠號")]
public int Port { get; set; } = 6666;
/// <summary>
/// 為了演示枚舉類型在web端的錄入,這裡沒用到 但是你可以拿到
/// </summary>
[ConfigParameter("連接類型")]
public ConnectionType ConnectionType { get; set; } = ConnectionType.Long;
[ConfigParameter("超時時間ms")]
public int Timeout { get; set; } = 300;
[ConfigParameter("最小通訊周期ms")]
public uint MinPeriod { get; set; } = 3000;
#endregion
public SimTcpClient(Guid deviceId)
{
DeviceId = deviceId;
}
/// <summary>
/// 判斷連接狀態
/// </summary>
public bool IsConnected
{
get
{
//客戶端對象不為空並且客戶端已連接則返回true
return client != null && client.TcpClient.Connected;
}
}
/// <summary>
/// 進行連接
/// </summary>
/// <returns>連接是否成功</returns>
public bool Connect()
{
try
{
//進行連接
client = new SimpleTcpClient().Connect(IpAddress, Port);
client.DataReceived += Client_DataReceived;
}
catch (Exception)
{
return false;
}
return IsConnected;
}
/// <summary>
/// 收到服務端數據
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Client_DataReceived(object? sender, Message e)
{
//如果收到的數據校驗正確,則放在記憶體中
if (e.Data.Length == 8 && e.Data[0] == 0x08)
latestRcvData = e.Data;
}
/// <summary>
/// 斷開連接
/// </summary>
/// <returns>斷開是否成功</returns>
public bool Close()
{
try
{
client.DataReceived -= Client_DataReceived;
//斷開連接
client?.Disconnect();
return !IsConnected;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 釋放
/// </summary>
public void Dispose()
{
try
{
//釋放資源
client?.Dispose();
}
catch (Exception)
{
}
}
/// <summary>
/// 發送數據
/// </summary>
private byte[] sendCmd = new byte[4] { 0x01, 0x02, 0x03, 0x04 };
/// <summary>
/// 解析並返回
/// </summary>
/// <param name="ioarg">ioarg.Address為起始變數位元組編號;ioarg.ValueType為類型</param>
/// <returns></returns>
[Method("讀模擬設備數據", description: "讀模擬設備數據,開始位元組和長度")]
public DriverReturnValueModel Read(DriverAddressIoArgModel ioarg)
{
var ret = new DriverReturnValueModel { StatusType = VaribaleStatusTypeEnum.Good };
ushort startIndex;
//判斷地址是否為整數
if (!ushort.TryParse(ioarg.Address, out startIndex))
{
ret.StatusType = VaribaleStatusTypeEnum.Bad;
ret.Message = "起始位元組編號錯誤";
return ret;
}
//連接正常則進行讀取
if (IsConnected)
{
try
{
//發送請求
client?.Write(sendCmd);
//等待恢復,這裡可以優化
Thread.Sleep(Timeout);
if (latestRcvData == null)
{
ret.StatusType = VaribaleStatusTypeEnum.Bad;
ret.Message = "沒有收到數據";
}
else
{
//解析數據,並返回
switch (ioarg.ValueType)
{
case DataTypeEnum.UByte:
case DataTypeEnum.Byte:
ret.Value = latestRcvData[startIndex];
break;
case DataTypeEnum.Int16:
var buffer16 = latestRcvData.Skip(startIndex).Take(2).ToArray();
ret.Value = BitConverter.ToInt16(new byte[] { buffer16[0], buffer16[1] }, 0);
break;
case DataTypeEnum.Float:
//拿到有用的數據
var buffer32 = latestRcvData.Skip(startIndex).Take(4).ToArray();
//大小端轉換一下
ret.Value = BitConverter.ToSingle(new byte[] { buffer32[3], buffer32[2], buffer32[1], buffer32[0] }, 0);
break;
default:
break;
}
}
}
catch (Exception ex)
{
ret.StatusType = VaribaleStatusTypeEnum.Bad;
ret.Message = $"讀取失敗,{ex.Message}";
}
}
else
{
ret.StatusType = VaribaleStatusTypeEnum.Bad;
ret.Message = "連接失敗";
}
return ret;
}
public async Task<RpcResponse> WriteAsync(string RequestId, string Method, DriverAddressIoArgModel Ioarg)
{
RpcResponse rpcResponse = new() { IsSuccess = false, Description = "設備驅動內未實現寫入功能" };
return rpcResponse;
}
}
public enum ConnectionType
{
Long,
Short
}
}
註冊驅動
- 生成
DriverSimTcpClient
項目iotgateway\IoTGateway\bin\Debug\net6.0\drivers\net6.0
路徑下可以看到生成了DriverSimTcpClient.dll - 運行IoTGateway,訪問本地518埠
- 添加驅動
網關配置->驅動管理->添加
:::warning 註意
添加驅動後需要重啟一下項目,後面會優化
:::
創建設備
- 採集配置->設備維護->添加設備
添加變數
- 採集配置->設備維護->添加設備
手動添加或者通過excel批量導入下麵變數
變數名 | 方法 | 地址 | 類型 | 表達式 | 設備名 |
---|---|---|---|---|---|
運行狀態 | Read | 1 | uint8 | 模擬設備 | |
設備溫度 | Read | 2 | float | 模擬設備 | |
電機轉速 | Read | 6 | int16 | raw*0.01 | 模擬設備 |
開始採集
採集配置->設備維護->編輯設備
啟動TcpServer
運行你熟悉的TCPServer測試工具,啟動埠6666,網關客戶端連接後發送響應報文