经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.Net 8与硬件设备能碰撞出怎么样的火花(使用ImageSharp和Protobuf协议通过HidApi与设备通讯)
来源:cnblogs  作者:绿荫阿广  时间:2023/12/21 9:05:54  对本文有异议

前言

本人最近在社区里说想做稚晖君的那个瀚文键盘来着,结果遇到两个老哥一个老哥送了我电路板,一个送了我焊接好元件的电路板,既然大家这么舍得,那我也就真的投入制作了这把客制化键盘,当然我为了省钱也是特意把外壳模型重新切割,用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[]并拆分成数据包发送到设备。

  1. private MessageD2H Call(MessageH2D h2d)
  2. {
  3. if (_device == null)
  4. {
  5. throw new Exception("设备为空");
  6. }
  7. var bytes = h2d.EnCodeProtoMessage();
  8. for (int i = 0; i < bytes.Length; i += PayloadSize)
  9. {
  10. var buf = new byte[PayloadSize];
  11. if (i + PayloadSize > bytes.Length)
  12. {
  13. buf = bytes[i..];
  14. }
  15. else
  16. {
  17. buf = bytes[i..(i + PayloadSize)];
  18. }
  19. var list = new byte[2] { 1, (byte)buf.Length };
  20. var result = list.Concat(buf).ToArray();
  21. _device.Write(result);
  22. }
  23. Task.Delay(20);
  24. var byteList = new List<byte>();
  25. while (true)
  26. {
  27. var read = _device.Read(RePortCount + 1);
  28. int cnt = read[1];
  29. byteList.AddRange(read[3..(cnt + 2)]);
  30. if (cnt < PayloadSize)
  31. {
  32. break;
  33. }
  34. }
  35. return MessageD2H.Parser.ParseFrom(byteList.ToArray());
  36. }
  • 数据打包有个重点问题,就是在图片数据进行拼接的时候有个byte[]长度需要采用protobuf编码之后再组装到数据byte[]的前面这个转成byte[]需要注意,代码如下:
  1. public static byte[] EnCodeProtoMessage(this MessageH2D messageH2D)
  2. {
  3. var msgBytes = messageH2D.ToByteArray();
  4. using (MemoryStream ms = new MemoryStream())
  5. {
  6. CodedOutputStream output = new CodedOutputStream(ms);
  7. output.WriteInt32(msgBytes.Length);
  8. output.Flush();
  9. byte[] byteList = ms.ToArray();
  10. var result = byteList.Concat(msgBytes).ToArray();
  11. return result;
  12. }
  13. }

代码图片

  • 重点部分是hid设备要每次发送64字节,第一字节是数字1,这个是固定的,第二字节是数据长度,后面的是数据内容。
    图示

数据传输测试

在sdk编写测试完成之后,就可以进行sdk的使用了,我使用控制台项目进行测试,包含图片的合成和文字的绘制,以及将绘制好的图片转成设备能够使用的byte数据。

  • 我先使用ImageSharp加载图片,再加载字体文件将文字和图片绘制到图片上,这个为后面制作动态数据做铺垫,代码如下:

    1. using SixLabors.Fonts;
    2. using SixLabors.ImageSharp;
    3. using SixLabors.ImageSharp.Drawing.Processing;
    4. using SixLabors.ImageSharp.PixelFormats;
    5. using SixLabors.ImageSharp.Processing;
    6. using System.Diagnostics;
    7. using System.Numerics;
    8. byte[] byteArray = new byte[128 * 296 / 8];
    9. var list = new List<byte>();
    10. var collection = new FontCollection();
    11. var family = collection.Add("./SmileySans-Oblique.ttf");
    12. var font = family.CreateFont(18, FontStyle.Bold);
    13. using (var image = Image.Load<Rgba32>("face.jpg"))
    14. {
    15. using var overlay = Image.Load<Rgba32>("bzhan.png");
    16. overlay.Mutate(x =>
    17. {
    18. x.Resize(new Size(50,50));
    19. });
    20. // Convert the image to grayscale
    21. image.Mutate(x =>
    22. {
    23. x.DrawImage(overlay, new Point(0, 64), opacity: 1);
    24. x.DrawText("粉丝数:", font, Color.Black, new Vector2(20, 220));
    25. x.DrawText("999999", font, Color.Black, new Vector2(20, 260));
    26. x.Grayscale();
    27. });
    28. image.Save("test.jpg");
    29. byteArray = image.EnCodeImageToBytes();
    30. }
  • 然后将ImageSharp合成的图片转成01矩阵再组装成byte[]这个不知道大家有没有什么好的办法,有的话可以推荐给我,我的逻辑写在了EnCodeImageToBytes这个拓展方法里。

    1. public static byte[] EnCodeImageToBytes(this Image<Rgba32> image)
    2. {
    3. // Create a 01 matrix
    4. int[,] matrix = new int[image.Height, image.Width];
    5. for (int y = 0; y < image.Height; y++)
    6. {
    7. for (int x = 0; x < image.Width; x++)
    8. {
    9. matrix[y, x] = image[x, y].R > 128 ? 1 : 0;
    10. }
    11. }
    12. // Convert the matrix to a byte array
    13. byte[] byteArray = new byte[image.Height * image.Width / 8];
    14. for (int y = 0; y < image.Height; y++)
    15. {
    16. for (int x = 0; x < image.Width; x += 8)
    17. {
    18. for (int k = 0; k < 8; k++)
    19. {
    20. byteArray[y * image.Width / 8 + x / 8] |= (byte)(matrix[y, x + k] << (7 - k));
    21. }
    22. }
    23. }
    24. return byteArray;
    25. }

全部代码如下:

  1. using Google.Protobuf;
  2. using Google.Protobuf.WellKnownTypes;
  3. using HelloWordKeyboard.DotNet;
  4. using HidApi;
  5. using SixLabors.Fonts;
  6. using SixLabors.ImageSharp;
  7. using SixLabors.ImageSharp.Drawing.Processing;
  8. using SixLabors.ImageSharp.PixelFormats;
  9. using SixLabors.ImageSharp.Processing;
  10. using System.Diagnostics;
  11. using System.Numerics;
  12. byte[] byteArray = new byte[128 * 296 / 8];
  13. var list = new List<byte>();
  14. var collection = new FontCollection();
  15. var family = collection.Add("./SmileySans-Oblique.ttf");
  16. var font = family.CreateFont(18, FontStyle.Bold);
  17. using (var image = Image.Load<Rgba32>("face.jpg"))
  18. {
  19. using var overlay = Image.Load<Rgba32>("bzhan.png");
  20. overlay.Mutate(x =>
  21. {
  22. x.Resize(new Size(50,50));
  23. });
  24. // Convert the image to grayscale
  25. image.Mutate(x =>
  26. {
  27. x.DrawImage(overlay, new Point(0, 64), opacity: 1);
  28. x.DrawText("粉丝数:", font, Color.Black, new Vector2(20, 220));
  29. x.DrawText("999999", font, Color.Black, new Vector2(20, 260));
  30. x.Grayscale();
  31. });
  32. image.Save("test.jpg");
  33. byteArray = image.EnCodeImageToBytes();
  34. }
  35. var hidDevice = new Hw75DynamicDevice();
  36. hidDevice.Open();
  37. Stopwatch sw = Stopwatch.StartNew();
  38. sw.Start();
  39. var data111 = hidDevice.SetEInkImage(byteArray, 0, 0, 128, 296, false);
  40. sw.Stop();
  41. Console.WriteLine($"send data ms:{sw.ElapsedMilliseconds}");
  42. Console.ReadKey();
  43. Hid.Exit();

个人心得体会

这次功能的编写让我最有感悟的地方就是自己对Github Copilot的依赖更多了,我基本上很多的知识都是询问它,因为从网上搜索还要自己过滤那些数据,比较耽误时间。

还有个点就是这个HidApi.Net的库是最近刚有人写的,社区还是有新鲜的血液的,支持.net6,7,8很新,也算是个惊喜呢,希望社区的轮子越来越多呢!!!!

其他角度的照片展示:

侧面

背面

参考推荐文档项目如下:

原文链接:https://www.cnblogs.com/GreenShade/p/17911066.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号