IoTClient開發3 - ModBusTcp協議客戶端實現

来源:https://www.cnblogs.com/zhaopei/archive/2019/11/04/11790181.html
-Advertisement-
Play Games

前言 進過前面兩章的介紹,今天開始正式的實戰。 進位轉換 很多朋友對於進位轉換可能是在剛學電腦的時候有接觸,後來做高級語言開發可能就慢慢忘記了。我們做工控開發的時候需要經常進行進位轉換,這裡和大家一起複習下。 一個位元組等8位(1byte = 8bit),可以存儲2^8(0 255)共計256個數字 ...


前言

進過前面兩章的介紹,今天開始正式的實戰。

進位轉換

很多朋友對於進位轉換可能是在剛學電腦的時候有接觸,後來做高級語言開發可能就慢慢忘記了。我們做工控開發的時候需要經常進行進位轉換,這裡和大家一起複習下。
一個位元組等8位(1byte = 8bit),可以存儲2^8(0-255)共計256個數字。所以我們要對8、256等數字要敏感。
int16(short), int32(int), int64(long) 分別是占用2個位元組、4個位元組、8個位元組,Single(float)也是占用4個位元組。

bool       System.Boolean (布爾型,其值為 true 或者 false)
byte       System.Byte    (位元組型,占 1 位元組,表示 8 位正整數,範圍 0 ~ 255)
sbyte      System.SByte   (帶符號位元組型,占 1 位元組,表示 8 位整數,範圍 -128 ~ 127)
char       System.Char    (字元型,占有 2 個位元組,表示 1 個 Unicode 字元)
short      System.Int16   (短整型,占 2 位元組,表示 16 位整數,範圍 -32,768 ~ 32,767)
ushort     System.UInt16  (無符號短整型,占 2 位元組,表示 16 位正整數,範圍 0 ~ 65,535)
uint       System.UInt32  (無符號整型,占 4 位元組,表示 32 位正整數,範圍 0 ~ 4,294,967,295)
int        System.Int32   (整型,占 4 位元組,表示 32 位整數,範圍 -2,147,483,648 到 2,147,483,647)
float      System.Single  (單精度浮點型,占 4 個位元組)
ulong      System.UInt64  (無符號長整型,占 8 位元組,表示 64 位正整數)
long       System.Int64   (長整型,占 8 位元組,表示 64 位整數)
double     System.Double  (雙精度浮點型,占8 個位元組)

接著我們來看其他進位轉十進位的計算

十進位轉十進位  
1263 = 1*10^3 + 2*10^2 + 6*10^1 + 3*10^0 = 1000 + 200 + 60 + 3 = 1263
二進位轉十進位 
1001 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 8 + 0 + 0 + 1 = 9 
十六進位轉十進位 
3245 = 3*16^3 + 2*16^2 + 4*16^1 + 5*16^0 = 3*4096 + 2*256 + 4*16 + 5 = 12869

十進位轉二進位

第八位 第七位 第六位 第五位 第四位 第三位 第二位 第一位
2^7     2^6    2^5    2^4     2^3    2^2    2^1   2^0
128     64     32     16      8      4      2      1

以上位二進位位能存儲最大十進位數,所以我們反過來也可以對照把十進位轉二進位。比如86,
86小於128多以第八位是0,86大於64所以第七位是1。86-64=22,22小於32所以第六位是0,22大於16所以第五位是1。。。 


所以最好轉成二進位是:0101 0110

二進位轉十六進位

我們用二進位 0101 0110來演示,也就是上面十進位的86。

當然,你最好用計算器驗證下

ModBusTcp協議介紹

我們在對進位轉換進行複習過後,接下來講ModBusTcp協議。
ModBus協議是現在工控裡面用的比較多比較通用的一種協議,什麼可靠啊、簡單啊等等一些優點就不說了,直接入正題。
ModBus分為RTU、ASCII、TCP三種方式進行通信,今天我們只講TCP。
在ModBus裡面有站號、功能碼、寄存器地址等概念。

  • 站號:多設備的標識號
  • 功能碼:一些功能的標識號
    功能碼詳解:
01:讀線圈
02:讀離散量
03:讀保持寄存器(每個寄存器含有兩個位元組)
04:讀輸入寄存器
05:寫單個線圈
06:寫單個寄存器
15:用於寫多個線圈
16:寫多個寄存器

ModBusTcp報文分析

協議的理解和實現主要就是要對協議報文理解。(註意:以下報文數據都是十六進位)

數據【讀取-請求報文】:19 B2 00 00 00 06 02 03 00 04 00 01

  • 19 B2 是客戶端發的檢驗信息,隨意定義。
  • 00 00 代表是基於tcp/ip協議的modbus
  • 00 06 標識後面還有多長的位元組
  • 02 表示站號地址
  • 03 為功能碼(讀保持寄存器)
  • 00 04 為寄存器地址
  • 00 01 為寄存器的長度(寄存器個數)

數據【讀取-響應報文】(分兩次獲取)

第一次獲取前八個位元組(Map報文頭):19 B2 00 00 00 05 02 03 02 00 20

  • 19 B2 檢驗驗證信息(複製的客戶端發的,配件檢驗)
  • 00 00 代表是基於tcp/ip協議的modbus(複製的客戶端發的)
  • 00 05 為當前位置到最後的長度
  • 02 表示站號地址(複製的客戶端發的)
  • 03 為功能碼(複製的客戶端發的)

第二次獲取的報文:02 00 20

  • 02 位元組個數
  • 00 20 響應的數據

數據【寫入-請求報文】:19 B2 00 00 00 09 02 10 00 04 00 01 02 00 20

  • 19 B2 是客戶端發的檢驗信息,隨意定義。
  • 00 00 代表是基於tcp/ip協議的modbus
  • 00 09 從本位元組下一個到最後
  • 02 站號
  • 10 功能碼(轉十進位就是16)
  • 00 04 寄存器地址
  • 00 01 寄存器的長度(寄存器個數)
  • 02 寫位元組的個數
  • 00 20 要寫入的值(轉十進位為32)

數據【寫入-響應報文】:19 B2 00 00 00 06 02 10 00 04 00 01

和請求報文的區別

  • 沒有了請求報文的數據值
  • 00 09 變成了00 06 因為報文長度變了
  • 其他的報文意義和請求報文一致

ModBusTcp對寄存器的讀取

有了上面的三個報文做參考,我們就可以用Socket來實現ModBusTcp協議了。其實協議就是按照報文的規定來,也沒有想的那麼複雜,和我們前面實現的聊天通訊軟體區別不大。
第一步,我們先實現數據讀取報文的組裝:

/// <summary>
/// 獲取讀取命令(此方法傳入參數後就可以得到類似19 B2 00 00 00 06 02 03 00 04 00 01這樣的請求報文)
/// </summary>
/// <param name="address">寄存器起始地址</param>
/// <param name="stationNumber">站號</param>
/// <param name="functionCode">功能碼</param>
/// <param name="length">讀取長度</param>
/// <returns></returns>
public static byte[] GetReadCommand(ushort address, byte stationNumber, byte functionCode, ushort length)
{
    byte[] buffer = new byte[12];
    buffer[0] = 0x19;
    buffer[1] = 0xB2;//Client發出的檢驗信息
    buffer[2] = 0x00;
    buffer[3] = 0x00;//表示tcp/ip 的協議的modbus的協議
    buffer[4] = 0x00;
    buffer[5] = 0x06;//表示的是該位元組以後的位元組長度

    buffer[6] = stationNumber;  //站號
    buffer[7] = functionCode;   //功能碼
    buffer[8] = BitConverter.GetBytes(address)[1];
    buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
    buffer[10] = BitConverter.GetBytes(length)[1];
    buffer[11] = BitConverter.GetBytes(length)[0];//表示request 寄存器的長度(寄存器個數)
    return buffer;
}

第二步,就是建立我們的Socket連接,併發送請求報文

//1 創建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//2 建立連接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 埠));

//3 獲取命令[組裝請求報文](寄存器起始地址:4、站號:2、功能碼:3、讀取寄存器長度:1)
byte[] command = GetReadCommand(4, 2, 3, 1);

//4 發送命令
socket.Send(command);

第三步,解析響應報文,得到數據值

//5 讀取響應
byte[] buffer1 = new byte[8];//先讀取前面八個位元組(Map報文頭)
socket.Receive(buffer1, 0, buffer1.Length, SocketFlags.None);

//5.1 獲取將要讀取的數據長度
int length = buffer1[4] * 256 + buffer1[5] - 2;//減2是因為這個長度數據包括了單元標識符和功能碼,占兩個位元組

//5.2 讀取數據
byte[] buffer2 = new byte[length];
var readLength2 = socket.Receive(buffer2, 0, buffer2.Length, SocketFlags.None);

byte[] buffer3 = new byte[readLength2 - 1];
//5.3  過濾第一個位元組(第一個位元組代表數據的位元組個數)
Array.Copy(buffer2, 1, buffer3, 0, buffer3.Length);
var buffer3Reverse = buffer3.Reverse().ToArray();
var value = BitConverter.ToInt16(buffer3Reverse, 0);

//6 關閉連接
socket.Shutdown(SocketShutdown.Both);
socket.Close();

ModBusTcp對寄存器的寫入

對於數據寫入就更簡單了。
第一步,組裝請求報文

/// <summary>
/// 獲取寫入命令
/// </summary>
/// <param name="address">寄存器地址</param>
/// <param name="values"></param>
/// <param name="stationNumber">站號</param>
/// <param name="functionCode">功能碼</param>
/// <returns></returns>
public static byte[] GetWriteCommand(ushort address, byte[] values, byte stationNumber, byte functionCode)
{
    byte[] buffer = new byte[13 + values.Length];
    buffer[0] = 0x19;
    buffer[1] = 0xB2;//檢驗信息,用來驗證response是否串數據了           
    buffer[4] = BitConverter.GetBytes(7 + values.Length)[1];
    buffer[5] = BitConverter.GetBytes(7 + values.Length)[0];//表示的是header handle後面還有多長的位元組

    buffer[6] = stationNumber; //站號
    buffer[7] = functionCode;  //功能碼
    buffer[8] = BitConverter.GetBytes(address)[1];
    buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
    buffer[10] = (byte)(values.Length / 2 / 256);
    buffer[11] = (byte)(values.Length / 2 % 256);//寫寄存器數量(除2是一個寄存器兩個位元組,寄存器16位。除以256是byte最大存儲255。)              
    buffer[12] = (byte)(values.Length);          //寫位元組的個數
    values.CopyTo(buffer, 13);                   //把目標值附加到數組後面
    return buffer;
}

第二步,建立Socket連接,併發送報文

//1 創建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//2 建立連接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 埠));

//值的轉換
short value = 32;
var values = BitConverter.GetBytes(value).Reverse().ToArray();

//3 獲取併發送命令(寄存器起始地址、站號、功能碼)
var command = GetWriteCommand(4, values, 2, 16);
socket.Send(command);

//4 關閉連接
socket.Shutdown(SocketShutdown.Both);
socket.Close();

結束


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...