diff --git a/server/src/Common.cs b/server/src/Common.cs
deleted file mode 100644
index ee7cd7a..0000000
--- a/server/src/Common.cs
+++ /dev/null
@@ -1,731 +0,0 @@
-using System.Collections;
-using DotNext;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Formats.Jpeg;
-using SixLabors.ImageSharp.PixelFormats;
-using System.Text;
-
-namespace Common
-{
- ///
- /// 数字处理工具
- ///
- public class Number
- {
- private static readonly byte[] BitReverseTable = new byte[] {
- 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
- 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
- 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
- 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
- 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
- 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
- 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
- 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
- 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
- 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
- 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
- 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
- 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
- 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
- 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
- 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
- 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
- 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
- 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
- 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
- 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
- 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
- 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
- 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
- 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
- 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
- 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
- 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
- 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
- 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
- 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
- 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
- };
-
- ///
- /// 整数转成二进制字节数组
- ///
- /// 整数
- /// 整数长度
- /// 是否高位在数组索引低
- /// 二进制字节数组
- public static Result NumberToBytes(ulong num, uint length, bool isLowNumHigh = false)
- {
- if (length > 8)
- {
- return new(new ArgumentException(
- "Unsigned long number can't over 8 bytes(64 bits).",
- nameof(length)
- ));
- }
-
- var arr = new byte[length];
-
- if (isLowNumHigh)
- {
- for (var i = 0; i < length; i++)
- {
- arr[length - 1 - i] = Convert.ToByte((num >> (i << 3)) & (0xFF));
- }
- }
- else
- {
- for (var i = 0; i < length; i++)
- {
- arr[i] = Convert.ToByte((num >> ((int)(length - 1 - i) << 3)) & (0xFF));
- }
- }
- return arr;
- }
-
- ///
- /// 二进制字节数组转成64bits整数
- ///
- /// 二进制字节数组
- /// 是否高位在数组索引低
- /// 整数
- public static Result BytesToUInt64(byte[] bytes, bool isLowNumHigh = false)
- {
- if (bytes.Length > 8)
- {
- return new(new ArgumentException(
- "Unsigned long number can't over 8 bytes(64 bits).",
- nameof(bytes)
- ));
- }
-
- UInt64 num = 0;
- int len = bytes.Length;
-
- try
- {
- if (isLowNumHigh)
- {
- for (var i = 0; i < len; i++)
- {
- num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3));
- }
- }
- else
- {
- for (var i = 0; i < len; i++)
- {
- num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3));
- }
- }
-
- return num;
- }
- catch (Exception error)
- {
- return new(error);
- }
- }
-
- ///
- /// 二进制字节数组转成32bits整数
- ///
- /// 二进制字节数组
- /// 是否高位在数组索引低
- /// 整数
- public static Result BytesToUInt32(byte[] bytes, bool isLowNumHigh = false)
- {
- if (bytes.Length > 4)
- {
- return new(new ArgumentException(
- "Unsigned long number can't over 4 bytes(32 bits).",
- nameof(bytes)
- ));
- }
-
- UInt32 num = 0;
- int len = bytes.Length;
-
- try
- {
- if (isLowNumHigh)
- {
- for (var i = 0; i < len; i++)
- {
- num += Convert.ToUInt32((UInt32)bytes[len - 1 - i] << (i << 3));
- }
- }
- else
- {
- for (var i = 0; i < len; i++)
- {
- num += Convert.ToUInt32((UInt32)bytes[i] << ((int)(len - 1 - i) << 3));
- }
- }
-
- return num;
- }
- catch (Exception error)
- {
- return new(error);
- }
-
- }
-
- ///
- /// [TODO:description]
- ///
- /// [TODO:parameter]
- /// [TODO:return]
- public static Result UInt32ArrayToBytes(UInt32[] uintArray)
- {
- byte[] byteArray = new byte[uintArray.Length * 4];
- try
- {
- Buffer.BlockCopy(uintArray, 0, byteArray, 0, uintArray.Length * 4);
- return byteArray;
- }
- catch (Exception error)
- {
- return new(error);
- }
- }
-
-
-
- ///
- /// 比特合并成二进制字节
- ///
- /// 第一个比特值
- /// 第一个比特值的长度(位数)
- /// 第二个比特值
- /// 第二个比特值的长度(位数)
- /// 合并后的二进制字节数组
- public static Result MultiBitsToBytes(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
- {
- return NumberToBytes(MultiBitsToNumber(bits1, bits1Len, bits2, bits2Len).Value,
- (bits1Len + bits2Len) % 8 != 0 ? (bits1Len + bits2Len) / 8 + 1 : (bits1Len + bits2Len) / 8);
- }
-
- ///
- /// 比特合并成整型
- ///
- /// 第一个比特值
- /// 第一个比特值的长度(位数)
- /// 第二个比特值
- /// 第二个比特值的长度(位数)
- /// 合并后的整型值
- public static Result MultiBitsToNumber(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
- {
- if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
-
- ulong num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
- return num;
- }
-
- ///
- /// 比特合并成整型
- ///
- /// 第一个比特值
- /// 第一个比特值的长度(位数)
- /// 第二个比特值
- /// 第二个比特值的长度(位数)
- /// 合并后的整型值
- public static Result MultiBitsToNumber(uint bits1, uint bits1Len, uint bits2, uint bits2Len)
- {
- if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
-
- uint num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
- return num;
- }
-
- ///
- /// 比特位检查
- ///
- /// 源比特值
- /// 目标比特值
- /// 掩码(默认为全1)
- /// 检查结果(是否匹配)
- public static bool BitsCheck(ulong srcBits, ulong dstBits, ulong mask = 0xFFFF_FFFF_FFFF_FFFF)
- {
- return (srcBits & mask) == dstBits;
- }
-
- ///
- /// 比特位检查
- ///
- /// 源比特值
- /// 目标比特值
- /// 掩码(默认为全1)
- /// 检查结果(是否匹配)
- public static bool BitsCheck(uint srcBits, uint dstBits, uint mask = 0xFFFF_FFFF)
- {
- return (srcBits & mask) == dstBits;
- }
-
- ///
- /// 获取整型对应位置的比特
- ///
- /// 整型数字
- /// 位置
- /// 比特
- public static Result ToBit(UInt32 srcBits, int location)
- {
- if (location < 0)
- return new(new ArgumentException(
- "Location can't be negetive", nameof(location)));
-
- return ((srcBits >> location) & ((UInt32)0b1)) == 1;
- }
-
- ///
- /// 将BitArray转化为32bits无符号整型
- ///
- /// BitArray比特数组
- /// 32bits无符号整型
- public static Result BitsToNumber(BitArray bits)
- {
- if (bits.Length > 32)
- throw new ArgumentException("Argument length shall be at most 32 bits.");
-
- var array = new UInt32[1];
- bits.CopyTo(array, 0);
- return array[0];
- }
-
-
- ///
- /// 字符串转二进制字节数组
- ///
- /// 输入的字符串
- /// 进制(默认为16进制)
- /// 转换后的二进制字节数组
- public static byte[] StringToBytes(string str, int numBase = 16)
- {
- var len = str.Length;
- var bytesLen = len / 2;
- var bytes = new byte[bytesLen];
-
- for (var i = 0; i < bytesLen; i++)
- {
- bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
- }
-
- return bytes;
- }
-
- ///
- /// 反转字节数组中的子数组
- ///
- /// 源字节数组
- /// 子数组的长度(反转的步长)
- /// 反转后的字节数组
- public static Result ReverseBytes(byte[] srcBytes, int distance)
- {
- if (distance <= 0)
- return new(new ArgumentException("Distance can't be negetive", nameof(distance)));
-
- var srcBytesLen = srcBytes.Length;
- if (distance > srcBytesLen)
- return new(new ArgumentException(
- "Distance is larger than bytesArray", nameof(distance)));
- if (srcBytesLen % distance != 0)
- return new(new ArgumentException(
- "The length of bytes can't be divided by distance without reminder", nameof(distance)));
-
- var dstBytes = new byte[srcBytesLen];
- var buffer = new byte[distance];
-
- for (int i = 0; i < srcBytesLen; i += distance)
- {
- var end = i + distance;
- buffer = srcBytes[i..end];
- Array.Reverse(buffer);
- Array.Copy(buffer, 0, dstBytes, i, distance);
- }
-
- return dstBytes;
- }
-
- ///
- /// 反转字节内比特顺序(使用查找表的方法)
- ///
- /// 字节
- /// 反转后的字节
- public static byte ReverseBits(byte srcByte)
- {
- return BitReverseTable[srcByte];
- }
-
- ///
- /// 反转字节数组的字节内比特顺序(使用查找表的方法)
- ///
- /// 字节数组
- /// 反转后的字节字节数组
- public static byte[] ReverseBits(byte[] srcBytes)
- {
- var bytesLen = srcBytes.Length;
- var dstBytes = new byte[bytesLen];
- for (int i = 0; i < bytesLen; i++)
- {
- dstBytes[i] = BitReverseTable[srcBytes[i]];
- }
- return dstBytes;
- }
- }
-
- ///
- /// 字符串处理工具
- ///
- public class String
- {
- ///
- /// 反转字符串
- ///
- /// 输入的字符串
- /// 反转后的字符串
- public static string Reverse(string s)
- {
- char[] charArray = s.ToCharArray();
- Array.Reverse(charArray);
- return new string(charArray);
- }
-
- }
-
- ///
- /// 图像处理工具
- ///
- public class Image
- {
- ///
- /// 将 RGB565 格式转换为 RGB24 格式
- /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
- /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
- ///
- /// RGB565格式的原始数据
- /// 图像宽度
- /// 图像高度
- /// 是否为小端序(默认为true)
- /// RGB24格式的转换后数据
- public static Result ConvertRGB565ToRGB24(byte[] rgb565Data, int width, int height, bool isLittleEndian = true)
- {
- if (rgb565Data == null)
- return new(new ArgumentNullException(nameof(rgb565Data)));
-
- if (width <= 0 || height <= 0)
- return new(new ArgumentException("Width and height must be positive"));
-
- // 计算像素数量
- var expectedPixelCount = width * height;
- var actualPixelCount = rgb565Data.Length / 2;
-
- if (actualPixelCount < expectedPixelCount)
- {
- return new(new ArgumentException(
- $"RGB565 data length insufficient. Expected: {expectedPixelCount * 2} bytes, Actual: {rgb565Data.Length} bytes"));
- }
-
- try
- {
- var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
- var rgb24Data = new byte[pixelCount * 3];
-
- for (int i = 0; i < pixelCount; i++)
- {
- // 读取 RGB565 数据
- var rgb565Index = i * 2;
- if (rgb565Index + 1 >= rgb565Data.Length) break;
-
- // 组合成16位值
- UInt16 rgb565;
- if (isLittleEndian)
- {
- rgb565 = (UInt16)(rgb565Data[rgb565Index] | (rgb565Data[rgb565Index + 1] << 8));
- }
- else
- {
- rgb565 = (UInt16)((rgb565Data[rgb565Index] << 8) | rgb565Data[rgb565Index + 1]);
- }
-
- // 提取各颜色分量
- var r5 = (rgb565 >> 11) & 0x1F; // 高5位为红色
- var g6 = (rgb565 >> 5) & 0x3F; // 中间6位为绿色
- var b5 = rgb565 & 0x1F; // 低5位为蓝色
-
- // 转换为8位颜色值
- var r8 = (byte)((r5 * 255) / 31); // 5位扩展到8位
- var g8 = (byte)((g6 * 255) / 63); // 6位扩展到8位
- var b8 = (byte)((b5 * 255) / 31); // 5位扩展到8位
-
- // 存储到 RGB24 数组
- var rgb24Index = i * 3;
- rgb24Data[rgb24Index] = r8; // R
- rgb24Data[rgb24Index + 1] = g8; // G
- rgb24Data[rgb24Index + 2] = b8; // B
- }
-
- return rgb24Data;
- }
- catch (Exception ex)
- {
- return new(ex);
- }
- }
-
- ///
- /// 将 RGB24 格式转换为 RGB565 格式
- /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
- /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
- ///
- /// RGB24格式的原始数据
- /// 图像宽度
- /// 图像高度
- /// 是否为小端序(默认为true)
- /// RGB565格式的转换后数据
- public static Result ConvertRGB24ToRGB565(byte[] rgb24Data, int width, int height, bool isLittleEndian = true)
- {
- if (rgb24Data == null)
- return new(new ArgumentNullException(nameof(rgb24Data)));
-
- if (width <= 0 || height <= 0)
- return new(new ArgumentException("Width and height must be positive"));
-
- var expectedPixelCount = width * height;
- var actualPixelCount = rgb24Data.Length / 3;
-
- if (actualPixelCount < expectedPixelCount)
- {
- return new(new ArgumentException(
- $"RGB24 data length insufficient. Expected: {expectedPixelCount * 3} bytes, Actual: {rgb24Data.Length} bytes"));
- }
-
- try
- {
- var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
- var rgb565Data = new byte[pixelCount * 2];
-
- for (int i = 0; i < pixelCount; i++)
- {
- var rgb24Index = i * 3;
- if (rgb24Index + 2 >= rgb24Data.Length) break;
-
- // 读取 RGB24 数据
- var r8 = rgb24Data[rgb24Index];
- var g8 = rgb24Data[rgb24Index + 1];
- var b8 = rgb24Data[rgb24Index + 2];
-
- // 转换为5位、6位、5位
- var r5 = (UInt16)((r8 * 31) / 255);
- var g6 = (UInt16)((g8 * 63) / 255);
- var b5 = (UInt16)((b8 * 31) / 255);
-
- // 组合成16位值
- var rgb565 = (UInt16)((r5 << 11) | (g6 << 5) | b5);
-
- // 存储到 RGB565 数组
- var rgb565Index = i * 2;
- if (isLittleEndian)
- {
- rgb565Data[rgb565Index] = (byte)(rgb565 & 0xFF);
- rgb565Data[rgb565Index + 1] = (byte)(rgb565 >> 8);
- }
- else
- {
- rgb565Data[rgb565Index] = (byte)(rgb565 >> 8);
- rgb565Data[rgb565Index + 1] = (byte)(rgb565 & 0xFF);
- }
- }
-
- return rgb565Data;
- }
- catch (Exception ex)
- {
- return new(ex);
- }
- }
-
- ///
- /// 将 RGB24 数据转换为 JPEG 格式
- ///
- /// RGB24格式的图像数据
- /// 图像宽度
- /// 图像高度
- /// JPEG质量(1-100,默认80)
- /// JPEG格式的字节数组
- public static Result ConvertRGB24ToJpeg(byte[] rgb24Data, int width, int height, int quality = 80)
- {
- if (rgb24Data == null)
- return new(new ArgumentNullException(nameof(rgb24Data)));
-
- if (width <= 0 || height <= 0)
- return new(new ArgumentException("Width and height must be positive"));
-
- if (quality < 1 || quality > 100)
- return new(new ArgumentException("Quality must be between 1 and 100"));
-
- var expectedDataLength = width * height * 3;
- if (rgb24Data.Length < expectedDataLength)
- {
- return new(new ArgumentException(
- $"RGB24 data length insufficient. Expected: {expectedDataLength} bytes, Actual: {rgb24Data.Length} bytes"));
- }
-
- try
- {
- using var image = new SixLabors.ImageSharp.Image(width, height);
-
- // 将 RGB 数据复制到 ImageSharp 图像
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- int index = (y * width + x) * 3;
- if (index + 2 < rgb24Data.Length)
- {
- var pixel = new Rgb24(rgb24Data[index], rgb24Data[index + 1], rgb24Data[index + 2]);
- image[x, y] = pixel;
- }
- }
- }
-
- using var stream = new MemoryStream();
- image.SaveAsJpeg(stream, new JpegEncoder { Quality = quality });
- return stream.ToArray();
- }
- catch (Exception ex)
- {
- return new(ex);
- }
- }
-
- ///
- /// 将 RGB565 数据直接转换为 JPEG 格式
- ///
- /// RGB565格式的图像数据
- /// 图像宽度
- /// 图像高度
- /// JPEG质量(1-100,默认80)
- /// 是否为小端序(默认为true)
- /// JPEG格式的字节数组
- public static Result ConvertRGB565ToJpeg(byte[] rgb565Data, int width, int height, int quality = 80, bool isLittleEndian = true)
- {
- // 先转换为RGB24
- var rgb24Result = ConvertRGB565ToRGB24(rgb565Data, width, height, isLittleEndian);
- if (!rgb24Result.IsSuccessful)
- {
- return new(rgb24Result.Error);
- }
-
- // 再转换为JPEG
- return ConvertRGB24ToJpeg(rgb24Result.Value, width, height, quality);
- }
-
- ///
- /// 创建 MJPEG 帧头部
- ///
- /// 帧数据长度
- /// 边界字符串(默认为"--boundary")
- /// MJPEG帧头部字节数组
- public static byte[] CreateMjpegFrameHeader(int frameDataLength, string boundary = "--boundary")
- {
- var header = $"{boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {frameDataLength}\r\n\r\n";
- return Encoding.ASCII.GetBytes(header);
- }
-
- ///
- /// 创建 MJPEG 帧尾部
- ///
- /// MJPEG帧尾部字节数组
- public static byte[] CreateMjpegFrameFooter()
- {
- return Encoding.ASCII.GetBytes("\r\n");
- }
-
- ///
- /// 创建完整的 MJPEG 帧数据
- ///
- /// JPEG数据
- /// 边界字符串(默认为"--boundary")
- /// 完整的MJPEG帧数据
- public static Result CreateMjpegFrame(byte[] jpegData, string boundary = "--boundary")
- {
- if (jpegData == null)
- return new(new ArgumentNullException(nameof(jpegData)));
-
- try
- {
- var header = CreateMjpegFrameHeader(jpegData.Length, boundary);
- var footer = CreateMjpegFrameFooter();
-
- var totalLength = header.Length + jpegData.Length + footer.Length;
- var frameData = new byte[totalLength];
-
- var offset = 0;
- Array.Copy(header, 0, frameData, offset, header.Length);
- offset += header.Length;
-
- Array.Copy(jpegData, 0, frameData, offset, jpegData.Length);
- offset += jpegData.Length;
-
- Array.Copy(footer, 0, frameData, offset, footer.Length);
-
- return frameData;
- }
- catch (Exception ex)
- {
- return new(ex);
- }
- }
-
- ///
- /// 验证图像数据长度是否正确
- ///
- /// 图像数据
- /// 图像宽度
- /// 图像高度
- /// 每像素字节数
- /// 验证结果
- public static bool ValidateImageDataLength(byte[] data, int width, int height, int bytesPerPixel)
- {
- if (data == null || width <= 0 || height <= 0 || bytesPerPixel <= 0)
- return false;
-
- var expectedLength = width * height * bytesPerPixel;
- return data.Length >= expectedLength;
- }
-
- ///
- /// 获取图像格式信息
- ///
- /// 图像格式枚举
- /// 格式信息
- public static ImageFormatInfo GetImageFormatInfo(ImageFormat format)
- {
- return format switch
- {
- ImageFormat.RGB565 => new ImageFormatInfo("RGB565", 2, "16-bit RGB format (5R+6G+5B)"),
- ImageFormat.RGB24 => new ImageFormatInfo("RGB24", 3, "24-bit RGB format (8R+8G+8B)"),
- ImageFormat.RGBA32 => new ImageFormatInfo("RGBA32", 4, "32-bit RGBA format (8R+8G+8B+8A)"),
- ImageFormat.Grayscale8 => new ImageFormatInfo("Grayscale8", 1, "8-bit grayscale format"),
- _ => new ImageFormatInfo("Unknown", 0, "Unknown image format")
- };
- }
- }
-
- ///
- /// 图像格式枚举
- ///
- public enum ImageFormat
- {
- RGB565,
- RGB24,
- RGBA32,
- Grayscale8
- }
-
- ///
- /// 图像格式信息
- ///
- public record ImageFormatInfo(string Name, int BytesPerPixel, string Description);
-}
diff --git a/server/src/Common/Image.cs b/server/src/Common/Image.cs
new file mode 100644
index 0000000..00e64b1
--- /dev/null
+++ b/server/src/Common/Image.cs
@@ -0,0 +1,358 @@
+using System.Text;
+using DotNext;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace Common;
+
+///
+/// 图像处理工具
+///
+public class Image
+{
+ ///
+ /// 将 RGB565 格式转换为 RGB24 格式
+ /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
+ /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
+ ///
+ /// RGB565格式的原始数据
+ /// 图像宽度
+ /// 图像高度
+ /// 是否为小端序(默认为true)
+ /// RGB24格式的转换后数据
+ public static Result ConvertRGB565ToRGB24(byte[] rgb565Data, int width, int height, bool isLittleEndian = true)
+ {
+ if (rgb565Data == null)
+ return new(new ArgumentNullException(nameof(rgb565Data)));
+
+ if (width <= 0 || height <= 0)
+ return new(new ArgumentException("Width and height must be positive"));
+
+ // 计算像素数量
+ var expectedPixelCount = width * height;
+ var actualPixelCount = rgb565Data.Length / 2;
+
+ if (actualPixelCount < expectedPixelCount)
+ {
+ return new(new ArgumentException(
+ $"RGB565 data length insufficient. Expected: {expectedPixelCount * 2} bytes, Actual: {rgb565Data.Length} bytes"));
+ }
+
+ try
+ {
+ var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
+ var rgb24Data = new byte[pixelCount * 3];
+
+ for (int i = 0; i < pixelCount; i++)
+ {
+ // 读取 RGB565 数据
+ var rgb565Index = i * 2;
+ if (rgb565Index + 1 >= rgb565Data.Length) break;
+
+ // 组合成16位值
+ UInt16 rgb565;
+ if (isLittleEndian)
+ {
+ rgb565 = (UInt16)(rgb565Data[rgb565Index] | (rgb565Data[rgb565Index + 1] << 8));
+ }
+ else
+ {
+ rgb565 = (UInt16)((rgb565Data[rgb565Index] << 8) | rgb565Data[rgb565Index + 1]);
+ }
+
+ // 提取各颜色分量
+ var r5 = (rgb565 >> 11) & 0x1F; // 高5位为红色
+ var g6 = (rgb565 >> 5) & 0x3F; // 中间6位为绿色
+ var b5 = rgb565 & 0x1F; // 低5位为蓝色
+
+ // 转换为8位颜色值
+ var r8 = (byte)((r5 * 255) / 31); // 5位扩展到8位
+ var g8 = (byte)((g6 * 255) / 63); // 6位扩展到8位
+ var b8 = (byte)((b5 * 255) / 31); // 5位扩展到8位
+
+ // 存储到 RGB24 数组
+ var rgb24Index = i * 3;
+ rgb24Data[rgb24Index] = r8; // R
+ rgb24Data[rgb24Index + 1] = g8; // G
+ rgb24Data[rgb24Index + 2] = b8; // B
+ }
+
+ return rgb24Data;
+ }
+ catch (Exception ex)
+ {
+ return new(ex);
+ }
+ }
+
+ ///
+ /// 将 RGB24 格式转换为 RGB565 格式
+ /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
+ /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
+ ///
+ /// RGB24格式的原始数据
+ /// 图像宽度
+ /// 图像高度
+ /// 是否为小端序(默认为true)
+ /// RGB565格式的转换后数据
+ public static Result ConvertRGB24ToRGB565(byte[] rgb24Data, int width, int height, bool isLittleEndian = true)
+ {
+ if (rgb24Data == null)
+ return new(new ArgumentNullException(nameof(rgb24Data)));
+
+ if (width <= 0 || height <= 0)
+ return new(new ArgumentException("Width and height must be positive"));
+
+ var expectedPixelCount = width * height;
+ var actualPixelCount = rgb24Data.Length / 3;
+
+ if (actualPixelCount < expectedPixelCount)
+ {
+ return new(new ArgumentException(
+ $"RGB24 data length insufficient. Expected: {expectedPixelCount * 3} bytes, Actual: {rgb24Data.Length} bytes"));
+ }
+
+ try
+ {
+ var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
+ var rgb565Data = new byte[pixelCount * 2];
+
+ for (int i = 0; i < pixelCount; i++)
+ {
+ var rgb24Index = i * 3;
+ if (rgb24Index + 2 >= rgb24Data.Length) break;
+
+ // 读取 RGB24 数据
+ var r8 = rgb24Data[rgb24Index];
+ var g8 = rgb24Data[rgb24Index + 1];
+ var b8 = rgb24Data[rgb24Index + 2];
+
+ // 转换为5位、6位、5位
+ var r5 = (UInt16)((r8 * 31) / 255);
+ var g6 = (UInt16)((g8 * 63) / 255);
+ var b5 = (UInt16)((b8 * 31) / 255);
+
+ // 组合成16位值
+ var rgb565 = (UInt16)((r5 << 11) | (g6 << 5) | b5);
+
+ // 存储到 RGB565 数组
+ var rgb565Index = i * 2;
+ if (isLittleEndian)
+ {
+ rgb565Data[rgb565Index] = (byte)(rgb565 & 0xFF);
+ rgb565Data[rgb565Index + 1] = (byte)(rgb565 >> 8);
+ }
+ else
+ {
+ rgb565Data[rgb565Index] = (byte)(rgb565 >> 8);
+ rgb565Data[rgb565Index + 1] = (byte)(rgb565 & 0xFF);
+ }
+ }
+
+ return rgb565Data;
+ }
+ catch (Exception ex)
+ {
+ return new(ex);
+ }
+ }
+
+ ///
+ /// 将 RGB24 数据转换为 JPEG 格式
+ ///
+ /// RGB24格式的图像数据
+ /// 图像宽度
+ /// 图像高度
+ /// JPEG质量(1-100,默认80)
+ /// JPEG格式的字节数组
+ public static Result ConvertRGB24ToJpeg(byte[] rgb24Data, int width, int height, int quality = 80)
+ {
+ if (rgb24Data == null)
+ return new(new ArgumentNullException(nameof(rgb24Data)));
+
+ if (width <= 0 || height <= 0)
+ return new(new ArgumentException("Width and height must be positive"));
+
+ if (quality < 1 || quality > 100)
+ return new(new ArgumentException("Quality must be between 1 and 100"));
+
+ var expectedDataLength = width * height * 3;
+ if (rgb24Data.Length < expectedDataLength)
+ {
+ return new(new ArgumentException(
+ $"RGB24 data length insufficient. Expected: {expectedDataLength} bytes, Actual: {rgb24Data.Length} bytes"));
+ }
+
+ try
+ {
+ using var image = new SixLabors.ImageSharp.Image(width, height);
+
+ // 将 RGB 数据复制到 ImageSharp 图像
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int index = (y * width + x) * 3;
+ if (index + 2 < rgb24Data.Length)
+ {
+ var pixel = new Rgb24(rgb24Data[index], rgb24Data[index + 1], rgb24Data[index + 2]);
+ image[x, y] = pixel;
+ }
+ }
+ }
+
+ using var stream = new MemoryStream();
+ image.SaveAsJpeg(stream, new JpegEncoder { Quality = quality });
+ return stream.ToArray();
+ }
+ catch (Exception ex)
+ {
+ return new(ex);
+ }
+ }
+
+ ///
+ /// 将 RGB565 数据直接转换为 JPEG 格式
+ ///
+ /// RGB565格式的图像数据
+ /// 图像宽度
+ /// 图像高度
+ /// JPEG质量(1-100,默认80)
+ /// 是否为小端序(默认为true)
+ /// JPEG格式的字节数组
+ public static Result ConvertRGB565ToJpeg(byte[] rgb565Data, int width, int height, int quality = 80, bool isLittleEndian = true)
+ {
+ // 先转换为RGB24
+ var rgb24Result = ConvertRGB565ToRGB24(rgb565Data, width, height, isLittleEndian);
+ if (!rgb24Result.IsSuccessful)
+ {
+ return new(rgb24Result.Error);
+ }
+
+ // 再转换为JPEG
+ return ConvertRGB24ToJpeg(rgb24Result.Value, width, height, quality);
+ }
+
+ ///
+ /// 创建 MJPEG 帧头部
+ ///
+ /// 帧数据长度
+ /// 边界字符串(默认为"--boundary")
+ /// MJPEG帧头部字节数组
+ public static byte[] CreateMjpegFrameHeader(int frameDataLength, string boundary = "--boundary")
+ {
+ var header = $"{boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {frameDataLength}\r\n\r\n";
+ return Encoding.ASCII.GetBytes(header);
+ }
+
+ ///
+ /// 创建 MJPEG 帧尾部
+ ///
+ /// MJPEG帧尾部字节数组
+ public static byte[] CreateMjpegFrameFooter()
+ {
+ return Encoding.ASCII.GetBytes("\r\n");
+ }
+
+ ///
+ /// 创建完整的 MJPEG 帧数据
+ ///
+ /// JPEG数据
+ /// 边界字符串(默认为"--boundary")
+ /// 完整的MJPEG帧数据
+ public static Result CreateMjpegFrame(byte[] jpegData, string boundary = "--boundary")
+ {
+ if (jpegData == null)
+ return new(new ArgumentNullException(nameof(jpegData)));
+
+ try
+ {
+ var header = CreateMjpegFrameHeader(jpegData.Length, boundary);
+ var footer = CreateMjpegFrameFooter();
+
+ var totalLength = header.Length + jpegData.Length + footer.Length;
+ var frameData = new byte[totalLength];
+
+ var offset = 0;
+ Array.Copy(header, 0, frameData, offset, header.Length);
+ offset += header.Length;
+
+ Array.Copy(jpegData, 0, frameData, offset, jpegData.Length);
+ offset += jpegData.Length;
+
+ Array.Copy(footer, 0, frameData, offset, footer.Length);
+
+ return frameData;
+ }
+ catch (Exception ex)
+ {
+ return new(ex);
+ }
+ }
+
+ ///
+ /// 验证图像数据长度是否正确
+ ///
+ /// 图像数据
+ /// 图像宽度
+ /// 图像高度
+ /// 每像素字节数
+ /// 验证结果
+ public static bool ValidateImageDataLength(byte[] data, int width, int height, int bytesPerPixel)
+ {
+ if (data == null || width <= 0 || height <= 0 || bytesPerPixel <= 0)
+ return false;
+
+ var expectedLength = width * height * bytesPerPixel;
+ return data.Length >= expectedLength;
+ }
+
+ ///
+ /// 获取图像格式信息
+ ///
+ /// 图像格式枚举
+ /// 格式信息
+ public static ImageFormatInfo GetImageFormatInfo(ImageFormat format)
+ {
+ return format switch
+ {
+ ImageFormat.RGB565 => new ImageFormatInfo("RGB565", 2, "16-bit RGB format (5R+6G+5B)"),
+ ImageFormat.RGB24 => new ImageFormatInfo("RGB24", 3, "24-bit RGB format (8R+8G+8B)"),
+ ImageFormat.RGBA32 => new ImageFormatInfo("RGBA32", 4, "32-bit RGBA format (8R+8G+8B+8A)"),
+ ImageFormat.Grayscale8 => new ImageFormatInfo("Grayscale8", 1, "8-bit grayscale format"),
+ _ => new ImageFormatInfo("Unknown", 0, "Unknown image format")
+ };
+ }
+}
+
+///
+/// 图像格式枚举
+///
+public enum ImageFormat
+{
+ ///
+ /// RGB565
+ ///
+ RGB565,
+
+ ///
+ /// RGB888 / RGB24
+ ///
+ RGB24,
+
+ ///
+ /// RGBA8888 / RGBA32
+ ///
+ RGBA32,
+
+ ///
+ /// 灰度图
+ ///
+ Grayscale8
+}
+
+///
+/// 图像格式信息
+///
+public record ImageFormatInfo(string Name, int BytesPerPixel, string Description);
diff --git a/server/src/Common/Number.cs b/server/src/Common/Number.cs
new file mode 100644
index 0000000..0bffc08
--- /dev/null
+++ b/server/src/Common/Number.cs
@@ -0,0 +1,370 @@
+using System.Collections;
+using DotNext;
+
+namespace Common;
+///
+/// 数字处理工具
+///
+public class Number
+{
+ private static readonly byte[] BitReverseTable = new byte[] {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+ };
+
+ ///
+ /// 整数转成二进制字节数组
+ ///
+ /// 整数
+ /// 整数长度
+ /// 是否高位在数组索引低
+ /// 二进制字节数组
+ public static Result NumberToBytes(ulong num, uint length, bool isLowNumHigh = false)
+ {
+ if (length > 8)
+ {
+ return new(new ArgumentException(
+ "Unsigned long number can't over 8 bytes(64 bits).",
+ nameof(length)
+ ));
+ }
+
+ var arr = new byte[length];
+
+ if (isLowNumHigh)
+ {
+ for (var i = 0; i < length; i++)
+ {
+ arr[length - 1 - i] = Convert.ToByte((num >> (i << 3)) & (0xFF));
+ }
+ }
+ else
+ {
+ for (var i = 0; i < length; i++)
+ {
+ arr[i] = Convert.ToByte((num >> ((int)(length - 1 - i) << 3)) & (0xFF));
+ }
+ }
+ return arr;
+ }
+
+ ///
+ /// 二进制字节数组转成64bits整数
+ ///
+ /// 二进制字节数组
+ /// 是否高位在数组索引低
+ /// 整数
+ public static Result BytesToUInt64(byte[] bytes, bool isLowNumHigh = false)
+ {
+ if (bytes.Length > 8)
+ {
+ return new(new ArgumentException(
+ "Unsigned long number can't over 8 bytes(64 bits).",
+ nameof(bytes)
+ ));
+ }
+
+ UInt64 num = 0;
+ int len = bytes.Length;
+
+ try
+ {
+ if (isLowNumHigh)
+ {
+ for (var i = 0; i < len; i++)
+ {
+ num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3));
+ }
+ }
+ else
+ {
+ for (var i = 0; i < len; i++)
+ {
+ num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3));
+ }
+ }
+
+ return num;
+ }
+ catch (Exception error)
+ {
+ return new(error);
+ }
+ }
+
+ ///
+ /// 二进制字节数组转成32bits整数
+ ///
+ /// 二进制字节数组
+ /// 是否高位在数组索引低
+ /// 整数
+ public static Result BytesToUInt32(byte[] bytes, bool isLowNumHigh = false)
+ {
+ if (bytes.Length > 4)
+ {
+ return new(new ArgumentException(
+ "Unsigned long number can't over 4 bytes(32 bits).",
+ nameof(bytes)
+ ));
+ }
+
+ UInt32 num = 0;
+ int len = bytes.Length;
+
+ try
+ {
+ if (isLowNumHigh)
+ {
+ for (var i = 0; i < len; i++)
+ {
+ num += Convert.ToUInt32((UInt32)bytes[len - 1 - i] << (i << 3));
+ }
+ }
+ else
+ {
+ for (var i = 0; i < len; i++)
+ {
+ num += Convert.ToUInt32((UInt32)bytes[i] << ((int)(len - 1 - i) << 3));
+ }
+ }
+
+ return num;
+ }
+ catch (Exception error)
+ {
+ return new(error);
+ }
+
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:return]
+ public static Result UInt32ArrayToBytes(UInt32[] uintArray)
+ {
+ byte[] byteArray = new byte[uintArray.Length * 4];
+ try
+ {
+ Buffer.BlockCopy(uintArray, 0, byteArray, 0, uintArray.Length * 4);
+ return byteArray;
+ }
+ catch (Exception error)
+ {
+ return new(error);
+ }
+ }
+
+
+
+ ///
+ /// 比特合并成二进制字节
+ ///
+ /// 第一个比特值
+ /// 第一个比特值的长度(位数)
+ /// 第二个比特值
+ /// 第二个比特值的长度(位数)
+ /// 合并后的二进制字节数组
+ public static Result MultiBitsToBytes(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
+ {
+ return NumberToBytes(MultiBitsToNumber(bits1, bits1Len, bits2, bits2Len).Value,
+ (bits1Len + bits2Len) % 8 != 0 ? (bits1Len + bits2Len) / 8 + 1 : (bits1Len + bits2Len) / 8);
+ }
+
+ ///
+ /// 比特合并成整型
+ ///
+ /// 第一个比特值
+ /// 第一个比特值的长度(位数)
+ /// 第二个比特值
+ /// 第二个比特值的长度(位数)
+ /// 合并后的整型值
+ public static Result MultiBitsToNumber(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
+ {
+ if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
+
+ ulong num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
+ return num;
+ }
+
+ ///
+ /// 比特合并成整型
+ ///
+ /// 第一个比特值
+ /// 第一个比特值的长度(位数)
+ /// 第二个比特值
+ /// 第二个比特值的长度(位数)
+ /// 合并后的整型值
+ public static Result MultiBitsToNumber(uint bits1, uint bits1Len, uint bits2, uint bits2Len)
+ {
+ if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
+
+ uint num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
+ return num;
+ }
+
+ ///
+ /// 比特位检查
+ ///
+ /// 源比特值
+ /// 目标比特值
+ /// 掩码(默认为全1)
+ /// 检查结果(是否匹配)
+ public static bool BitsCheck(ulong srcBits, ulong dstBits, ulong mask = 0xFFFF_FFFF_FFFF_FFFF)
+ {
+ return (srcBits & mask) == dstBits;
+ }
+
+ ///
+ /// 比特位检查
+ ///
+ /// 源比特值
+ /// 目标比特值
+ /// 掩码(默认为全1)
+ /// 检查结果(是否匹配)
+ public static bool BitsCheck(uint srcBits, uint dstBits, uint mask = 0xFFFF_FFFF)
+ {
+ return (srcBits & mask) == dstBits;
+ }
+
+ ///
+ /// 获取整型对应位置的比特
+ ///
+ /// 整型数字
+ /// 位置
+ /// 比特
+ public static Result ToBit(UInt32 srcBits, int location)
+ {
+ if (location < 0)
+ return new(new ArgumentException(
+ "Location can't be negetive", nameof(location)));
+
+ return ((srcBits >> location) & ((UInt32)0b1)) == 1;
+ }
+
+ ///
+ /// 将BitArray转化为32bits无符号整型
+ ///
+ /// BitArray比特数组
+ /// 32bits无符号整型
+ public static Result BitsToNumber(BitArray bits)
+ {
+ if (bits.Length > 32)
+ throw new ArgumentException("Argument length shall be at most 32 bits.");
+
+ var array = new UInt32[1];
+ bits.CopyTo(array, 0);
+ return array[0];
+ }
+
+
+ ///
+ /// 字符串转二进制字节数组
+ ///
+ /// 输入的字符串
+ /// 进制(默认为16进制)
+ /// 转换后的二进制字节数组
+ public static byte[] StringToBytes(string str, int numBase = 16)
+ {
+ var len = str.Length;
+ var bytesLen = len / 2;
+ var bytes = new byte[bytesLen];
+
+ for (var i = 0; i < bytesLen; i++)
+ {
+ bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
+ }
+
+ return bytes;
+ }
+
+ ///
+ /// 反转字节数组中的子数组
+ ///
+ /// 源字节数组
+ /// 子数组的长度(反转的步长)
+ /// 反转后的字节数组
+ public static Result ReverseBytes(byte[] srcBytes, int distance)
+ {
+ if (distance <= 0)
+ return new(new ArgumentException("Distance can't be negetive", nameof(distance)));
+
+ var srcBytesLen = srcBytes.Length;
+ if (distance > srcBytesLen)
+ return new(new ArgumentException(
+ "Distance is larger than bytesArray", nameof(distance)));
+ if (srcBytesLen % distance != 0)
+ return new(new ArgumentException(
+ "The length of bytes can't be divided by distance without reminder", nameof(distance)));
+
+ var dstBytes = new byte[srcBytesLen];
+ var buffer = new byte[distance];
+
+ for (int i = 0; i < srcBytesLen; i += distance)
+ {
+ var end = i + distance;
+ buffer = srcBytes[i..end];
+ Array.Reverse(buffer);
+ Array.Copy(buffer, 0, dstBytes, i, distance);
+ }
+
+ return dstBytes;
+ }
+
+ ///
+ /// 反转字节内比特顺序(使用查找表的方法)
+ ///
+ /// 字节
+ /// 反转后的字节
+ public static byte ReverseBits(byte srcByte)
+ {
+ return BitReverseTable[srcByte];
+ }
+
+ ///
+ /// 反转字节数组的字节内比特顺序(使用查找表的方法)
+ ///
+ /// 字节数组
+ /// 反转后的字节字节数组
+ public static byte[] ReverseBits(byte[] srcBytes)
+ {
+ var bytesLen = srcBytes.Length;
+ var dstBytes = new byte[bytesLen];
+ for (int i = 0; i < bytesLen; i++)
+ {
+ dstBytes[i] = BitReverseTable[srcBytes[i]];
+ }
+ return dstBytes;
+ }
+}
diff --git a/server/src/Common/SemaphorePool.cs b/server/src/Common/SemaphorePool.cs
new file mode 100644
index 0000000..34ec9fb
--- /dev/null
+++ b/server/src/Common/SemaphorePool.cs
@@ -0,0 +1,103 @@
+using System.Collections.Concurrent;
+
+namespace Common;
+
+///
+/// [TODO:description]
+///
+public class SemaphorePool
+{
+ private SemaphoreSlim semaphore;
+ private ConcurrentQueue queue;
+ private int beginNum;
+
+ ///
+ /// [TODO:description]
+ ///
+ public int RemainingCount { get; }
+
+ ///
+ /// [TODO:description]
+ ///
+ public int MaxCount { get; }
+
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:return]
+ public SemaphorePool(int initialCount, int beginNum = 0)
+ {
+ semaphore = new SemaphoreSlim(initialCount);
+ queue = new ConcurrentQueue();
+ this.beginNum = beginNum;
+ this.RemainingCount = initialCount;
+ this.MaxCount = initialCount;
+ for (int i = 0; i < initialCount; i++)
+ {
+ queue.Enqueue(beginNum + i);
+ }
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:return]
+ public SemaphorePool(int initialCount, int maxCount, int beginNum = 0)
+ {
+ semaphore = new SemaphoreSlim(initialCount, maxCount);
+ queue = new ConcurrentQueue();
+ this.beginNum = beginNum;
+ this.RemainingCount = initialCount;
+ this.MaxCount = maxCount;
+ for (int i = 0; i < initialCount; i++)
+ {
+ queue.Enqueue(beginNum + i);
+ }
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:return]
+ public void Wait()
+ {
+ WaitAsync().Wait();
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:return]
+ public Task WaitAsync()
+ {
+ var tcs = new TaskCompletionSource();
+ queue.Enqueue(tcs);
+ semaphore.WaitAsync().ContinueWith(t =>
+ {
+ TaskCompletionSource popped;
+ if (queue.TryDequeue(out popped))
+ popped.SetResult(true);
+ });
+ return tcs.Task;
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:return]
+ public void Release()
+ {
+ semaphore.Release();
+ queue.Clear();
+ for (int i = 0; i < MaxCount; i++)
+ {
+ queue.Enqueue(beginNum + i);
+ }
+ }
+}
diff --git a/server/src/Common/String.cs b/server/src/Common/String.cs
new file mode 100644
index 0000000..cace185
--- /dev/null
+++ b/server/src/Common/String.cs
@@ -0,0 +1,20 @@
+namespace Common;
+
+///
+/// 字符串处理工具
+///
+public class String
+{
+ ///
+ /// 反转字符串
+ ///
+ /// 输入的字符串
+ /// 反转后的字符串
+ public static string Reverse(string s)
+ {
+ char[] charArray = s.ToCharArray();
+ Array.Reverse(charArray);
+ return new string(charArray);
+ }
+
+}
diff --git a/server/src/UdpClientPool.cs b/server/src/UdpClientPool.cs
index a0d9d0c..5dbddb9 100644
--- a/server/src/UdpClientPool.cs
+++ b/server/src/UdpClientPool.cs
@@ -311,10 +311,10 @@ public class UDPClientPool
/// IP端点(IP地址与端口)
/// 任务ID
/// 设备地址
- /// 要读取的数据长度(字节)
+ /// 要读取的数据长度(4字节)
/// 超时时间(毫秒)
/// 读取结果,包含接收到的字节数组
- public static async ValueTask> ReadAddrBytes(
+ public static async ValueTask> ReadAddr4Bytes(
IPEndPoint endPoint, int taskID, UInt32 devAddr, int dataLength, int timeout = 1000)
{
var ret = false;
@@ -330,7 +330,7 @@ public class UDPClientPool
return new(new Exception("Message bus not working!"));
// Calculate read times and segments
- var max4BytesPerRead = 0x80; // 1024 bytes per read
+ var max4BytesPerRead = 0x80; // 512 bytes per read
var rest4Bytes = dataLength % max4BytesPerRead;
var readTimes = (rest4Bytes != 0) ?
(dataLength / max4BytesPerRead + 1) :
diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs
index 934cede..8b636ca 100644
--- a/server/src/UdpServer.cs
+++ b/server/src/UdpServer.cs
@@ -72,7 +72,9 @@ public class UDPServer
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
- private static Dictionary> udpData = new Dictionary>();
+ private Dictionary> udpData = new Dictionary>();
+
+ private Semaphore taskPool = new Semaphore(3, 3);
private int listenPort;
private UdpClient listener;