.Net 8與硬體設備能碰撞出怎麼樣的火花(使用ImageSharp和Protobuf協議通過HidApi與設備通訊)

来源:https://www.cnblogs.com/GreenShade/archive/2023/12/18/17911066.html
-Advertisement-
Play Games

前言 本人最近在社區里說想做稚暉君的那個瀚文鍵盤來著,結果遇到兩個老哥一個老哥送了我電路板,一個送了我焊接好元件的電路板,既然大家這麼捨得,那我也就真的投入製作了這把客制化鍵盤,當然我為了省錢也是特意把外殼模型重新切割,用3D印表機列印了整個外殼,不得不說省了八九百的CNC費用。鍵盤介紹我就不說了, ...


前言

本人最近在社區里說想做稚暉君的那個瀚文鍵盤來著,結果遇到兩個老哥一個老哥送了我電路板,一個送了我焊接好元件的電路板,既然大家這麼捨得,那我也就真的投入製作了這把客制化鍵盤,當然我為了省錢也是特意把外殼模型重新切割,用3D印表機列印了整個外殼,不得不說省了八九百的CNC費用。鍵盤介紹我就不說了,鍵盤主要特色是左邊的拓展模塊,有墨水屏和手感超好的旋鈕,當然也支持自定義開發,能開發也是我寫這篇文章的原因,畢竟是為了開發功能,效果圖如下,大家可以關註我的b站賬號綠蔭阿廣,來學習交流一些有趣的東西。

正面

技術選型

在我查閱了一些社區鍵盤資料發現社區固件有幾個版本,稚暉君原版的固件太老了不好用,送我鍵盤的老哥的版本我覺得挺方便而且用戶量應該也很多,於是我就基於這個版本的固件進行dotnet版本的sdk開發了,目前有其他版本的sdk,有python版本的,vue版本的,我是可以拿來直接參考的。

1. 框架選擇

作為一名.Net開發,我肯定是想用.net進行開發的,理由是這個鍵盤用在PC上,用.Net實現SDK對接WPF,MAUI和WinUI可以做很多的任務型的功能。選擇採用最新版本的.Net8,然後在SDK測試編寫完成之後,對接到我之前的WinUI桌面程式里,大家肯定會問,為什麼不選擇MAUI,我想說當然因為我暫時不想花時間重新寫,不過SDK是支持跨平臺的,這點問題不大。

2. 設備通訊協議

鍵盤採用的固件是開源的ZMK這個代碼編寫的,設備在電腦識別為hid設備,通訊格式使用的Protobuf協議,所以針對.Net也需要使用這個Protobuf進行數據的打包,這個地方花了我一些時間,主要是有些地方不太懂,坑主要是數據轉成位元組數組的時候的一些問題,這個在後面的代碼講解里有用到。

3. 庫選擇

本來以為.Net可以用的hid庫有很多,在本人測試了一圈以後發現不錯的也就這個HidApi.Net還可以,其他的什麼Device.Net,HidLibrary都不是很滿意,在我測試以後選擇了HidApi.Net和設備通訊,Google.Protobuf和Grpc.Tools加工通訊數據,SixLabors.ImageSharp進行圖片數據的轉換。

  • HidApi.Net
  • Google.Protobuf
  • Grpc.Tools
  • SixLabors.ImageSharp
    最終效果如下圖:
    效果圖

代碼講解

項目代碼我這次提交到了電子腦殼的倉庫里,因為我要將功能集成到電子腦殼裡,所以放在了這個倉庫,目前所在分支為helloworld-keyboard,後期應該會合併到主分支。
倉庫地址:https://github.com/maker-community/ElectronBot.DotNet

項目結構

通訊協議實現

通訊的核心部分是Hw75DynamicDevice的Call方法,包含了將protobuf生成的c#對象轉成byte[]並拆分成數據包發送到設備。

 private MessageD2H Call(MessageH2D h2d)
 {
     if (_device == null)
     {
         throw new Exception("設備為空");
     }
     var bytes = h2d.EnCodeProtoMessage();

     for (int i = 0; i < bytes.Length; i += PayloadSize)
     {
         var buf = new byte[PayloadSize];

         if (i + PayloadSize > bytes.Length)
         {
             buf = bytes[i..];
         }
         else
         {
             buf = bytes[i..(i + PayloadSize)];
         }

         var list = new byte[2] { 1, (byte)buf.Length };

         var result = list.Concat(buf).ToArray();
         _device.Write(result);
     }

     Task.Delay(20);

     var byteList = new List<byte>();

     while (true)
     {
         var read = _device.Read(RePortCount + 1);
         int cnt = read[1];
         byteList.AddRange(read[3..(cnt + 2)]);
         if (cnt < PayloadSize)
         {
             break;
         }
     }
     return MessageD2H.Parser.ParseFrom(byteList.ToArray());
 }
  • 數據打包有個重點問題,就是在圖片數據進行拼接的時候有個byte[]長度需要採用protobuf編碼之後再組裝到數據byte[]的前面這個轉成byte[]需要註意,代碼如下:
    public static byte[] EnCodeProtoMessage(this MessageH2D messageH2D)
    {
        var msgBytes = messageH2D.ToByteArray();

        using (MemoryStream ms = new MemoryStream())
        {
            CodedOutputStream output = new CodedOutputStream(ms);
            output.WriteInt32(msgBytes.Length);
            output.Flush();
            byte[] byteList = ms.ToArray();

            var result = byteList.Concat(msgBytes).ToArray();

            return result;
        }
    }

代碼圖片

  • 重點部分是hid設備要每次發送64位元組,第一位元組是數字1,這個是固定的,第二位元組是數據長度,後面的是數據內容。
    圖示

數據傳輸測試

在sdk編寫測試完成之後,就可以進行sdk的使用了,我使用控制台項目進行測試,包含圖片的合成和文字的繪製,以及將繪製好的圖片轉成設備能夠使用的byte數據。

  • 我先使用ImageSharp載入圖片,再載入字體文件將文字和圖片繪製到圖片上,這個為後面製作動態數據做鋪墊,代碼如下:

    using SixLabors.Fonts;
    using SixLabors.ImageSharp;
    using SixLabors.ImageSharp.Drawing.Processing;
    using SixLabors.ImageSharp.PixelFormats;
    using SixLabors.ImageSharp.Processing;
    using System.Diagnostics;
    using System.Numerics;
    
    byte[] byteArray = new byte[128 * 296 / 8];
    
    var list = new List<byte>();
    
    var collection = new FontCollection();
    var family = collection.Add("./SmileySans-Oblique.ttf");
    var font = family.CreateFont(18, FontStyle.Bold);
    
    using (var image = Image.Load<Rgba32>("face.jpg"))
    {
        using var overlay = Image.Load<Rgba32>("bzhan.png");
        
        overlay.Mutate(x =>
        {
            x.Resize(new Size(50,50));
        });
        // Convert the image to grayscale
        image.Mutate(x =>
        {
            
            x.DrawImage(overlay,  new Point(0, 64), opacity: 1);
            x.DrawText("粉絲數:", font, Color.Black, new Vector2(20, 220));
            x.DrawText("999999", font, Color.Black, new Vector2(20, 260));
            x.Grayscale();
        });
        
        image.Save("test.jpg");
    
        byteArray = image.EnCodeImageToBytes();
    }
    
    
  • 然後將ImageSharp合成的圖片轉成01矩陣再組裝成byte[]這個不知道大家有沒有什麼好的辦法,有的話可以推薦給我,我的邏輯寫在了EnCodeImageToBytes這個拓展方法里。

    public static byte[] EnCodeImageToBytes(this Image<Rgba32> image)
    {
        // Create a 01 matrix
        int[,] matrix = new int[image.Height, image.Width];
    
        for (int y = 0; y < image.Height; y++)
        {
            for (int x = 0; x < image.Width; x++)
            {
                matrix[y, x] = image[x, y].R > 128 ? 1 : 0;
            }
        }
    
        // Convert the matrix to a byte array
        byte[] byteArray = new byte[image.Height * image.Width / 8];
        for (int y = 0; y < image.Height; y++)
        {
            for (int x = 0; x < image.Width; x += 8)
            {
                for (int k = 0; k < 8; k++)
                {
                    byteArray[y * image.Width / 8 + x / 8] |= (byte)(matrix[y, x + k] << (7 - k));
                }
            }
        }
    
        return byteArray;
    }
    

全部代碼如下:

using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using HelloWordKeyboard.DotNet;
using HidApi;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Diagnostics;
using System.Numerics;

byte[] byteArray = new byte[128 * 296 / 8];

var list = new List<byte>();

var collection = new FontCollection();
var family = collection.Add("./SmileySans-Oblique.ttf");
var font = family.CreateFont(18, FontStyle.Bold);

using (var image = Image.Load<Rgba32>("face.jpg"))
{
    using var overlay = Image.Load<Rgba32>("bzhan.png");
    
    overlay.Mutate(x =>
    {
        x.Resize(new Size(50,50));
    });
    // Convert the image to grayscale
    image.Mutate(x =>
    {
        
        x.DrawImage(overlay,  new Point(0, 64), opacity: 1);
        x.DrawText("粉絲數:", font, Color.Black, new Vector2(20, 220));
        x.DrawText("999999", font, Color.Black, new Vector2(20, 260));
        x.Grayscale();
    });
    
    image.Save("test.jpg");

    byteArray = image.EnCodeImageToBytes();
}

var hidDevice = new Hw75DynamicDevice();

hidDevice.Open();

Stopwatch sw = Stopwatch.StartNew();

sw.Start();

var data111 = hidDevice.SetEInkImage(byteArray, 0, 0, 128, 296, false);

sw.Stop();

Console.WriteLine($"send data ms:{sw.ElapsedMilliseconds}");

Console.ReadKey();

Hid.Exit();

個人心得體會

這次功能的編寫讓我最有感悟的地方就是自己對Github Copilot的依賴更多了,我基本上很多的知識都是詢問它,因為從網上搜索還要自己過濾那些數據,比較耽誤時間。

還有個點就是這個HidApi.Net的庫是最近剛有人寫的,社區還是有新鮮的血液的,支持.net6,7,8很新,也算是個驚喜呢,希望社區的輪子越來越多呢!!!!

其他角度的照片展示:

側面

背面

參考推薦文檔項目如下:


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

-Advertisement-
Play Games
更多相關文章
  • 學習視頻:【編程不良人】2021年SpringBoot最新最全教程 第十章、項目開發 實現一個登錄註冊,增刪改查功能的系統 10.1 項目開發流程 需求分析 分析用戶主要需求 提取項目核心功能,根據核心功能構建頁面原型 庫表設計: 分析系統有哪些表 分析表之間關聯關係 確定欄位 詳細設計(流程圖、偽 ...
  • 將內容從 Word 文檔中提取出來可以方便我們對其進行其他操作,如將內容儲存在資料庫中、將內容導入到其他程式中、用於 AI 訓練以及製作其他文檔等。第三方庫 Spire.Doc for Python 提供了一個簡單的方法直接提取 Word 文檔中的內容,包括文本和圖片,而不需要大量的複製粘貼操作,也 ...
  • 我們討論網路編程中的IO模型時,需要先明確什麼是IO以及IO操作為什麼在程式開發中是很關鍵的一部分,首先我們看下IO的定義。 IO的定義 IO操作(Input/Output操作)是電腦系統中的一種重要操作,用於數據的輸入和輸出,通常涉及到電腦與外部設備(如硬碟、網卡、鍵盤、滑鼠、印表機等)之間的 ...
  • 一、要求 1.設計並驗證如下演算法:圖採用鄰接矩陣表示,實現無向圖的深度優先搜索與有向圖的廣度優先搜索。 2.設計並驗證如下演算法:帶權圖採用鄰接表表示,實現無向圖的廣度優先搜索與有向圖的深度優先搜索。 二、代碼 1. #include<stdio.h> #include<stdlib.h> #incl ...
  • 數據的預處理是數據分析,或者機器學習訓練前的重要步驟。通過數據預處理,可以 提高數據質量,處理數據的缺失值、異常值和重覆值等問題,增加數據的準確性和可靠性 整合不同數據,數據的來源和結構可能多種多樣,分析和訓練前要整合成一個數據集 提高數據性能,對數據的值進行變換,規約等(比如無量綱化),讓演算法更加 ...
  • `QListWidget` 是 Qt 中的一個列表框組件,用於顯示一列項目,並允許用戶進行選擇。每個項目可以包含一個圖標和文本,可以使用 `QListWidgetItem` 類來表示。`ListWidget`組件與`TreeWidget`有些相似,區別在於`TreeWidget`可以實現嵌套以及多字... ...
  • WebAPI部署到IIS 1 開啟IIS功能 控制面板->程式->程式和功能->啟用或關閉Windows功能,以下打勾: 2 下載對應版本的dotNet Core 本文為ASP .NET Core6.0版本,需下載對應6.0版本的運行時,下載地址:https://dotnet.microsoft.c ...
  • 1. 簡單需求 通過圖文識別讀取一個指定window視窗的文本。 獲取視窗句柄,截圖保存成bitmap ,調用圖文識別庫. 測試結果是對中文下的識別不是特別好。 需要註意的是,tessdata要下載指定目錄頁下。 2. 引用包 a. 引用 tesseract4.1 b. Emgu.CV組件 3. 上 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...