首页 > 其他分享 >.Net 8与硬件设备能碰撞出怎么样的火花(使用ImageSharp和Protobuf协议通过HidApi与设备通讯)

.Net 8与硬件设备能碰撞出怎么样的火花(使用ImageSharp和Protobuf协议通过HidApi与设备通讯)

时间:2023-12-18 17:48:24浏览次数:35  
标签:Protobuf image using var new Net HidApi byte ImageSharp

前言

本人最近在社区里说想做稚晖君的那个瀚文键盘来着,结果遇到两个老哥一个老哥送了我电路板,一个送了我焊接好元件的电路板,既然大家这么舍得,那我也就真的投入制作了这把客制化键盘,当然我为了省钱也是特意把外壳模型重新切割,用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很新,也算是个惊喜呢,希望社区的轮子越来越多呢!!!!

其他角度的照片展示:

侧面

背面

参考推荐文档项目如下:

标签:Protobuf,image,using,var,new,Net,HidApi,byte,ImageSharp
From: https://www.cnblogs.com/GreenShade/p/17911066.html

相关文章

  • Kubernetes管理应用程序、服务常用命令、集群监视
    1、如何使用Kubernetes管理应用程序1.1查看集群信息:kubectlcluster-info:显示集群信息。kubectlconfigview:显示当前kubectl配置信息。1.2查看资源状态:kubectlgetpods:查看所有Pod的状态。kubectlgetdeployments:查看所有部署的状态。kubectlgetservices......
  • RS232转profinet网关扫码枪自由口与1500程序对比
    RS232转profinet网关扫码枪自由口与1500程序对比RS232转profinet网关(XD-PNR200)自由口是一种用于将RS232串口信号转换为profinet协议的设备,它具有自由口的功能。本文以某自动化生产线为例进行案例研究。通过RS232转Profinet网关(XD-PNR200),将生产线的多个RS232扫码枪与PLC连接起来,......
  • ASP.NET WEBAPI 接入微信公众平台总结,Token验证失败解决办法
    首先,请允许我说一句:shit!因为这个问题不难,但是网上有关ASP.NETWEBAPI的资料太少。都是PHP等等的。我也是在看了某位大神的博客后有启发,一点点研究出来的。来看正题!1.微信公众平台的接入方法,无非4个参数(signature,timestamp,nonce,echostr)加1个Token(两边对应)2.Token,timestamp,......
  • python3.8 模块 paramiko报错 AttributeError: 'NoneType' object has no attribute '
    报错信息Exceptionignoredin:<functionBufferedFile.__del__at0x7f4886fbd160>Traceback(mostrecentcalllast):File"/usr/local/python3/lib/python3.8/site-packages/paramiko/file.py",line67,in__del__File"/usr/local/python3/......
  • 关于`.Net Core`捕捉`C/C++`中的异常
    结论开门见山的说,.NetCore无法捕捉在C/C++编写的Dll中的方法的异常和错误。说明在.NetFramework框架期间,可以通过为方法增加特性:HandleProcessCorruptedStateExceptionsAttribute,或者使用RuntimeWrapperException捕捉运行时的错误,但是在.NetCore框架中,上述特性已经不再生......
  • .net 温故知新【16】:Asp.Net Core WebAPI 筛选器
    一、筛选器通过使用筛选器可在请求处理管道中的特定阶段之前或之后运行代码。这即是我们经常听到的面向切面编程AOP(AspectOrientedProgramming)技术,AOP通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。筛选器在ASP.NETCore操作调用管道(有时称为筛选......
  • 端侧显著性检测新高度,OPPO提出面向真实场景的PSUNet
    前言 在高分辨率场景下,现有的显著目标检测方法难以同时满足快速推理和准确结果的要求。它们受到用于高分辨率图像的公共数据集和高效网络模块的质量的限制。为了缓解这些问题,本文构建一个显著对象匹配数据集HRSON和一个轻量级网络PSUNet。考虑到移动部署框架的高效推理,设计了对称......
  • 通过备份 Etcd 来完美恢复 Kubernetes 中的误删数据
    误删除或者服务器宕机,会导致Etcd数据的丢失或某个节点的Etcd数据异常时,当误删时,需要恢复数据,这个在实际环境当中是不可避免的。以下描述删除两个namespace下的Pod,如何恢复对应namespace的数据。1、操作环境3个(master、etcd)+1个node新建1个namespace下且创建Pod和......
  • 修改kubernetes-dashboard默认token认证时间
    详解:k8s默认dashboardtoken时间是900s,15分钟,到期后会自动退出登陆。解决办法:修改默认时间找到部署dashboard的yaml文件增加其中这一行[root@master1~]#catrecommended.yaml#Copyright2017TheKubernetesAuthors.##LicensedundertheApacheLicense,Version2.0(th......
  • kubernetes1.18.2安装kube-prometheus
    注:使用kube-prometheus安装非常方便,前面的prometheus+grafana+metrics-server都不用安装,已经集成在里面。1、clonegithub地址gitclonehttps://github.com/coreos/kube-prometheus.gitcdkube-prometheus2、查看manifest路径下所有的yaml文件[root@master1manifests]#lltotal......