驅動開發實戰之TcpClient

来源:https://www.cnblogs.com/whdong/archive/2022/06/05/16345368.html
-Advertisement-
Play Games

最近通過WPF開發項目,為了對WPF知識點進行總結,所以利用業餘時間,開發一個學生信息管理系統【Student Information Management System】。前四篇文章進行了框架搭建和模塊劃分,後臺WebApi介面編寫,以及課程管理模塊,班級管理模塊,學生管理模塊的開發,本文在前四篇... ...


場景模擬

假設你有一批非標設備需要對接,對方提供瞭如下協議文檔:

協議概述

設備作為TCPServer,埠6666
位元組序:Little-Endian,即低地址存放低位

請求回覆

需要你主動發起讀取請求:0x01 02 03 04
設備回覆:0x08 01 41 D6 3D 71 1A 20

參數說明

  1. 總位元組數

    (byte[0])即0x08:用於簡單的校驗

  2. 運行狀態

    (byte[1])即0x01:1為運行;其他為停止

  3. 設備溫度

    (byte[2]-byte[5])即0x41 D6 3D 71:單精度浮點數值26.78

  4. 電機轉速

    (byte[6]-byte[7])即0x1A 20:對應16進位無符號整型,倍率0.01值66.88

驅動開發

我們根據上面的協議,開發驅動。請先瀏覽上一篇驅動簡介

創建驅動項目

  1. 在解決方案->Drivers文件夾,右鍵添加->新建項目->C#類庫

  2. 項目名DriverSimTcpClient,放在iotgateway\Plugins\Drivers路徑下

  3. 修改Class1SimTcpClient

  4. 雙擊項目,修改配置

<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
  }
}

註冊驅動

  1. 生成DriverSimTcpClient 項目

    iotgateway\IoTGateway\bin\Debug\net6.0\drivers\net6.0路徑下可以看到生成了DriverSimTcpClient.dll

  2. 運行IoTGateway,訪問本地518埠
  3. 添加驅動

    網關配置->驅動管理->添加

:::warning 註意

添加驅動後需要重啟一下項目,後面會優化
:::

創建設備

  1. 採集配置->設備維護->添加設備

添加變數

  1. 採集配置->設備維護->添加設備

    手動添加或者通過excel批量導入下麵變數

變數名 方法 地址 類型 表達式 設備名
運行狀態 Read 1 uint8 模擬設備
設備溫度 Read 2 float 模擬設備
電機轉速 Read 6 int16 raw*0.01 模擬設備

開始採集

採集配置->設備維護->編輯設備

啟動TcpServer

運行你熟悉的TCPServer測試工具,啟動埠6666,網關客戶端連接後發送響應報文

查看數據


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

-Advertisement-
Play Games
更多相關文章
  • 從本篇文章開始,筆者打算寫一個系列的《clickhouse專欄》,其全稱是Click Stream,Data WareHouse,簡稱ClickHouse。從其全稱中的“Data WareHouse”,我們可以看出clickhouse的定位是數據倉庫。那麼“數據倉庫”和“資料庫”有什麼區別呢?理解這 ...
  • 1 與其它協程庫使用對比 這個 C 協程庫是雲風(cloudwu) 寫的,其介面風格與 Lua 協程類似,並且都是非對稱 stackful 協程。這個是源代碼中的示例: #include "coroutine.h" #include <stdio.h> struct args { int n; }; ...
  • #結構 ##順序結構 JAVA的基本結構就是順序結構,除非特意指明,否則就按照順序,一句一句執行。 順序結構是最簡單的演算法結構 語句與語句之間,框與框之間按照從上到下的順序執行,它是任何演算法都離不開的基本演算法結構 ##選擇結構 if單選擇結構 if雙選擇結構 if多選擇結構 嵌套的if結構 swit ...
  • 項目環境docker及docker-compose文檔 1、Linux環境介紹 centos7.6 16G以上記憶體空間(至少8G) 2、靜態IP設置 1、找到配置文件 cd /etc/sysconfig/network-scripts/ vi ifcfg-ens33 TYPE=EthernetPRO ...
  • 前面我們分析了攜程的 apollo(見 詳解apollo的設計與使用),現在再來看看阿裡的 nacos。和 apollo 一樣,nacos 也是一款配置中心,同樣可以實現配置的集中管理、分環境管理、即時生效等等。不過,nacos 還具備了服務發現的功能。這篇博客將重點分析 nacos 和 apoll... ...
  • 關註「WeiyiGeek」點我,點我 設為「特別關註」,每天帶你在B站玩轉網路安全運維、應用開發、物聯網IOT學習! 希望各位看友【關註、點贊、評論、收藏、投幣】,助力每一個夢想。 文章目錄 0x00 快速瞭解 EasyOCR 介紹 EasyOCR 參考來源 0x01 安裝部署 環境依賴 環境安裝 ...
  • Markdown語法學習筆記 {#Top} 寫在前面:本篇筆記僅記錄學習後的總結,以供日後快速回顧使用。 更多擴展語法參考Markdown官網 標題上設置了錨點鏈接 # Markdown語法學習筆記 {#Top} 緊接著下方設置了生成索引目錄 1. 標題 Markdown 語法 HTML 預覽效果 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...