1.C#跨平臺物聯網通信框架ServerSuperIO(SSIO)介紹html
《連載 | 物聯網框架ServerSuperIO教程》1.4種通信模式機制。git
《連載 | 物聯網框架ServerSuperIO教程》2.服務實例的配置參數說明github
《連載 | 物聯網框架ServerSuperIO教程》- 3.設備驅動介紹數據庫
注:ServerSuperIO有可能被移植到Windows 10 IOT上,那麼未來有可能開發一套設備驅動,能夠支行在服務端、嵌入式設備中,將造成完整的解決方案。緩存
如今已經調試經過部分代碼,還得須要一段時間,通常都是晚上幹,時間也有限。以下圖:網絡
目 錄框架
4.如開發一套設備驅動,同時支持串口和網絡通信... 2ide
4.1 概述... 2函數
4.2 通信協議規定... 2oop
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
做爲物聯網通信框架,確定要支持多種通信鏈路,在多種通信鏈路的基礎上完成多種通信協議的交互,例如:Modbus、自定義協議等等。可是,有一個問題:針對同一臺硬件設備或傳感器,完成串口和網絡兩種通信方式的數據採集和控制,是否要分別寫代碼?若是從現實角度分析,同一硬件,它要完成的業務邏輯確定是相同的,因此ServerSuperIO物聯網框架,容許開發一套設備驅動,同時支持串口和網絡兩種通信方式的交互。
通信很簡單、交互很簡單、業務很簡單……若是把不少簡單的問題合在一塊兒,那麼就變得不簡單了,因此要有一個框架性的東西,從新把衆多問題變得簡單。
在完成一個設備驅動的開發以前,首先要知道它的通信協議,比如兩我的交流的語言同樣。針對通信協議,咱們自定義一個簡單交互方式,只是發送命令,提取數據信息。
計算機發送0x61指令爲讀實時數據命令,共發送6個字節,校驗和爲從「從機地址」開始的累加和,不包括「數據報頭」、「校驗和」和「協議結束」。
發送指令數據幀以下:
幀結構 |
數據報頭 |
從機地址 |
指令代碼 |
校驗和 |
協議結束 |
|
0x55 |
0xAA |
|
0x61 |
|
0x0D |
|
字節數 |
1 |
1 |
1 |
1 |
1 |
1 |
下位機接收到讀實時數據命令後,並校驗成功,返回實時數據,校驗和爲從「從機地址」開始的累加和,不包括「數據報頭」、「校驗和」和「協議結束」。
接收數據幀以下:
幀結構 |
數據報頭 |
從機地址 |
指令代碼 |
流量 |
信號 |
校驗和 |
協議結束 |
|
0x55 |
0xAA |
|
0x61 |
浮點型 |
浮點型 |
|
0x0D |
|
字節數 |
1 |
1 |
1 |
1 |
4 |
4 |
1 |
1 |
發送(十六進制):0x55 0xaa 0x00 0x61 0x61 0x0d
接收(十六進制):0x55 0xaa 0x00 0x61 0x43 0x7a 0x00 0x00 0x43 0xb4 0x15 0x0d
流量數據爲:250.00
信號數據爲:360.00
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用於得到(反序列化)此類的信息,在設備驅動中可使用。
通常來講硬件設備會有讀參數的命令,那麼返回來的參數也須要進行持久化存儲,而且每臺設備的參數均可能不同,在此提供一個可擴展的接口。在這個通信協議中並無涉及到設備參數相關的協議說明,可是咱們也須要建立一個參數數據持久對象類,能夠不寫任何擴展的參數屬性,在SSIO框架對參數的接口進行了引用,這是必須進行了工做。代碼以下:
public class DevicePara:ServerSuperIO.Device.DeviceParameter { public override object Repair() { return new DevicePara(); } }
DevicePara繼承自DeviceParameter類,狀況與實時數據持久對象相似,能夠參數。
與設備進行交互會涉及到不少交互式的命令或指令代碼,而這些命令在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對應着接收數據以後進行解析操做。就這樣一個簡單的協議命令驅動就構建完成了。
有了協議命令以後,咱們須要構建協議驅動對象,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。
上邊的基礎工做都作完以後,如今就構建設備驅動的核心部分,也就是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.設備驅動介紹》中詳細介紹了,請參考。
一個簡單的設備驅動就已經開發好了,光有驅動還不行,那麼咱們基於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框架來完成。
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