注:ServerSuperIO有可能被移植到Windows 10 IOT上,那麼将來有可能開發一套裝置驅動,可以支行在服務端、嵌入式裝置中,将形成完整的解決方案。
現在已經調試通過部分代碼,還得需要一段時間,一般都是晚上幹,時間也有限。如下圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuUmZwQTZkJDZkN2MzEGMlNGOlNmY1E2NwYjN5QzNkNDNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
目 錄
4.如開發一套裝置驅動,同時支援序列槽和網絡通訊... 2
4.1 概述... 2
4.2 通訊協定規定... 2
4.2.1 發送讀實時資料指令協定... 2
4.2.2 解析實時資料協定... 3
4.2.3 發送和接收資料事例... 3
4.3 開發裝置驅動... 3
4.3.1 建構實時資料持久對象(不是必須)... 3
4.3.2 建構參數資料持久對象... 5
4.3.3 建構發送和解析協定指令對象... 5
4.3.4 建構協定驅動對象... 6
4.3.5 建構裝置驅動對象... 8
4.4 建構宿主程式... 12
4.5 運作效果... 15
4.如開發一套裝置驅動,同時支援序列槽和網絡通訊
4.1 概述
作為物聯網通訊架構,肯定要支援多種通訊鍊路,在多種通訊鍊路的基礎上完成多種通訊協定的互動,例如:Modbus、自定義協定等等。但是,有一個問題:針對同一台硬體裝置或傳感器,完成序列槽和網絡兩種通訊方式的資料采集和控制,是否要分别寫代碼?如果從現實角度分析,同一硬體,它要完成的業務邏輯肯定是相同的,是以ServerSuperIO物聯網架構,允許開發一套裝置驅動,同時支援序列槽和網絡兩種通訊方式的互動。
通訊很簡單、互動很簡單、業務很簡單……如果把很多簡單的問題合在一起,那麼就變得不簡單了,是以要有一個架構性的東西,重新把衆多問題變得簡單。
4.2 通訊協定規定
在完成一個裝置驅動的開發之前,首先要知道它的通訊協定,好比兩個人交流的語言一樣。針對通訊協定,我們自定義一個簡單互動方式,隻是發送指令,提取資料資訊。
4.2.1 發送讀實時資料指令協定
計算機發送0x61指令為讀實時資料指令,共發送6個位元組,校驗和為從“從機位址”開始的累加和,不包括“資料報頭”、“校驗和”和“協定結束”。
發送指令資料幀如下:
幀結構 | 資料報頭 | 從機位址 | 指令代碼 | 校驗和 | 協定結束 | |
0x55 | 0xAA | 0x61 | 0x0D | |||
位元組數 | 1 |
4.2.2 解析實時資料協定
下位機接收到讀實時資料指令後,并校驗成功,傳回實時資料,校驗和為從“從機位址”開始的累加和,不包括“資料報頭”、“校驗和”和“協定結束”。
接收資料幀如下:
流量 | 信号 | |||||||
浮點型 | ||||||||
4 |
4.2.3 發送和接收資料事例
發送(十六進制):0x55 0xaa 0x00 0x61 0x61 0x0d
接收(十六進制):0x55 0xaa 0x00 0x61 0x43 0x7a 0x00 0x00 0x43 0xb4 0x15 0x0d
流量資料為:250.00
信号資料為:360.00
4.3 開發裝置驅動
4.3.1 建構實時資料持久對象(不是必須)
1.通過傳回資料的通訊協定,有流量和信号兩個動态變量,我們需要建立一個動态對象實體類,主要用于協定驅動與裝置驅動之間的資料互動。代碼如下:
public class Dyn
{
private float _Flow = 0.0f;
/// <summary>
/// 流量
/// </summary>
public float Flow
{
get { return _Flow; }
set { _Flow = value; }
}
private float _Signal = 0.0f;
/// <summary>
/// 信号
/// </summary>
public float Signal
{
get { return _Signal; }
set { _Signal = value; }
}
}
2.我們主要的工作是要建立一個實時資料持久對象類,實時緩存資料資訊,也可以把該實時資料資訊儲存到資料庫中或其他存儲媒質。實時資料持久對象類的代碼如下:
public class DeviceDyn:DeviceDynamic
{
public DeviceDyn() : base()
{
Dyn=new Dyn();
}
public override string GetAlertState()
{
throw new NotImplementedException("無報警資訊");
}
public override object Repair()
{
return new DeviceDyn();
}
public Dyn Dyn { get; set; }
}
DeviceDyn 類繼承自DeviceDynamic,因為每個硬體裝置的報警資訊有可能不一樣,是以GetAlertState函數可以實該功能,但是SSIO架構并沒有直接引用;這個類本質上是一個可以序列化,在不加互斥的情況下可能造成檔案損壞,是以Repair可以完成修複功能,在DeviceDynamic基類裡實作了該功能;另外,實作DeviceDynamic基類自帶兩個函數,Save函數用于持久化(序列化)此類的資訊,Load用于獲得(反序列化)此類的資訊,在裝置驅動中可以使用。
4.3.2 建構參數資料持久對象
一般來說硬體裝置會有讀參數的指令,那麼傳回來的參數也需要進行持久化存儲,并且每台裝置的參數都可能不一樣,在此提供一個可擴充的接口。在這個通訊協定中并沒有涉及到裝置參數相關的協定說明,但是我們也需要建立一個參數資料持久對象類,可以不寫任何擴充的參數屬性,在SSIO架構對參數的接口進行了引用,這是必須進行了工作。代碼如下:
public class DevicePara:ServerSuperIO.Device.DeviceParameter
{
public override object Repair()
{
return new DevicePara();
}
}
DevicePara繼承自DeviceParameter類,情況與實時資料持久對象類似,可以參數。
4.3.3 建構發送和解析協定指令對象
與裝置進行互動會涉及到很多互動式的指令或指令代碼,而這些指令在SSIO架構内是以協定指令對象的形式存在,大體包括三個部:執行指令接口、打包發送資料接口、解析接收資料接口等。
針對上面的通訊協定,有一個61指令,那麼我們就可以根據61指令為命名建構一個協定指令對象,包括發送資料和解析資料部分。如果有其他指令代碼,舉一反三。代碼如下:
internal class DeviceCommand:ProtocolCommand
{
public override string Name
{
get { return "61"; }
}
public override void ExcuteCommand<T>(T t)
{
throw new NotImplementedException();
}
public override byte[] Package<T> (string code, T1 t1,T2 t2)
{
//發送:0x55 0xaa 0x00 0x61 0x61 0x0d
byte[] data = new byte[6];
data[0] = 0x55;
data[1] = 0xaa;
data[2] = byte.Parse(code);
data[3] = 0x61;
data[4] = this.ProtocolDriver.GetCheckData(data)[0];
data[5] = 0x0d;
return data;
}
public override dynamic Analysis<T>(byte[] data, T t)
{
Dyn dyn = new Dyn()
//一般下位機是單片的話,接收到資料的高低位需要互換,才能正常解析。
byte[] flow = BinaryUtil.SubBytes(data, 4, 4, true);
dyn.Flow = BitConverter.ToSingle(flow, 0);
byte[] signal = BinaryUtil.SubBytes(data, 8, 4, true);
dyn.Signal = BitConverter.ToSingle(signal, 0);
return dyn;
}
}
建構協定指令需要全部繼承自ProtocolCommand,根據通訊協定規定,Name屬性傳回61,作為關鍵字;Package是打包要送的資料資訊;Analysis對應着接收資料之後進行解析操作。就這樣一個簡單的協定指令驅動就建構完成了。
4.3.4 建構協定驅動對象
有了協定指令之後,我們需要建構協定驅動對象,SSIO架構支援自定義協定也在于此,并且與裝置驅動的接口相關聯,在SSIO架構的進階應用中也進行了引用,建構這引對象很關鍵。代碼如下:
internal class DeviceProtocol:ProtocolDriver
{
public override bool CheckData(byte[] data)
{
if (data[0] == 0x55 && data[1] == 0xaa && data[data.Length - 1] == 0x0d)
{
return true;
}
else
{
return false;
}
}
public override byte[] GetCommand(byte[] data)
{
return new byte[] { data[3] };
}
public override int GetAddress(byte[] data)
{
return data[2];
}
public override byte[] GetHead(byte[] data)
{
return new byte[] { data[0], data[1] };
}
public override byte[] GetEnd(byte[] data)
{
return new byte[] { data[data.Length - 1] };
}
public override byte[] GetCheckData(byte[] data)
{
byte checkSum = 0;
for (int i = 2; i < data.Length - 2; i++)
{
checkSum += data[i];
}
return new byte[] { checkSum };
}
public override string GetCode(byte[] data)
{
throw new NotImplementedException();
}
public override int GetPackageLength(byte[] data, IChannel channel, ref int readTimeout)
{
throw new NotImplementedException();
}
}
DeviceProtocol 協定驅動繼承自ProtocolDriver ,一個裝置驅動隻存在一個協定驅動,一個協定驅動可以存在多個協定指令(如61指令)。該類中的CheckData函數很關鍵,SSIO架構中的裝置驅動基類引用了,主要是完成校驗接收資料的完事性,是否符合協定,進而決定了通訊狀态:通訊正常、通訊中斷、通訊幹擾、以及通訊未知,不同的通訊狀态也決定了調用裝置驅動中的哪個函數接口:Communicate、CommunicateInterrupt、CommunicateError和CommunicateNone。
4.3.5 建構裝置驅動對象
上邊的基礎工作都做完之後,現在就建構裝置驅動的核心部分,也就是SSIO架構與裝置驅動對接、協調、排程的唯一接口,寫完這個接口,裝置驅動就可以在SSIO上直接運作了,并且與硬體裝置進行互動。直接上代碼:
public class DeviceDriver:RunDevice
{
private DeviceDyn _deviceDyn;
private DevicePara _devicePara;
private DeviceProtocol _protocol;
public DeviceDriver() : base()
{
_devicePara = new DevicePara();
_deviceDyn = new DeviceDyn();
_protocol = new DeviceProtocol();
}
public override void Initialize(string devid)
{
this.Protocol.InitDriver(this.GetType(),null);
//初始化裝置參數資訊
_devicePara.DeviceID = devid;//裝置的ID必須先指派,因為要查找對應的參數檔案。
if (System.IO.File.Exists(_devicePara.SavePath))
{
//如果參數檔案存在,則獲得參數執行個體
_devicePara = _devicePara.Load<DevicePara>();
}
else
{
//如果參數檔案不存在,則序列化一個檔案
_devicePara.Save<DevicePara>(_devicePara);
}
//初始化裝置實時資料資訊
_deviceDyn.DeviceID = devid;//裝置的ID必須先指派,因為要查找對應的實時資料檔案。
if (System.IO.File.Exists(_deviceDyn.SavePath))
{
//參數檔案存在,則獲得參數執行個體
_deviceDyn = _deviceDyn.Load<DeviceDyn>();
}
else
{
//如果參數檔案不存在,則序列化一個檔案
_deviceDyn.Save<DeviceDyn>(_deviceDyn);
}
}
public override byte[] GetConstantCommand()
{
return this.Protocol.DriverPackage<String>("0", "61", null);
}
public override void Communicate(ServerSuperIO.Communicate.IRequestInfo info)
{
Dyn dyn = this.Protocol.DriverAnalysis<String>("61", info.Data, null);
if (dyn != null)
{
_deviceDyn.Dyn = dyn;
}
OnDeviceRuningLog("通訊正常");
}
public override void CommunicateInterrupt(ServerSuperIO.Communicate.IRequestInfo info)
{
OnDeviceRuningLog("通訊中斷");
}
public override void CommunicateError(ServerSuperIO.Communicate.IRequestInfo info)
{
OnDeviceRuningLog("通訊幹擾");
}
public override void CommunicateNone()
{
OnDeviceRuningLog("通訊未知");
}
public override void Alert()
{
return;
}
public override void Save()
{
try
{
_deviceDyn.Save<DeviceDyn>(_deviceDyn);
}
catch (Exception ex)
{
OnDeviceRuningLog(ex.Message);
}
}
public override void Show()
{
List<string> list=new List<string>();
list.Add(_devicePara.DeviceName);
list.Add(_deviceDyn.Dyn.Flow.ToString());
list.Add(_deviceDyn.Dyn.Signal.ToString());
OnDeviceObjectChanged(list.ToArray());
}
public override void UnknownIO()
{
OnDeviceRuningLog("未知通訊接口");
}
public override void CommunicateStateChanged(ServerSuperIO.Communicate.CommunicateState comState)
{
OnDeviceRuningLog("通訊狀态改變");
}
public override void ChannelStateChanged(ServerSuperIO.Communicate.ChannelState channelState)
{
OnDeviceRuningLog("通道狀态改變");
}
public override void Exit()
{
OnDeviceRuningLog("退出裝置");
}
public override void Delete()
{
OnDeviceRuningLog("删除裝置");
}
public override object GetObject()
{
throw new NotImplementedException();
}
public override void ShowContextMenu()
{
throw new NotImplementedException();
}
public override IDeviceDynamic DeviceDynamic
{
get { return _deviceDyn; }
}
public override IDeviceParameter DeviceParameter
{
get { return _devicePara; }
}
public override IProtocolDriver Protocol
{
get { return _protocol;}
}
public override DeviceType DeviceType
{
get { return DeviceType.Common; }
}
public override string ModelNumber
{
get { return "serversuperio"; }
}
public override System.Windows.Forms.Control DeviceGraphics
{
get { throw new NotImplementedException(); }
}
}
實時動态資料對象_deviceDyn、參數資料對象_devicePara、協定驅動對象_protocol分别提供給接口:DeviceDynamic、DeviceParameter和Protocol,為SSIO提供可引用的基礎屬性參數。
Initialize是裝置驅動初始化的函數接口,在這個接口完成兩個主要工作:初始化協定驅動和參數性的資訊。通過this.Protocol.InitDriver(this.GetType(),null);代碼可以加載所有協定指令到協定驅動的緩存中,以便實時調用。當然這裡邊也可以進行其他方面的工作,但是注意對異常的處理。
DeviceType這個是裝置的類型,一般指定為Common就好了。其他函數接口功能已經在《
物聯網架構ServerSuperIO教程-3.裝置驅動介紹》中詳細介紹了,請參考。
4.4 建構宿主程式
一個簡單的裝置驅動就已經開發好了,光有驅動還不行,那麼我們基于SSIO架構再寫幾行代碼,完成一個宿主程式,把裝置驅動執行個體化,放SSIO的服務執行個體中運作,完成序列槽和網絡兩種方式的通訊互動,代碼也非常簡單。代碼如下:
class Program
{
static void Main(string[] args)
{
DeviceDriver dev1 = new DeviceDriver();
dev1.DeviceParameter.DeviceName = "序列槽裝置1";
dev1.DeviceParameter.DeviceAddr = 0;
dev1.DeviceParameter.DeviceID = "0";
dev1.DeviceDynamic.DeviceID = "0";
dev1.DeviceParameter.COM.Port = 1;
dev1.DeviceParameter.COM.Baud = 9600;
dev1.CommunicateType = CommunicateType.COM;
dev1.Initialize("0");
DeviceDriver dev4 = new DeviceDriver();
dev4.DeviceParameter.DeviceName = "網絡裝置2";
dev4.DeviceParameter.DeviceAddr = 0;
dev4.DeviceParameter.DeviceID = "3";
dev4.DeviceDynamic.DeviceID = "3";
dev4.DeviceParameter.NET.RemoteIP = "127.0.0.1";
dev4.DeviceParameter.NET.RemotePort = 9600;
dev4.CommunicateType = CommunicateType.NET;
dev4.Initialize("3");
IServer server = new ServerFactory().CreateServer(new ServerConfig()
{
ServerName = "服務執行個體1",
SocketMode = SocketMode.Tcp,
ControlMode = ControlMode.Loop,
CheckSameSocketSession = false,
StartCheckPackageLength = false,
});
server.AddDeviceCompleted += server_AddDeviceCompleted;
server.DeleteDeviceCompleted += server_DeleteDeviceCompleted;
server.SocketConnected+=server_SocketConnected;
server.SocketClosed+=server_SocketClosed;
server.Start();
server.AddDevice(dev1);
server.AddDevice(dev4);
while ("exit"==Console.ReadLine())
{
server.Stop();
}
}
private static void server_SocketClosed(string ip, int port)
{
Console.WriteLine(String.Format("斷開:{0}-{1} 成功", ip, port));
}
private static void server_SocketConnected(string ip, int port)
{
Console.WriteLine(String.Format("連接配接:{0}-{1} 成功",ip, port));
}
private static void server_AddDeviceCompleted(string devid, string devName, bool isSuccess)
{
Console.WriteLine(devName+",增加:"+isSuccess.ToString());
}
private static void server_DeleteDeviceCompleted(string devid, string devName, bool isSuccess)
{
Console.WriteLine(devName + ",删除:" + isSuccess.ToString());
}
}
}
這個代碼大家都能看明白,具體的控制模式我們接下來會一一介紹。在建構宿主程式的時候,切忌對服務執行個體這樣引用:server.ChannelManager、server.ControllerManager、server.DeviceManager。盡管提供了這樣的接口,主要是為了SSIO架構内部使用的,不需要我們單獨去操作這些接口。有的網友是這樣的寫的,那麼就變成了一個純的通信IO架構,那麼就失去了SSIO架構本身的價值。作為二次開發者,隻需要設定裝置驅動的參數,以及向服務執行個體中增加或删除裝置就行了,其他所有的運作全部交給SSIO架構來完成。
4.5 運作效果
2.[
開源]C#跨平台物聯網通訊架構ServerSuperIO(SSIO)介紹2.
應用SuperIO(SIO)和開源跨平台物聯網架構ServerSuperIO(SSIO)建構系統的整體方案3.
C#工業物聯網和內建系統解決方案的技術路線(資料源、資料采集、資料上傳與接收、ActiveMQ、Mongodb、WebApi、手機App)5.ServerSuperIO開源位址:
https://github.com/wxzz/ServerSuperIO物聯網&內建技術(.NET) QQ群:54256083