diff --git a/.gitignore b/.gitignore
index 1ee53850b84cd478da00264a919c8180a746056e..4b08085765a4e27129240e9cebbaa288d0607a73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,3 +360,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
+/merge_cs_files.bat
+/AllCodeMerged.txt
diff --git a/App.config b/App.config
new file mode 100644
index 0000000000000000000000000000000000000000..193aecc675e7d4600fde2d37cf350bce7a056948
--- /dev/null
+++ b/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DeviceCommons(C++)/DeviceCommons(C++).cpp b/DeviceCommons(C++)/DeviceCommons(C++).cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4f7dd50141f69e1e0efbd95325e999290b1bc81
--- /dev/null
+++ b/DeviceCommons(C++)/DeviceCommons(C++).cpp
@@ -0,0 +1,21 @@
+#include "DeviceMessageBuilder.h"
+#include
+
+int main() {
+ using namespace DeviceCommons;
+
+ auto msg = DeviceMessageBuilder{}
+ .withVersion(2)
+ .withCRC(CRCType::CRC16)
+ .withMainDevice("MainDev", 0x94, {
+ Reading{1000, {State::makeString(1,"Online")}},
+ Reading{2000, {State::makeString(2,"OK")}}
+ })
+ .addChild("Child1", 0x95, {
+ Reading{3000, {State::makeString(1,"Temp"), State::makeString(2,"25.3")}}
+ })
+ .buildHex();
+
+ std::cout << msg << std::endl;
+ return 0;
+}
\ No newline at end of file
diff --git a/DeviceCommons(C++)/DeviceCommons(C++).vcxproj b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..7b77a62236c2c36f64b28aa38d5bd2e3d98a3ea3
--- /dev/null
+++ b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj
@@ -0,0 +1,138 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {1e257bea-3355-4c08-b114-d40a5db66d2f}
+ DeviceCommonsC
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..0a66dc9157d29ee045f982667122d45d7303af7b
--- /dev/null
+++ b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters
@@ -0,0 +1,27 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/DeviceCommons(C++)/DeviceMessageBuilder.h b/DeviceCommons(C++)/DeviceMessageBuilder.h
new file mode 100644
index 0000000000000000000000000000000000000000..263a9a1bc9c282ee2cb4d998987eed554472e6e3
--- /dev/null
+++ b/DeviceCommons(C++)/DeviceMessageBuilder.h
@@ -0,0 +1,161 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace DeviceCommons {
+
+ // ==================== ö ====================
+ enum class CRCType : uint8_t { None = 0, CRC8 = 1, CRC16 = 2, CRC32 = 3 };
+ enum class TimeStampFormat : uint8_t { MS = 0, S = 1 };
+ enum class HeaderValueType : uint8_t { Standard = 0, Extend = 1 };
+ enum class StateValueType : uint8_t {
+ Float32 = 1, Int32 = 2, String = 3, Bool = 4,
+ ShortFloat = 5, UInt16 = 6, Int16 = 7,
+ Timestamp = 8, Binary = 9, Double = 10
+ };
+
+ // ==================== ====================
+ inline std::string toHex(const std::vector& v) {
+ std::ostringstream oss;
+ for (auto b : v) oss << std::hex << std::setw(2) << std::setfill('0') << (int)b;
+ return oss.str();
+ }
+
+ inline void writeBE16(std::vector& dst, uint16_t v) {
+ dst.push_back(static_cast((v >> 8) & 0xFF));
+ dst.push_back(static_cast(v & 0xFF));
+ }
+
+ // ============ ״̬ ============
+ struct State {
+ uint8_t sid;
+ StateValueType type;
+ std::vector value;
+
+ static State makeString(uint8_t id, const std::string& str) {
+ State s{ id, StateValueType::String, {} };
+ s.value.push_back(static_cast(str.size()));
+ s.value.insert(s.value.end(), str.begin(), str.end());
+ return s;
+ }
+ };
+
+ // ============ ============
+ struct Reading {
+ int16_t timeOffset;
+ std::vector states;
+
+ std::vector serialize() const {
+ std::vector out;
+ writeBE16(out, static_cast(timeOffset));
+ out.push_back(static_cast(states.size()));
+ for (const auto& st : states) {
+ out.push_back(st.sid);
+ out.push_back(static_cast(st.type));
+ out.insert(out.end(), st.value.begin(), st.value.end());
+ }
+ return out;
+ }
+ };
+
+ // ============ 豸Ϣ ============
+ struct DeviceInfo {
+ std::string did;
+ uint8_t deviceType;
+ std::vector readings;
+
+ std::vector serialize() const {
+ std::vector out;
+ out.push_back(static_cast(did.size()));
+ out.insert(out.end(), did.begin(), did.end());
+ out.push_back(deviceType);
+ out.push_back(static_cast(readings.size()));
+ for (const auto& r : readings) {
+ auto sr = r.serialize();
+ out.insert(out.end(), sr.begin(), sr.end());
+ }
+ return out;
+ }
+ };
+
+ // ==================== CRC-16/IBM ====================
+ namespace {
+ uint16_t crc16(const std::vector& data) {
+ uint16_t crc = 0x0000;
+ for (uint8_t b : data) {
+ crc ^= uint16_t(b) << 8;
+ for (int i = 0; i < 8; ++i)
+ crc = (crc & 0x8000) ? (crc << 1) ^ 0x8005 : crc << 1;
+ crc &= 0xFFFF;
+ }
+ return crc;
+ }
+ }
+
+ // ==================== Builder ====================
+ class DeviceMessageBuilder {
+ uint8_t version_ = 1;
+ CRCType crcType_ = CRCType::CRC16;
+ TimeStampFormat tsFormat_ = TimeStampFormat::MS;
+ HeaderValueType valueType_ = HeaderValueType::Standard;
+
+ DeviceInfo mainDevice_;
+ std::vector children_;
+
+ public:
+ DeviceMessageBuilder& withVersion(uint8_t v) { version_ = v; return *this; }
+ DeviceMessageBuilder& withCRC(CRCType c) { crcType_ = c; return *this; }
+
+ DeviceMessageBuilder& withMainDevice(const std::string& did, uint8_t type,
+ std::vector readings = {}) {
+ mainDevice_ = DeviceInfo{ did, type, std::move(readings) };
+ return *this;
+ }
+
+ DeviceMessageBuilder& addChild(const std::string& did, uint8_t type,
+ std::vector readings = {}) {
+ children_.push_back(DeviceInfo{ did, type, std::move(readings) });
+ return *this;
+ }
+
+ std::vector buildBytes() const {
+ std::vector out;
+
+ // 1. Header (4 bytes)
+ out.push_back(0xC0); out.push_back(0xBF);
+ out.push_back(version_);
+ uint8_t mark = (static_cast(crcType_) << 4) |
+ (static_cast(valueType_) << 1) |
+ static_cast(tsFormat_);
+ out.push_back(mark);
+
+ // 2. Main Device
+ auto main = mainDevice_.serialize();
+ out.insert(out.end(), main.begin(), main.end());
+
+ // 3. Child Devices
+ out.push_back(static_cast(children_.size()));
+ for (const auto& ch : children_) {
+ auto c = ch.serialize();
+ out.insert(out.end(), c.begin(), c.end());
+ }
+
+ // 4. CRC16
+ if (crcType_ == CRCType::CRC16) {
+ uint16_t crc = crc16(out);
+ out.push_back(static_cast(crc & 0xFF)); // low
+ out.push_back(static_cast((crc >> 8) & 0xFF)); // high
+ }
+ return out;
+ }
+
+ std::string buildHex() const {
+ return toHex(buildBytes());
+ }
+ };
+
+} // namespace DeviceCommons
\ No newline at end of file
diff --git a/DeviceCommons.sln b/DeviceCommons.sln
new file mode 100644
index 0000000000000000000000000000000000000000..5a7b5667011be71ab883ef5d171ccf8df82f6442
--- /dev/null
+++ b/DeviceCommons.sln
@@ -0,0 +1,65 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35931.197
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommons", "DeviceCommons\DeviceCommons.csproj", "{8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DeviceCommons(C++)", "DeviceCommons(C++)\DeviceCommons(C++).vcxproj", "{1E257BEA-3355-4C08-B114-D40A5DB66D2F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x64.Build.0 = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x86.Build.0 = Debug|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x64.ActiveCfg = Release|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x64.Build.0 = Release|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x86.ActiveCfg = Release|Any CPU
+ {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x86.Build.0 = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x64.Build.0 = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x86.Build.0 = Debug|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x64.ActiveCfg = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x64.Build.0 = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x86.ActiveCfg = Release|Any CPU
+ {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x86.Build.0 = Release|Any CPU
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|Any CPU.Build.0 = Debug|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x64.ActiveCfg = Debug|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x64.Build.0 = Debug|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x86.ActiveCfg = Debug|Win32
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x86.Build.0 = Debug|Win32
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|Any CPU.ActiveCfg = Release|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|Any CPU.Build.0 = Release|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x64.ActiveCfg = Release|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x64.Build.0 = Release|x64
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x86.ActiveCfg = Release|Win32
+ {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {ED6E20DB-4EE1-4410-8B29-43312EDA8F02}
+ EndGlobalSection
+EndGlobal
diff --git a/DeviceCommons/DataHandling/Compression/Compressor.cs b/DeviceCommons/DataHandling/Compression/Compressor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b079b350fbb8de5db7cf6dd36ff7c1d0dedb7144
--- /dev/null
+++ b/DeviceCommons/DataHandling/Compression/Compressor.cs
@@ -0,0 +1,57 @@
+using System.IO.Compression;
+
+namespace DeviceCommons.DataHandling.Compression
+{
+ public class Compressor
+ {
+ public static byte[] Compress(ReadOnlySpan data)
+ {
+ return Compress(data.ToArray());
+ }
+
+ public static byte[] Compress(byte[] data)
+ {
+ using var compressedStream = new MemoryStream();
+ using var gzipStream = new GZipStream(compressedStream, CompressionLevel.Optimal);
+ gzipStream.Write(data, 0, data.Length);
+ return compressedStream.ToArray();
+ }
+
+ public static byte[] Decompress(byte[] compressedData)
+ {
+ using (var compressedStream = new MemoryStream(compressedData))
+ {
+ using (var decompressedStream = new MemoryStream())
+ {
+ using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
+ {
+ gzipStream.CopyTo(decompressedStream);
+ }
+ return decompressedStream.ToArray();
+ }
+ }
+ }
+
+ public static async ValueTask CompressAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default)
+ {
+ using var compressedStream = new MemoryStream();
+ using (var gzipStream = new GZipStream(compressedStream, CompressionLevel.Optimal, true))
+ {
+ await gzipStream.WriteAsync(data, cancellationToken).ConfigureAwait(false);
+ await gzipStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ return compressedStream.ToArray();
+ }
+
+ public static async ValueTask DecompressAsync(ReadOnlyMemory compressedData, CancellationToken cancellationToken = default)
+ {
+ using var compressedStream = new MemoryStream(compressedData.ToArray());
+ using var decompressedStream = new MemoryStream();
+ using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress, true))
+ {
+ await gzipStream.CopyToAsync(decompressedStream, cancellationToken).ConfigureAwait(false);
+ }
+ return decompressedStream.ToArray();
+ }
+ }
+}
diff --git a/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs b/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs
new file mode 100644
index 0000000000000000000000000000000000000000..48ce3b48e6cf5931b1a87172745475e5eec61d00
--- /dev/null
+++ b/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs
@@ -0,0 +1,27 @@
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DataHandling
+{
+ ///
+ /// 提供设备消息处理过程中使用的内存池和数组池资源
+ /// 用于优化内存分配,减少垃圾回收压力
+ ///
+ public class DeviceMessageArrayPool
+ {
+ /// 共享字节内存池实例
+ internal static readonly MemoryPool MemoryPool = MemoryPool.Shared;
+
+ /// 共享字节数组池实例
+ internal static readonly ArrayPool ByteArrayPool = ArrayPool.Shared;
+
+ /// 设备消息信息对象数组池
+ internal static readonly ArrayPool Info_ArrayPool = ArrayPool.Shared;
+
+ /// 设备消息读数信息对象数组池
+ internal static readonly ArrayPool InfoReading_ArrayPool = ArrayPool.Shared;
+
+ /// 设备消息读数状态对象数组池
+ internal static readonly ArrayPool InfoReadingState_ArrayPool = ArrayPool.Shared;
+ }
+}
diff --git a/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs b/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1296e35a23efb6295e64be4acff72c4708f444b1
--- /dev/null
+++ b/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs
@@ -0,0 +1,33 @@
+using DeviceCommons.DeviceMessages.Serialization;
+using DeviceCommons.DeviceMessages.Serialization.V1.Parsers;
+using DeviceCommons.DeviceMessages.Serialization.V1.Serializers;
+
+namespace DeviceCommons.DataHandling
+{
+ public static class DeviceMessageSerializerProvider
+ {
+ public static readonly IDeviceMessageSerializer MessageSer = new DeviceMessageSerializer();
+ public static readonly IDeviceMessageParser MessagePar = new DeviceMessageParser();
+
+ public static readonly IDeviceMessageHeaderSerializer HeaderSer = new DeviceMessageHeaderSerializer();
+ public static readonly IDeviceMessageHeaderParser HeaderPar = new DeviceMessageHeaderParser();
+
+ public static readonly IDeviceMessageInfoSerializer InfoSer = new DeviceMessageInfoSerializer();
+ public static readonly IDeviceMessageInfoParser InfoPar = new DeviceMessageInfoParser();
+
+ public static readonly IDeviceMessageInfoReadingsSerializer InfoReadingsSer = new DeviceMessageInfoReadingsSerializer();
+ public static readonly IDeviceMessageInfoReadingsParser InfoReadingsPar = new DeviceMessageInfoReadingsParser();
+
+ public static readonly IDeviceMessageInfoReadingSerializer InfoReadingSer = new DeviceMessageInfoReadingSerializer();
+ public static readonly IDeviceMessageInfoReadingParser InfoReadingPar = new DeviceMessageInfoReadingParser();
+
+ public static readonly IDeviceMessageInfoReadingStatesSerializer InfoReadingStatesSer = new DeviceMessageInfoReadingStatesSerializer();
+ public static readonly IDeviceMessageInfoReadingStatesParser InfoReadingStatesPar = new DeviceMessageInfoReadingStatesParser();
+
+ public static readonly IDeviceMessageInfoReadingStateSerializer InfoReadingStateSer = new DeviceMessageInfoReadingStateSerializer();
+ public static readonly IDeviceMessageInfoReadingStateParser InfoReadingStatePar = new DeviceMessageInfoReadingStateParser();
+
+ public static readonly IDeviceMessageChildSerializer ChildSer = new DeviceMessageChildSerializer();
+ public static readonly IDeviceMessageChildParser ChildPar = new DeviceMessageChildParser();
+ }
+}
diff --git a/DeviceCommons/DataHandling/DeviceMessageUtilities.cs b/DeviceCommons/DataHandling/DeviceMessageUtilities.cs
new file mode 100644
index 0000000000000000000000000000000000000000..daf10fbc1c944c128327623cb21945c5f689d2d7
--- /dev/null
+++ b/DeviceCommons/DataHandling/DeviceMessageUtilities.cs
@@ -0,0 +1,71 @@
+using DeviceCommons.DeviceMessages.Enums;
+
+namespace DeviceCommons.DataHandling
+{
+ public class DeviceMessageUtilities
+ {
+ public static void CheckBuffer(ReadOnlySpan buffer, int startIndex, int requiredLength)
+ {
+ ArgumentNullException.ThrowIfNull(nameof(buffer));
+
+ if ((uint)startIndex >= (uint)buffer.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+ if (buffer.Length - startIndex < requiredLength)
+ throw new ArgumentException("Insufficient buffer length");
+
+ if (buffer.IsEmpty || startIndex < 0 || startIndex >= buffer.Length)
+ throw new ArgumentException("Invalid data or startIndex");
+ }
+
+ public static int GetValueLength(StateValueTypeEnum valueType, ReadOnlySpan data, int startIndex = 0)
+ {
+ switch (valueType)
+ {
+ case StateValueTypeEnum.Float32:
+ case StateValueTypeEnum.Int32:
+ return 4;
+ case StateValueTypeEnum.String:
+ if (data == null || data.Length <= startIndex)
+ throw new FormatException("Invalid data format for string length field.");
+ return data[startIndex];
+ case StateValueTypeEnum.Bool:
+ return 1;
+ case StateValueTypeEnum.UInt16:
+ case StateValueTypeEnum.Int16:
+ return 2;
+ case StateValueTypeEnum.Binary:
+ if (data == null || data.Length < startIndex + 2)
+ throw new ArgumentException("Invalid data for binary length");
+ return BitConverter.ToUInt16(data.ToArray(), startIndex);
+ case StateValueTypeEnum.Timestamp:
+ return 8;
+ case StateValueTypeEnum.Double:
+ return 8;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(valueType), valueType, null);
+ }
+ }
+
+ public static int GetValueLength(ReadOnlySpan data, int startIndex)
+ {
+ CheckBuffer(data, startIndex, 0);
+ if (data[startIndex] > 10)
+ throw new ArgumentException("Insufficient buffer length");
+
+
+ var valueType = (StateValueTypeEnum)data[startIndex];
+ switch (valueType)
+ {
+ case StateValueTypeEnum.String:
+ CheckBuffer(data, startIndex, 1);
+ return GetValueLength(valueType, data, startIndex + 1) + 1;
+ case StateValueTypeEnum.Binary:
+ CheckBuffer(data, startIndex, 2);
+ return GetValueLength(valueType, data, startIndex + 1) + 2;
+ default:
+ return GetValueLength(valueType,null);
+ }
+ }
+ }
+}
diff --git a/DeviceCommons/DataHandling/Formatters/HexConverter.cs b/DeviceCommons/DataHandling/Formatters/HexConverter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a0f955d6c6716418456f3516c0814166ff7dc64b
--- /dev/null
+++ b/DeviceCommons/DataHandling/Formatters/HexConverter.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DataHandling.Formatters
+{
+ public static class HexConverter
+ {
+ public static byte[] FromHexString(this string hex) => Convert.FromHexString(hex);
+ public static string ToHexString(this ReadOnlySpan bytes) => Convert.ToHexString(bytes);
+ }
+}
diff --git a/DeviceCommons/DeviceCommons.csproj b/DeviceCommons/DeviceCommons.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..407846a5aee5323a116fd6d8a321849d9fee800f
--- /dev/null
+++ b/DeviceCommons/DeviceCommons.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs
new file mode 100644
index 0000000000000000000000000000000000000000..14a6d2ae951d3198969cf4a46c70d7ca49519211
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs
@@ -0,0 +1,10 @@
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public abstract class AbstractMessage where T : IBase
+ {
+ public T Model { get; set; }
+ protected AbstractMessage(T t) => Model = t;
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..996cbf8f77d1822b4336bf0ffdc0fe5aae058517
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs
@@ -0,0 +1,48 @@
+using DeviceCommons.DataHandling.Compression;
+using DeviceCommons.DataHandling.Formatters;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public abstract class AbstractMessageParser : AbstractMessage, IMessageParser where T : IBase
+ {
+ protected AbstractMessageParser(T t) : base(t)
+ {
+ }
+
+ public virtual Func? DecryptFunc { get; set; }
+ public abstract T Parser(ReadOnlySpan bytes);
+
+ public virtual T Parser(string data)
+ {
+ IsEncryptOrCompress(data, out string dataTemp, out bool isEncrypt, out bool isCompress);
+ if (isEncrypt)
+ {
+ if (DecryptFunc == null) throw new Exception("解密方法未定义");
+ dataTemp = DecryptFunc(dataTemp);
+ }
+ var bytes = dataTemp.FromHexString();
+
+ return Parser(isCompress ? Compressor.Decompress(bytes) : bytes);
+ }
+
+ public virtual async Task ParserAsync(byte[] bytes) => await Task.Run(() => Parser(bytes));
+
+ public virtual async Task ParserAsync(string data) => await Task.Run(() => Parser(data));
+
+ public static void IsEncryptOrCompress(string sourceData, out string data, out bool isEncrypt, out bool isCompress)
+ {
+ var decSplit = sourceData.Split('|');
+ data = decSplit[0];
+ isEncrypt = false;
+ isCompress = false;
+ if (decSplit.Length > 1)
+ {
+ var states = decSplit[0].Split(',');
+ data = decSplit[1];
+ isEncrypt = states[0].ToLower().Equals("enc");
+ isCompress = states[1].ToLower().Equals("gzip");
+ }
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..47fa85a890cf3c177dd6dd79079feb42bb94eabd
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs
@@ -0,0 +1,121 @@
+using DeviceCommons.DataHandling.Compression;
+using DeviceCommons.DataHandling.Formatters;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public abstract class AbstractMessageSerializer : AbstractMessage, IMessageSerializer where T : IBase
+ {
+ protected AbstractMessageSerializer(T t) : base(t)
+ {
+ }
+
+ public Func? EncryptFunc { get; set; }
+
+ public virtual void Serializer(
+ ArrayBufferWriter writer, T message
+ )
+ {
+ Model = message;
+ Serializer(writer);
+ }
+
+ public abstract void Serializer(
+ ArrayBufferWriter writer
+ );
+
+ public string Serializer(
+ ArrayBufferWriter writer, T message, bool isCompress
+ ) =>
+ Serializer(writer, message, false, isCompress);
+
+ public string Serializer(
+ ArrayBufferWriter writer, bool isCompress
+ ) =>
+ Serializer(writer, false, isCompress);
+
+ public virtual string Serializer(ArrayBufferWriter writer, T message, bool isEncrypt = false, bool isCompress = false)
+ {
+ string header = isEncrypt ? "enc," : "dec,";
+ header += isCompress ? "gzip|" : "raw|";
+ Serializer(writer, message);
+ ReadOnlySpan bytes = writer.WrittenSpan;
+ if (isCompress) bytes = Compressor.Compress(writer.GetSpan());
+ string hex = bytes.ToHexString().ToUpper();
+ if (isEncrypt)
+ {
+ if (EncryptFunc == null)
+ throw new InvalidOperationException("Encryption function is not defined. Please configure it via SetEncryptFunc.");
+ hex = EncryptFunc(hex);
+ }
+ return header + hex;
+ }
+
+ public string Serializer(ArrayBufferWriter writer, bool isEncrypt = false, bool isCompress = false)
+ {
+ string header = isEncrypt ? "enc," : "dec,";
+ header += isCompress ? "gzip|" : "raw|";
+ Serializer(writer);
+ ReadOnlySpan bytes = writer.GetSpan();
+ if (isCompress) bytes = Compressor.Compress(bytes);
+ string hex = bytes.ToHexString();
+ if (isEncrypt)
+ {
+ if (EncryptFunc == null) throw new InvalidOperationException("加密方法未定义,请先通过SetEncryptFunc配置");
+ hex = EncryptFunc(hex);
+ }
+ return header + hex;
+ }
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer, T message
+ ) =>
+ await Task.Run(() => Serializer(writer, message));
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer
+ ) =>
+ await Task.Run(() => Serializer(writer));
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer, T message, bool isCompress
+ ) =>
+ await Task.Run(() => Serializer(writer, message, isCompress));
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer, bool isCompress
+ ) =>
+ await Task.Run(() => Serializer(writer, isCompress));
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer, T message, bool isEncrypt, bool isCompress
+ ) =>
+ await Task.Run(() => Serializer(writer, message, isEncrypt, isCompress));
+
+ public virtual async Task SerializerAsync(
+ ArrayBufferWriter writer, bool isEncrypt, bool isCompress
+ ) =>
+ await Task.Run(() => Serializer(writer, isEncrypt, isCompress));
+
+ protected virtual void SerializeWithHeader(ArrayBufferWriter writer, int headerLength,
+ Action> writeHeaderAction, Action> serializeContentAction)
+ {
+ ArgumentNullException.ThrowIfNull(serializeContentAction);
+
+ serializeContentAction?.Invoke(writer);
+
+ int existingLength = writer.WrittenCount;
+ byte[] existingData = existingLength > 0 ? writer.WrittenSpan.ToArray() : [];
+
+ writer.Clear();
+
+ writeHeaderAction?.Invoke(writer);
+
+ if (existingLength > 0)
+ {
+ writer.Write(existingData);
+ }
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..31d69f9781c401c030890174e486e3f8d4092791
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs
@@ -0,0 +1,15 @@
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public interface IMessageParser : IMessagePayload
+ {
+ Func? DecryptFunc { get; set; }
+
+ T Parser(ReadOnlySpan bytes);
+
+ T Parser(string data);
+
+ Task ParserAsync(byte[] bytes);
+
+ Task ParserAsync(string data);
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c47ec21d50eee1ba78ceaae5d982a5ebcd6c5bb8
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs
@@ -0,0 +1,7 @@
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public interface IMessagePayload
+ {
+ T Model { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4c2751e80ee077b045e7d08de6bd4e863551be02
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs
@@ -0,0 +1,23 @@
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Abstractions
+{
+ public interface IMessageSerializer : IMessagePayload where T : IBase
+ {
+ Func? EncryptFunc { get; set; }
+
+ void Serializer(ArrayBufferWriter writer);
+
+ void Serializer(ArrayBufferWriter writer, T message);
+
+ string Serializer(ArrayBufferWriter writer, T message, bool isCompress);
+
+ string Serializer(ArrayBufferWriter writer, T message, bool isEncrypt = false, bool isCompress = false);
+
+ Task SerializerAsync(ArrayBufferWriter writer, T message);
+ Task SerializerAsync(ArrayBufferWriter writer, T message, bool isCompress);
+
+ Task SerializerAsync(ArrayBufferWriter writer, T message, bool isEncrypt, bool isCompress);
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..78faba0c2ca7dc76269630335be92daba61e5b34
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs
@@ -0,0 +1,35 @@
+using DeviceCommons.DeviceMessages.Models;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Builders
+{
+ public class DeviceInfoBuilder
+ {
+ private readonly DeviceMessageInfo _device;
+ private readonly List _readings = new();
+
+ public DeviceInfoBuilder(DeviceMessageInfo device) => _device = device;
+
+ public DeviceInfoBuilder AddReading(short timeOffset, Action config)
+ {
+ var reading = new DeviceMessageInfoReading
+ {
+ TimeOffset = timeOffset
+ };
+
+ var builder = new DeviceInfoReadingBuilder(reading, _device.DeviceType);
+ config(builder);
+ _readings.Add(builder.Build());
+ return this;
+ }
+
+ public DeviceMessageInfo Build()
+ {
+ _device.Reading = new DeviceMessageInfoReadings
+ {
+ ReadingArray = _readings.ToArray()
+ };
+ return _device;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c2c49e6e8baff6cb21f70a66220f805cd1aa2aec
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs
@@ -0,0 +1,36 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Factories;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Builders
+{
+ public class DeviceInfoReadingBuilder
+ {
+ private readonly DeviceMessageInfoReading _reading;
+ private readonly List _states = new();
+ private readonly byte _deviceType;
+
+ public DeviceInfoReadingBuilder(DeviceMessageInfoReading reading, byte deviceType)
+ {
+ _reading = reading;
+ _deviceType = deviceType;
+ }
+
+ public DeviceInfoReadingBuilder AddState(byte sid, object value, StateValueTypeEnum? valueType = null)
+ {
+ var factory = StateFactoryRegistry.GetFactory(_deviceType);
+ var state = factory.CreateState(sid, value, valueType);
+ _states.Add(state);
+ return this;
+ }
+
+ public DeviceMessageInfoReading Build()
+ {
+ _reading.State = new DeviceMessageInfoReadingStates
+ {
+ StateArray = [.. _states]
+ };
+ return _reading;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6e23ca24065fbf92c0368749ea5b402ed1bbfee4
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs
@@ -0,0 +1,202 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.Security;
+using System.Buffers;
+using System.Collections.Frozen;
+
+namespace DeviceCommons.DeviceMessages.Builders
+{
+ public class DeviceMessageBuilder : IDeviceMessageBuilder
+ {
+ private readonly DeviceMessage _message = new();
+ private DeviceInfoBuilder? _currentDeviceBuilder;
+
+ public static IDeviceMessageBuilder Create() => new DeviceMessageBuilder();
+
+ public IDeviceMessageBuilder WithHeader(
+ byte version = 0x01,
+ CRCTypeEnum crcType = CRCTypeEnum.CRC16,
+ TimeStampFormatEnum timeFormat = TimeStampFormatEnum.MS,
+ HeaderValueTypeEnum valueType = HeaderValueTypeEnum.Standard)
+ {
+ _message.Header = new DeviceMessageHeader
+ {
+ Version = version,
+ CRCType = crcType,
+ TimeStampFormat = timeFormat,
+ ValueType = valueType
+ };
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithVersion(byte version)
+ {
+ _message.Header ??= new DeviceMessageHeader();
+ _message.Header.Version = version;
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithMainDevice(string did, byte deviceType)
+ {
+ return WithMainDevice(did, deviceType, _ => { });
+ }
+
+ public IDeviceMessageBuilder WithMainDevice(string did, byte deviceType, Action config)
+ {
+ var device = new DeviceMessageInfo
+ {
+ DID = did,
+ DeviceType = deviceType
+ };
+
+ _currentDeviceBuilder = new DeviceInfoBuilder(device);
+ config(_currentDeviceBuilder);
+ _message.MainDevice = _currentDeviceBuilder.Build();
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithChildDevice(string did, byte deviceType, Action config)
+ {
+ if (_message.MainDevice == null)
+ throw new InvalidOperationException("Main device must be set before adding child devices");
+
+ var device = new DeviceMessageInfo
+ {
+ DID = did,
+ DeviceType = deviceType
+ };
+
+ var builder = new DeviceInfoBuilder(device);
+ config(builder);
+
+ _message.ChildDevice ??= new DeviceMessageChild();
+ _message.ChildDevice.AddOrUpdateChild(builder.Build());
+
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithEncryption(
+ Func? encryptFunc = null,
+ Func? decryptFunc = null)
+ {
+ DeviceMessageSerializerProvider.MessagePar.DecryptFunc = decryptFunc;
+ DeviceMessageSerializerProvider.MessageSer.EncryptFunc = encryptFunc;
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithAesEncryption(string password)
+ {
+ var aes = new AesEncryptor();
+ return WithEncryption(
+ plainText => aes.Encrypt(plainText, password),
+ cipherText => aes.Decrypt(cipherText, password)
+ );
+ }
+
+ public IDeviceMessageBuilder AddReading(short timeOffset, Action config)
+ {
+ if (_currentDeviceBuilder == null)
+ throw new InvalidOperationException("No active device configuration");
+
+ _currentDeviceBuilder.AddReading(timeOffset, config);
+ return this;
+ }
+
+ public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, T value)
+ where T : notnull
+ {
+ var type = _valueTypeMap.TryGetValue(typeof(T), out var t)
+ ? t
+ : (value is byte[]? StateValueTypeEnum.Binary : StateValueTypeEnum.String);
+
+ AddReading(timeOffset, (sid, value, type));
+ return this;
+ }
+
+ public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, string value) =>
+ AddReading(timeOffset, (sid, value, StateValueTypeEnum.String));
+
+ public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, byte[] value) =>
+ AddReading(timeOffset, (sid, value, StateValueTypeEnum.Binary));
+
+ public IDeviceMessageBuilder AddReading(short timeOffset, params (byte sid, object? value, StateValueTypeEnum? type)[] states)
+ {
+ if (_currentDeviceBuilder == null)
+ throw new InvalidOperationException("No active device configuration");
+
+ _currentDeviceBuilder.AddReading(timeOffset, reading =>
+ {
+ foreach (var (sid, value, type) in states)
+ {
+ reading.AddState(sid, value, type ?? InferType(value)); // 如果调用者没给 type,就推断
+ }
+ });
+
+ return this;
+ }
+
+ public DeviceMessage Build()
+ {
+ if (_message.MainDevice == null)
+ throw new InvalidOperationException("Main device is required");
+
+ return _message;
+ }
+
+ public byte[] BuildBytes()
+ {
+ var arrayBufferWriter = new ArrayBufferWriter();
+ DeviceMessageSerializerProvider.MessageSer.Serializer(arrayBufferWriter, Build());
+ return arrayBufferWriter.WrittenSpan.ToArray();
+ }
+
+ public string BuildHex(bool compress = false, bool encrypt = false)
+ {
+ var arrayBufferWriter = new ArrayBufferWriter();
+ return DeviceMessageSerializerProvider.MessageSer.Serializer(arrayBufferWriter, Build(), encrypt, compress);
+ }
+
+ public Task BuildBytesAsync() => Task.Run(() => BuildBytes());
+
+ public Task BuildHexAsync(bool compress = false, bool encrypt = false) =>
+ Task.Run(() => BuildHex(compress, encrypt));
+
+ public IDeviceMessageBuilder WithEncryptFunc(Func encryptFunc)
+ {
+ DeviceMessageSerializerProvider.MessageSer.EncryptFunc = encryptFunc;
+ return this;
+ }
+
+ public IDeviceMessageBuilder WithDecryptFunc(Func decryptFunc)
+ {
+ DeviceMessageSerializerProvider.MessagePar.DecryptFunc = decryptFunc;
+ return this;
+ }
+
+ private static StateValueTypeEnum InferType(object value) => value switch
+ {
+ float => StateValueTypeEnum.Float32,
+ double => StateValueTypeEnum.Double,
+ int => StateValueTypeEnum.Int32,
+ short => StateValueTypeEnum.Int16,
+ ushort => StateValueTypeEnum.UInt16,
+ bool => StateValueTypeEnum.Bool,
+ ulong => StateValueTypeEnum.Timestamp,
+ byte[] => StateValueTypeEnum.Binary,
+ _ => StateValueTypeEnum.String
+ };
+
+ private static readonly FrozenDictionary _valueTypeMap =
+ new Dictionary
+ {
+ [typeof(float)] = StateValueTypeEnum.Float32,
+ [typeof(int)] = StateValueTypeEnum.Int32,
+ [typeof(short)] = StateValueTypeEnum.Int16,
+ [typeof(ushort)] = StateValueTypeEnum.UInt16,
+ [typeof(bool)] = StateValueTypeEnum.Bool,
+ [typeof(double)] = StateValueTypeEnum.Double,
+ [typeof(ulong)] = StateValueTypeEnum.Timestamp,
+ }.ToFrozenDictionary();
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs b/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0a8fee193a2b1e86797b09c5582d77064d07a536
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs
@@ -0,0 +1,51 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Builders
+{
+ public interface IDeviceMessageBuilder
+ {
+ IDeviceMessageBuilder WithHeader(
+ byte version = 0x01,
+ CRCTypeEnum crcType = CRCTypeEnum.CRC16,
+ TimeStampFormatEnum timeFormat = TimeStampFormatEnum.MS,
+ HeaderValueTypeEnum valueType = HeaderValueTypeEnum.Standard);
+
+ IDeviceMessageBuilder WithMainDevice(string did, byte deviceType);
+
+ IDeviceMessageBuilder WithMainDevice(string did, byte deviceType, Action config);
+
+ IDeviceMessageBuilder WithChildDevice(string did, byte deviceType, Action config);
+
+ IDeviceMessageBuilder WithEncryptFunc(Func encryptFunc);
+
+ IDeviceMessageBuilder WithDecryptFunc(Func decryptFunc);
+
+ IDeviceMessageBuilder WithAesEncryption(string password);
+
+ IDeviceMessageBuilder WithVersion(byte version);
+
+
+ IDeviceMessageBuilder AddReading(short timeOffset, byte sid, string value);
+
+ IDeviceMessageBuilder AddReading(short timeOffset, byte sid, byte[] value);
+
+ IDeviceMessageBuilder AddReading(short timeOffset, byte sid, T value);
+
+ IDeviceMessageBuilder AddReading(short timeOffset, Action config);
+
+ IDeviceMessageBuilder AddReading(short timeOffset, params (byte sid, object value, StateValueTypeEnum? type)[] states);
+
+
+ DeviceMessage Build();
+
+ byte[] BuildBytes();
+
+ string BuildHex(bool compress = false, bool encrypt = false);
+
+ // 添加异步方法
+ Task BuildBytesAsync();
+
+ Task BuildHexAsync(bool compress = false, bool encrypt = false);
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..39817ee405d10ec1a792da779e47b4a8e418fa84
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs
@@ -0,0 +1,10 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum CRCTypeEnum : byte
+ {
+ None = 0,
+ CRC8 = 1,
+ CRC16 = 2,
+ CRC32 = 3
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..11cff6476e857cb47dcac5ad2fcdb267ac06de68
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum HeaderValueTypeEnum : byte
+ {
+ Standard = 0,
+ Extend = 1,
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs b/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..398b793b997cb9c83c9e7a9834b1369c29acc161
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs
@@ -0,0 +1,25 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ [Flags]
+ public enum MarkEnum : byte
+ {
+ TimeStampMS = 0 << 0,
+ TimeStampS = 1 << 0,
+
+ ValueStandard = 0 << 1,
+ ValueExtend = 1 << 1,
+
+ Reserve2Close = 0 << 2,
+ Reserve2Open = 1 << 2,
+
+ Reserve3Close = 0 << 3,
+ Reserve3Open = 1 << 3,
+
+ CRCMask = 0b1111_0000,
+ CRCNone = 0 << 4,
+ CRC8 = 1 << 4,
+ CRC16 = 2 << 4,
+ CRC32 = 3 << 4,
+ CRC64 = 4 << 4,
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs b/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f76f0672eb6d73c6b6487f95ebdd17e4fea5af98
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum Reserve1Enum : byte
+ {
+ Close = 0,
+ Open = 1
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs b/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..410e30d39a8b4b73068abc5fc4a3d0edfe274ac5
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum Reserve2Enum : byte
+ {
+ Close = 0,
+ Open = 1
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8ea17d279e460ee6dd35b01188186a102667d83e
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs
@@ -0,0 +1,15 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum StateValueTypeEnum : byte
+ {
+ Float32 = 1,
+ Int32 = 2,
+ String = 3,
+ Bool = 4,
+ UInt16 = 6,
+ Int16 = 7,
+ Timestamp = 8,
+ Binary = 9,
+ Double = 10,
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs b/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9ea55847988a1bd2fe804743bb12fbbbdc7a9ca5
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DeviceMessages.Enums
+{
+ public enum TimeStampFormatEnum : byte
+ {
+ MS = 0,
+ S = 1,
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs b/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b9a66cdbc55d7b7126ec70c5b92ca190e19e0067
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs
@@ -0,0 +1,18 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Factories
+{
+ public class DefaultStateFactory : IStateFactory
+ {
+ public IDeviceMessageInfoReadingState CreateState(byte sid, object value, StateValueTypeEnum? valueType = null)
+ {
+ return new DeviceMessageInfoReadingState
+ {
+ SID = sid,
+ ValueType = valueType ?? StateValueTypeEnum.String,
+ ValueText = value
+ };
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs b/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..54bcd0e8c9578a5ce8a37bf031f1cf0b85c66ad0
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs
@@ -0,0 +1,10 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Factories
+{
+ public interface IStateFactory
+ {
+ IDeviceMessageInfoReadingState CreateState(byte sid, object value, StateValueTypeEnum? valueType = null);
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs b/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fd5a3de0c7c29ef6577fc166694035dd2626c181
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs
@@ -0,0 +1,31 @@
+using System.Collections.Concurrent;
+
+namespace DeviceCommons.DeviceMessages.Factories
+{
+ public static class StateFactoryRegistry
+ {
+ private static readonly ConcurrentDictionary> _factoryCreators = new();
+ private static readonly ConcurrentDictionary _factoryCache = new();
+ private static readonly IStateFactory _defaultFactory = new DefaultStateFactory();
+
+ public static void RegisterFactory(byte deviceType, Func factoryCreator)
+ {
+ _factoryCreators[deviceType] = factoryCreator;
+ }
+
+ public static IStateFactory GetFactory(byte deviceType)
+ {
+ if (_factoryCache.TryGetValue(deviceType, out var cachedFactory))
+ return cachedFactory;
+
+ if (_factoryCreators.TryGetValue(deviceType, out var creator))
+ {
+ var factory = creator();
+ _factoryCache[deviceType] = factory;
+ return factory;
+ }
+
+ return _defaultFactory;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs
new file mode 100644
index 0000000000000000000000000000000000000000..31f337a78934c211a606de76e8f9edf48e1abf0f
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs
@@ -0,0 +1,47 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessage : IDeviceMessage
+ {
+ public IDeviceMessageHeader? Header { get; set; } = new DeviceMessageHeader();
+
+ public IDeviceMessageInfo? MainDevice { get; set; } = new DeviceMessageInfo();
+
+ public IDeviceMessageChild? ChildDevice { get; set; } = new DeviceMessageChild();
+
+ public void Dispose()
+ {
+ DisposeMainDevice();
+ DisposeChildDevices();
+ GC.SuppressFinalize(this);
+ }
+
+ private void DisposeMainDevice()
+ {
+ if (MainDevice?.Reading?.ReadingArray == null) return;
+ foreach (var reading in MainDevice.Reading.ReadingArray) DisposeReadingReadings(MainDevice);
+ }
+
+ private void DisposeChildDevices()
+ {
+ if (ChildDevice?.ChildArray == null) return;
+ foreach (var child in ChildDevice.ChildArray) DisposeReadingReadings(child);
+ }
+ private static void DisposeReadingReadings(IDeviceMessageInfo? info)
+ {
+ if (info?.Reading?.ReadingArray == null) return;
+ foreach (var reading in info.Reading.ReadingArray) DisposeReadingStates(reading);
+ info?.Reading?.Dispose();
+ }
+
+ private static void DisposeReadingStates(IDeviceMessageInfoReading? reading)
+ {
+ if (reading?.State?.StateArray == null) return;
+ foreach (var state in reading.State.StateArray) state?.Dispose();
+ }
+
+ ~DeviceMessage()
+ {
+ Dispose();
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7761cb3a973d0adb743dd973d6cd50c44de6d2eb
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs
@@ -0,0 +1,44 @@
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageChild : IDeviceMessageChild
+ {
+ private readonly Dictionary _children = [];
+
+ public DeviceMessageChild()
+ {
+ }
+
+ public byte Count => (byte)_children.Count;
+
+ public IDeviceMessageInfo[]? ChildArray
+ {
+ get => [.. _children.Values.OrderBy(i => i.DID)];
+ set
+ {
+ _children.Clear();
+ if (value != null)
+ {
+ foreach (var dev in value)
+ {
+ if (dev != null)
+ {
+ if (!string.IsNullOrEmpty(dev.DID))
+ _children[dev?.DID ?? ""] = dev ?? new DeviceMessageInfo();
+ }
+ else throw new ArgumentNullException("初始化后再赋值");
+ }
+ }
+ }
+ }
+
+ public void AddOrUpdateChild(IDeviceMessageInfo child)
+ {
+ if (string.IsNullOrEmpty(child.DID))
+ throw new ArgumentException("DID不能为空");
+
+ _children[child?.DID ?? ""] = child ?? new DeviceMessageInfo();
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1b8d7382977f071e02cd4fcca9750da6ff177c78
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs
@@ -0,0 +1,30 @@
+using DeviceCommons.DeviceMessages.Enums;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageHeader : IDeviceMessageHeader
+ {
+ public DeviceMessageHeader()
+ {
+ }
+
+ public byte[] Header { get; set; } = new byte[] { 0xC0, 0xBF };
+
+ public byte Version { get; set; } = 0x01;
+
+ public TimeStampFormatEnum TimeStampFormat { get; set; } = TimeStampFormatEnum.MS;
+
+ public HeaderValueTypeEnum ValueType { get; set; } = HeaderValueTypeEnum.Standard;
+
+ public Reserve1Enum Reserve1 { get; set; } = Reserve1Enum.Close;
+
+ public Reserve2Enum Reserve2 { get; set; } = Reserve2Enum.Close;
+
+ public CRCTypeEnum CRCType { get; set; } = CRCTypeEnum.CRC16;
+
+ public byte Mark
+ {
+ get => (byte)((int)CRCType << 4 | (int)Reserve2 << 3 | (int)Reserve1 << 2 | (int)ValueType << 1 | (int)TimeStampFormat);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dc57a73bb5eb52f4ee21ca7ec3b537ee865d98bb
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs
@@ -0,0 +1,21 @@
+using System.Text;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageInfo : IDeviceMessageInfo
+ {
+ public DeviceMessageInfo()
+ {
+ }
+
+ public byte Length { get => (byte)(DIDBytes?.Length ?? 0); }
+
+ public byte[]? DIDBytes { get => Encoding.UTF8.GetBytes(DID ?? ""); set => DID = Encoding.UTF8.GetString(value ?? []); }
+
+ public string? DID { get; set; }
+
+ public byte DeviceType { get; set; } = 0x00;
+
+ public IDeviceMessageInfoReadings? Reading { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2aab26956fad5671bf14d502d3d22ce014a95ea6
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs
@@ -0,0 +1,25 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageInfoReading : IDeviceMessageInfoReading
+ {
+ public DeviceMessageInfoReading()
+ {
+ }
+
+ public byte[] Offset
+ {
+ get
+ {
+ byte[] bytes = new byte[2];
+ bytes[0] = (byte)(TimeOffset >> 8);
+ bytes[1] = (byte)TimeOffset;
+ return bytes;
+ }
+ set => TimeOffset = (short)(value[0] << 8 | value[1]);
+ }
+
+ public short TimeOffset { get; set; } = 0;
+
+ public IDeviceMessageInfoReadingStates? State { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7938c0b1516771fe16cd595f33510cb4eb56efdc
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs
@@ -0,0 +1,80 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Enums;
+using System.Text;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageInfoReadingState : IDeviceMessageInfoReadingState
+ {
+ public byte SID { get; set; }
+ public byte Type
+ {
+ get => (byte)ValueType;
+ set
+ {
+ ValueType = (StateValueTypeEnum)value;
+ if (ValueType != StateValueTypeEnum.String &&
+ ValueType != StateValueTypeEnum.Binary)
+ {
+ Value = new byte[DeviceMessageUtilities.GetValueLength(ValueType,null)];
+ }
+ else
+ {
+ Value = [];
+ }
+ }
+ }
+
+ public byte[] Value { get; set; } = Array.Empty();
+
+ public object? ValueText
+ {
+ get
+ {
+ return (StateValueTypeEnum)Type switch
+ {
+ StateValueTypeEnum.Float32 => BitConverter.ToSingle(Value, 0),
+ StateValueTypeEnum.Int32 => BitConverter.ToInt32(Value, 0),
+ StateValueTypeEnum.Bool => BitConverter.ToBoolean(Value, 0),
+ StateValueTypeEnum.UInt16 => BitConverter.ToUInt16(Value, 0),
+ StateValueTypeEnum.Int16 => BitConverter.ToInt16(Value, 0),
+ StateValueTypeEnum.Timestamp => BitConverter.ToUInt64(Value, 0),
+ StateValueTypeEnum.Double => BitConverter.ToDouble(Value, 0),
+ StateValueTypeEnum.Binary => Encoding.UTF8.GetString(Value),
+ StateValueTypeEnum.String => Encoding.UTF8.GetString(Value),
+ _ => null,
+ };
+ }
+ set
+ {
+ Value = (StateValueTypeEnum)Type switch
+ {
+ StateValueTypeEnum.Float32 => BitConverter.GetBytes(float.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Int32 => BitConverter.GetBytes(int.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Bool => BitConverter.GetBytes(bool.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.UInt16 => BitConverter.GetBytes(ushort.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Int16 => BitConverter.GetBytes(short.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Timestamp => BitConverter.GetBytes(ulong.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Double => BitConverter.GetBytes(double.Parse(value?.ToString() ?? "0")),
+ StateValueTypeEnum.Binary => Encoding.UTF8.GetBytes(value?.ToString() ?? ""),
+ StateValueTypeEnum.String => Encoding.UTF8.GetBytes(value?.ToString() ?? ""),
+ _ => [],
+ };
+ }
+ }
+ public StateValueTypeEnum ValueType { get; set; } = StateValueTypeEnum.String;
+
+ public string? Metadata { get; set; }
+
+ public void Dispose()
+ {
+ if (Value != null)
+ {
+ Array.Clear(Value, 0, Value.Length);
+ DeviceMessageArrayPool.ByteArrayPool.Return(Value);
+ Value = [];
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3843f744284faf124a06ad067296fbbd95e3e4fa
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs
@@ -0,0 +1,25 @@
+using DeviceCommons.DataHandling;
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageInfoReadingStates : IDeviceMessageInfoReadingStates
+ {
+
+ public byte Count => (byte)(StateArray?.Length ?? 0);
+
+ public IDeviceMessageInfoReadingState[]? StateArray { get; set; }
+ public void Dispose()
+ {
+ if (StateArray != null)
+ {
+ foreach (var state in StateArray)
+ {
+ state?.Dispose();
+ }
+ Array.Clear(StateArray, 0, StateArray.Length);
+ DeviceMessageArrayPool.InfoReadingState_ArrayPool.Return(StateArray);
+ StateArray = [];
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs
new file mode 100644
index 0000000000000000000000000000000000000000..192c10c07aaf7e56dfd070aef5897679dac3005b
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs
@@ -0,0 +1,20 @@
+using DeviceCommons.DataHandling;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public class DeviceMessageInfoReadings : IDeviceMessageInfoReadings
+ {
+ public byte Count { get => (byte)(ReadingArray?.Length ?? 0); }
+ public IDeviceMessageInfoReading[]? ReadingArray { get; set; }
+ public void Dispose()
+ {
+ if (ReadingArray != null)
+ {
+ Array.Clear(ReadingArray, 0, ReadingArray.Length);
+ DeviceMessageArrayPool.InfoReading_ArrayPool.Return(ReadingArray);
+ ReadingArray = [];
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IBase.cs b/DeviceCommons/DeviceMessages/Models/V1/IBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4594aab7aae42dc91f0cc0e795d6e19e5c951fd2
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IBase.cs
@@ -0,0 +1,8 @@
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IBase
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7a419259c38b89fe3aba91770d52b1db08b01181
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs
@@ -0,0 +1,12 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessage : IBase, IDisposable
+ {
+
+ IDeviceMessageHeader? Header { get; set; }
+
+ IDeviceMessageInfo? MainDevice { get; set; }
+
+ IDeviceMessageChild? ChildDevice { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cf8ca9c44a384c2da83f0445e07f25cc6f0a695c
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs
@@ -0,0 +1,9 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageChild : IBase
+ {
+ byte Count { get; }
+ IDeviceMessageInfo[]? ChildArray { get; set; }
+ void AddOrUpdateChild(IDeviceMessageInfo child);
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3ef83e32d03415e7ab450bf9cab19b7d615e84a3
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs
@@ -0,0 +1,21 @@
+using DeviceCommons.DeviceMessages.Enums;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageHeader : IBase
+ {
+ byte[] Header { get; set; }
+
+ byte Version { get; set; }
+ byte Mark { get; }
+ TimeStampFormatEnum TimeStampFormat { get; set; }
+
+ HeaderValueTypeEnum ValueType { get; set; }
+
+ Reserve1Enum Reserve1 { get; set; }
+
+ Reserve2Enum Reserve2 { get; set; }
+
+ CRCTypeEnum CRCType { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d3b6946877e3869648eaeba323c80b16943052ed
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs
@@ -0,0 +1,16 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageInfo : IBase
+ {
+
+ byte Length { get; }
+
+ byte[]? DIDBytes { get; set; }
+
+ string? DID { get; set; }
+
+ byte DeviceType { get; set; }
+
+ IDeviceMessageInfoReadings? Reading { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0590c010f00b8c5932d6dd34374013935fa0bf41
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs
@@ -0,0 +1,11 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageInfoReading : IBase
+ {
+ byte[] Offset { get; set; }
+
+ short TimeOffset { get; set; }
+
+ IDeviceMessageInfoReadingStates? State { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs
new file mode 100644
index 0000000000000000000000000000000000000000..68fae0382582d2659437833f021261fe0149228f
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs
@@ -0,0 +1,14 @@
+using DeviceCommons.DeviceMessages.Enums;
+
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageInfoReadingState : IBase, IDisposable
+ {
+ byte SID { get; set; }
+ byte[] Value { get; set; }
+ object? ValueText { get; set; }
+ byte Type { get; set; }
+ StateValueTypeEnum ValueType { get; set; }
+ string? Metadata { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs
new file mode 100644
index 0000000000000000000000000000000000000000..77a3a733b1568ab48768ee70cfb92fc4e45dbf02
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs
@@ -0,0 +1,8 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageInfoReadingStates : IBase, IDisposable
+ {
+ byte Count { get; }
+ IDeviceMessageInfoReadingState[]? StateArray { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs
new file mode 100644
index 0000000000000000000000000000000000000000..818ac5d245a64810b039552bf71dea23a17153b2
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs
@@ -0,0 +1,9 @@
+namespace DeviceCommons.DeviceMessages.Models.V1
+{
+ public interface IDeviceMessageInfoReadings : IBase, IDisposable
+ {
+ public byte Count { get; }
+
+ public IDeviceMessageInfoReading[]? ReadingArray { get; set; }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a9887c81d648d737d446f62310367f9b730e2e4a
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs
@@ -0,0 +1,43 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.Exceptions;
+
+namespace DeviceCommons.DeviceMessages.Serialization
+{
+ public class DeviceMessageParser : AbstractMessageParser, IDeviceMessageParser
+ {
+ public DeviceMessageParser() : base(new DeviceMessage())
+ {
+ }
+
+ public override IDeviceMessage Parser(ReadOnlySpan bytes)
+ {
+ if (bytes == null) throw new ArgumentNullException(nameof(bytes));
+ if (bytes.Length < 4) throw new ArgumentException($"最小长度4字节,实际收到{bytes.Length}字节");
+ try
+ {
+
+ byte[] headerBytes = new byte[4];
+ headerBytes = bytes[..4].ToArray();
+
+ Model.Header = DeviceMessageSerializerProvider.HeaderPar.Parser(headerBytes);
+ if (Model.Header == null) throw new InvalidOperationException("未初始化消息头");
+ switch (Model.Header.Version)
+ {
+ case 0x01:
+ _ = new V1.ProcessVersionData(Model, bytes);
+ break;
+ default:
+ _ = new V1.ProcessVersionData(Model, bytes);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidMessageException("消息解析失败", ex);
+ }
+ return Model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a237015bcd34921b96c35da7f0bfe7c60d11d417
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs
@@ -0,0 +1,35 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.DeviceMessages.Serialization.V1.Serializers;
+using DeviceCommons.Security;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization
+{
+ public class DeviceMessageSerializer : AbstractMessageSerializer, IDeviceMessageSerializer
+ {
+ public DeviceMessageSerializer() : base(new DeviceMessage())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessage model)
+ {
+ _ = new DeviceMessageSerializer();
+
+ new DeviceMessageChildSerializer().Serializer(writer, model.ChildDevice ?? new DeviceMessageChild());
+ new DeviceMessageInfoSerializer().Serializer(writer, model.MainDevice ?? new DeviceMessageInfo());
+ new DeviceMessageHeaderSerializer().Serializer(writer, model.Header ?? new DeviceMessageHeader());
+
+ byte[] bytes = writer.WrittenSpan.ToArray();
+
+ if (model.Header?.CRCType != CRCTypeEnum.None)
+ {
+ writer.Write(CrcCalculator.CalculateCrcBytes(model.Header.CRCType, writer.WrittenSpan.ToArray()));
+ }
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a99bae9655c6803e183ff0f5b034fc6d7b2e3e33
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization
+{
+ public interface IDeviceMessageParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cc9e12823aa9926c8a266cb555ad1751f0731f10
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization
+{
+ public interface IDeviceMessageSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bc88c1a3ce3c7b5c8801e09c42c15f80b59502b8
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs
@@ -0,0 +1,93 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Text;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageChildParser : AbstractMessageParser, IDeviceMessageChildParser
+ {
+ public DeviceMessageChildParser() : base(new DeviceMessageChild())
+ {
+ }
+
+ public override IDeviceMessageChild Parser(ReadOnlySpan bytes)
+ {
+ IDeviceMessageChild model = new DeviceMessageChild();
+ byte childCount = bytes[0];
+ List childList = new List();
+ int currentIndex = 1;
+
+ for (int i = 0; i < childCount; i++)
+ {
+ byte didLength = bytes[currentIndex];
+ currentIndex++;
+
+ string deviceId = "";
+ if (didLength > 0)
+ {
+ deviceId = Encoding.UTF8.GetString(bytes.ToArray(), currentIndex, didLength);
+ currentIndex += didLength;
+ }
+
+ byte deviceType = bytes[currentIndex];
+ currentIndex++;
+
+ int readingsDataLength = CalculateReadingsDataLength(bytes, currentIndex);
+
+ if (readingsDataLength > 0)
+ {
+ var childDevice = new DeviceMessageInfo
+ {
+ DID = deviceId,
+ DeviceType = deviceType,
+ Reading = DeviceMessageSerializerProvider.InfoReadingsPar.Parser(bytes.Slice(currentIndex, readingsDataLength))
+ };
+
+ childList.Add(childDevice);
+
+ currentIndex += readingsDataLength;
+ }
+ else
+ {
+ childList.Add(new DeviceMessageInfo
+ {
+ DID = deviceId,
+ DeviceType = deviceType,
+ Reading = new DeviceMessageInfoReadings()
+ });
+ }
+ }
+
+ model.ChildArray = childList.ToArray();
+ return model;
+ }
+
+ public static int CalculateReadingsDataLength(ReadOnlySpan bytes, int index)
+ {
+ int startIndex = index;
+ if (startIndex >= bytes.Length) return 0;
+
+ byte readingCount = bytes[index];
+ index++;
+
+ for (int i = 0; i < readingCount; i++)
+ {
+ index += 2;
+
+ byte stateCount = bytes[index];
+ index++;
+
+ for (int j = 0; j < stateCount; j++)
+ {
+ _ = bytes[index + 1];
+ index += 2;
+
+ int valueLength = DeviceMessageUtilities.GetValueLength(bytes, index - 1);
+ index += valueLength;
+ }
+ }
+ return index - startIndex;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..db8b64bf5d8f48a3dc25dff56e210aeaccce5efe
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs
@@ -0,0 +1,30 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageHeaderParser : AbstractMessageParser, IDeviceMessageHeaderParser
+ {
+ public DeviceMessageHeaderParser() : base(new DeviceMessageHeader())
+ {
+
+ }
+
+ public override IDeviceMessageHeader Parser(ReadOnlySpan bytes)
+ {
+ IDeviceMessageHeader model = new DeviceMessageHeader();
+
+ model.Header = bytes[2..].ToArray();
+
+ model.Version = bytes[2];
+ model.TimeStampFormat = (TimeStampFormatEnum)(bytes[3] >> 0 & 0x01);
+ model.ValueType = (HeaderValueTypeEnum)(bytes[3] >> 1 & 0x01);
+ model.Reserve1 = (Reserve1Enum)(bytes[3] >> 2 & 0x01);
+ model.Reserve2 = (Reserve2Enum)(bytes[3] >> 3 & 0x01);
+ model.CRCType = (CRCTypeEnum)(bytes[3] >> 4 & 0x07);
+
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e4f8228aaeeec649a9dd884c304244cf003b978c
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs
@@ -0,0 +1,42 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageInfoParser : AbstractMessageParser, IDeviceMessageInfoParser
+ {
+ public DeviceMessageInfoParser() : base(new DeviceMessageInfo())
+ {
+
+ }
+
+ public override IDeviceMessageInfo Parser(ReadOnlySpan bytes)
+ {
+ IDeviceMessageInfo model = new DeviceMessageInfo();
+
+ byte didLength = bytes[0];
+ int currentIndex = 1;
+
+
+ if (didLength > 0)
+ {
+ model.DIDBytes = bytes.Slice(currentIndex, didLength).ToArray();
+ currentIndex += didLength;
+ }
+ else model.DIDBytes = [];
+
+ model.DeviceType = bytes[currentIndex];
+ currentIndex++;
+
+
+ int readingsDataLength = bytes.Length - currentIndex;
+ if (readingsDataLength > 0)
+ {
+ model.Reading ??= DeviceMessageSerializerProvider.InfoReadingsPar.Parser(bytes.Slice(currentIndex, readingsDataLength));
+ }
+ else model.Reading = new DeviceMessageInfoReadings();
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..049dd77b084f9f21e9046d55d06f8286003a0e8b
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs
@@ -0,0 +1,27 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageInfoReadingParser : AbstractMessageParser, IDeviceMessageInfoReadingParser
+ {
+ public DeviceMessageInfoReadingParser() : base(new DeviceMessageInfoReading())
+ {
+ }
+
+ public override IDeviceMessageInfoReading Parser(ReadOnlySpan bytes)
+ {
+ if (bytes.Length < 2)
+ throw new ArgumentException("字节长度不足,至少需要2字节", nameof(bytes));
+
+ IDeviceMessageInfoReading model = new DeviceMessageInfoReading();
+
+ model.Offset = bytes[..2].ToArray();
+
+ model.State = DeviceMessageSerializerProvider.InfoReadingStatesPar.Parser(bytes[2..]);
+
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e9654c7fd94cffd11597ad1f43534b5e5f823738
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs
@@ -0,0 +1,48 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageInfoReadingStateParser : AbstractMessageParser, IDeviceMessageInfoReadingStateParser
+ {
+ public DeviceMessageInfoReadingStateParser() : base(new DeviceMessageInfoReadingState())
+ {
+ }
+
+ public override IDeviceMessageInfoReadingState Parser(ReadOnlySpan bytes)
+ {
+ if (bytes.Length < 2)
+ throw new ArgumentException("Insufficient data for state parsing");
+
+ byte sid = bytes[0];
+ StateValueTypeEnum valueType = (StateValueTypeEnum)bytes[1];
+ int dataStartIndex = 2;
+ int valueLength;
+
+ if (valueType == StateValueTypeEnum.String || valueType == StateValueTypeEnum.Binary)
+ {
+ int lengthFieldSize = valueType == StateValueTypeEnum.String ? 1 : 2;
+ if (bytes.Length < dataStartIndex + lengthFieldSize)
+ throw new ArgumentException("Insufficient data for length field");
+
+ valueLength = DeviceMessageUtilities.GetValueLength(valueType, bytes[dataStartIndex..]);
+ dataStartIndex += lengthFieldSize;
+ }
+ else valueLength = DeviceMessageUtilities.GetValueLength(valueType, null);
+
+ if (bytes.Length < dataStartIndex + valueLength)
+ throw new ArgumentException("Insufficient data for value");
+
+ var model = new DeviceMessageInfoReadingState
+ {
+ SID = sid,
+ Type = (byte)valueType,
+ Value = bytes.Slice(dataStartIndex, valueLength).ToArray()
+ };
+
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..261c81ad6f4cc34fcf39f2f8babd38c0e685f95d
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs
@@ -0,0 +1,49 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageInfoReadingStatesParser : AbstractMessageParser, IDeviceMessageInfoReadingStatesParser
+ {
+ public DeviceMessageInfoReadingStatesParser() : base(new DeviceMessageInfoReadingStates())
+ {
+ }
+
+ public override IDeviceMessageInfoReadingStates Parser(ReadOnlySpan bytes)
+ {
+ if (bytes == null || bytes.Length == 0)
+ throw new ArgumentNullException(nameof(bytes));
+
+ IDeviceMessageInfoReadingStates model = new DeviceMessageInfoReadingStates();
+
+ byte stateCount = bytes[0];
+
+ int currentIndex = 1;
+
+ model.StateArray = DeviceMessageArrayPool.InfoReadingState_ArrayPool.Rent(stateCount);
+
+ for (var i = 0; i < stateCount; i++)
+ {
+ byte sid = bytes[currentIndex];
+
+ byte type = bytes[currentIndex + 1];
+
+ int valueLength = 2 + DeviceMessageUtilities.GetValueLength(bytes, currentIndex + 1);
+
+ byte[] stateData = bytes.Slice(currentIndex, valueLength).ToArray();
+
+ model.StateArray[i] = DeviceMessageSerializerProvider.InfoReadingStatePar.Parser(stateData);
+
+ currentIndex += valueLength;
+
+ if (currentIndex > bytes.Length)
+ throw new ArgumentException("字节数组不包含完整的状态数据");
+ }
+
+ model.StateArray = model.StateArray.AsSpan()[..stateCount].ToArray();
+
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f961262e9d80622da5aca5d3bfefc92092cab90
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs
@@ -0,0 +1,42 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public class DeviceMessageInfoReadingsParser : AbstractMessageParser, IDeviceMessageInfoReadingsParser
+ {
+ public DeviceMessageInfoReadingsParser() : base(new DeviceMessageInfoReadings())
+ {
+ }
+ public override IDeviceMessageInfoReadings Parser(ReadOnlySpan bytes)
+ {
+ IDeviceMessageInfoReadings model = new DeviceMessageInfoReadings();
+ byte readingCount = bytes[0];
+ model.ReadingArray = DeviceMessageArrayPool.InfoReading_ArrayPool.Rent(readingCount);
+ int currentIndex = 1;
+ for (int i = 0; i < readingCount; i++)
+ {
+
+ byte stateCount = bytes[currentIndex + 2];
+ int stateDataLength = 1;
+ int stateIndex = currentIndex + 3;
+ for (int j = 0; j < stateCount; j++)
+ {
+ byte type = bytes[stateIndex + 1];
+ int valueLength = DeviceMessageUtilities.GetValueLength(bytes, stateIndex + 1);
+ stateDataLength += 2 + valueLength;
+ stateIndex += 2 + valueLength;
+ }
+ int totalReadingLength = 2 + stateDataLength;
+ model.ReadingArray[i] =
+ DeviceMessageSerializerProvider.InfoReadingPar.Parser(
+ bytes.Slice(currentIndex, totalReadingLength
+ ));
+ currentIndex += totalReadingLength;
+ }
+ model.ReadingArray = model.ReadingArray.AsSpan().Slice(0, readingCount).ToArray();
+ return model;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ff536fa3933c94b178433850c3a5d113db42fb6a
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageChildParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1bf0d6e430c3a130a5b0331f78a50538a2301bd4
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs
@@ -0,0 +1,10 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageHeaderParser : IMessageParser
+ {
+
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b0715df80ca9e74a6f22765e289399f67827237f
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageInfoParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..63355ad3ea8e056ab7e4de44ab5ce7372f8c9ec8
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageInfoReadingParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..90b3373c5c774821b4935323bfb471f76c88abc3
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageInfoReadingStateParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..745fe5f69c915509822a9b731ba2aade7a6423df
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageInfoReadingStatesParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f0c1d1a2cfb8ae70e8191850d08f3c8c3526bddf
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers
+{
+ public interface IDeviceMessageInfoReadingsParser : IMessageParser
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs b/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bc46bfbae993324e0835e2e55854c6210c958fe0
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs
@@ -0,0 +1,86 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.Exceptions;
+using DeviceCommons.Security;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1
+{
+ public class ProcessVersionData
+ {
+ public ProcessVersionData(IDeviceMessage model, ReadOnlySpan bytes)
+ {
+ if (model == null) throw new ArgumentNullException("model");
+ if (model.Header == null) throw new ArgumentNullException("model.Header");
+
+ int crcLength = CrcCalculator.GetCrcLength(model.Header?.CRCType ?? CRCTypeEnum.CRC16);
+
+ if (crcLength > 0)
+ {
+
+ byte[] receivedCrc = bytes.Slice(bytes.Length - crcLength).ToArray();
+ byte[] dataForCrc = bytes[..(bytes.Length - crcLength)].ToArray();
+
+ byte[] calculatedCrc = CrcCalculator.CalculateCrcBytes(model.Header.CRCType, dataForCrc);
+
+
+ if (!receivedCrc.SequenceEqual(calculatedCrc))
+ {
+ string received = BitConverter.ToString(receivedCrc);
+ string calculated = BitConverter.ToString(calculatedCrc);
+ throw new InvalidMessageException($"CRC validation failed. Expected 0x{calculated}, but received 0x{received}.");
+ }
+ }
+
+ int index = 4;
+ int mainDeviceLength = CalculateMainDeviceDataLength(bytes, index);
+ if (mainDeviceLength > 0)
+ {
+ byte[] mainDeviceData = bytes.Slice(index, mainDeviceLength).ToArray();
+ model.MainDevice = DeviceMessageSerializerProvider.InfoPar.Parser(mainDeviceData);
+ index += mainDeviceLength;
+ }
+
+ int childDeviceLength = bytes.Length - index - crcLength;
+ if (childDeviceLength < 0) throw new ArgumentException("Invalid data length for child devices");
+
+ if (childDeviceLength > 0)
+ {
+ byte[] childDeviceData = bytes.Slice(index, childDeviceLength).ToArray();
+ model.ChildDevice = DeviceMessageSerializerProvider.ChildPar.Parser(childDeviceData);
+ index += childDeviceLength;
+ }
+ }
+
+ private static int CalculateMainDeviceDataLength(ReadOnlySpan bytes, int startIndex)
+ {
+ int index = startIndex;
+ if (index >= bytes.Length) return 0;
+ byte didLength = bytes[index++];
+ index += didLength;
+ if (index >= bytes.Length) return index - startIndex;
+ index++;
+ if (index >= bytes.Length) return index - startIndex;
+ byte readingCount = bytes[index++];
+ for (int i = 0; i < readingCount; i++)
+ {
+ if (index + 2 > bytes.Length) return bytes.Length - startIndex;
+ index += 2;
+ if (index >= bytes.Length) break;
+ byte stateCount = bytes[index++];
+ for (int j = 0; j < stateCount; j++)
+ {
+ if (index + 2 > bytes.Length)
+ return bytes.Length - startIndex;
+ byte sid = bytes[index++];
+ byte type = bytes[index++];
+ int valueLength = DeviceMessageUtilities.GetValueLength(bytes, index - 1);
+ if (index + valueLength > bytes.Length)
+ return bytes.Length - startIndex;
+ index += valueLength;
+ }
+ }
+ return index - startIndex;
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..78a88489bcc5ad8d9978c08f5d761ff1c7a9af6d
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs
@@ -0,0 +1,33 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageChildSerializer : AbstractMessageSerializer, IDeviceMessageChildSerializer
+ {
+ public DeviceMessageChildSerializer() : base(new DeviceMessageChild())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageChild model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ SerializeWithHeader(
+ writer,
+ headerLength: 1,
+ writeHeaderAction: w => w.Write([model?.Count ?? 0]),
+ serializeContentAction: w =>
+ {
+ foreach (var child in model.ChildArray ?? [])
+ {
+ new DeviceMessageInfoSerializer().Serializer(writer, child);
+ }
+ });
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..43d5c4170e4bd8e572006abd3b50f6a51b3d0adb
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs
@@ -0,0 +1,35 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageHeaderSerializer : AbstractMessageSerializer, IDeviceMessageHeaderSerializer
+ {
+ public DeviceMessageHeaderSerializer() : base(new DeviceMessageHeader())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ private readonly static int HEADERLENGTH = 4;
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageHeader model)
+ {
+ int existingLength = writer.WrittenCount;
+
+ using var pool = DeviceMessageArrayPool.MemoryPool.Rent(existingLength + HEADERLENGTH);
+
+ writer.WrittenMemory.ToArray().CopyTo(pool.Memory);
+ ReadOnlySpan existingData = pool.Memory.Span[..existingLength];
+
+ writer.Clear();
+
+ writer.Write(model.Header);
+ writer.Write([model.Version]);
+ writer.Write([model.Mark]);
+
+ if (existingLength > 0) writer.Write(existingData);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9e4978ed0c28dae6259c84ad2f72961662a70f8c
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs
@@ -0,0 +1,30 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageInfoReadingSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingSerializer
+ {
+ public DeviceMessageInfoReadingSerializer() : base(new DeviceMessageInfoReading())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReading model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+ if (model.Offset == null) throw new ArgumentException("Offset cannot be null", nameof(model));
+ if (model.Offset.Length != 2) throw new ArgumentOutOfRangeException(nameof(model), "Offset must be exactly 2 bytes long");
+ SerializeWithHeader(
+ writer,
+ headerLength: 2,
+ writeHeaderAction: w => w.Write(model?.Offset),
+ serializeContentAction: w =>
+ {
+ new DeviceMessageInfoReadingStatesSerializer().Serializer(writer, model.State ?? new DeviceMessageInfoReadingStates());
+ });
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2a798e7a9c1947be338c74ab94a7935b64ce0fef
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs
@@ -0,0 +1,66 @@
+using DeviceCommons.DataHandling;
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageInfoReadingStateSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingStateSerializer
+ {
+ public DeviceMessageInfoReadingStateSerializer() : base(new DeviceMessageInfoReadingState())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadingState model)
+ {
+ if (model == null) throw new ArgumentNullException(nameof(model));
+ if (model.Value == null) throw new ArgumentException("Value cannot be null", nameof(model));
+
+ StateValueTypeEnum valueType = (StateValueTypeEnum)model.Type;
+ int valueLength = model.Value?.Length ?? 0;
+
+ if (valueType != StateValueTypeEnum.String && valueType != StateValueTypeEnum.Binary)
+ {
+ int expectedLength = DeviceMessageUtilities.GetValueLength(valueType, null);
+ if (valueLength != expectedLength)
+ throw new ArgumentException($"值长度不符合类型要求。期望长度: {expectedLength}, 实际长度: {valueLength}");
+ }
+
+ int totalLength = 2;
+ if (valueType == StateValueTypeEnum.String) totalLength += 1;
+ else if (valueType == StateValueTypeEnum.Binary) totalLength += 2;
+ totalLength += valueLength;
+
+ int existingLength = writer.WrittenCount;
+
+ using var pool = DeviceMessageArrayPool.MemoryPool.Rent(existingLength + 1);
+
+ writer.WrittenMemory.ToArray().CopyTo(pool.Memory);
+ ReadOnlySpan existingData = pool.Memory.Span[..existingLength];
+
+
+ writer.Clear();
+
+ writer.Write([model.SID]);
+ writer.Write([model.Type]);
+
+ switch (valueType)
+ {
+ case StateValueTypeEnum.String:
+ if (valueLength > byte.MaxValue) throw new ArgumentException($"字符串长度超出1字节范围: {valueLength}");
+ writer.Write([(byte)valueLength]);
+ break;
+
+ case StateValueTypeEnum.Binary:
+ writer.Write(BitConverter.GetBytes((ushort)valueLength));
+ break;
+ }
+ writer.Write(model.Value);
+
+ if (existingLength > 0) writer.Write(existingData);
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..765e7dd1acb3f88434ef0c514541103a6451b665
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs
@@ -0,0 +1,33 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageInfoReadingStatesSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingStatesSerializer
+ {
+ public DeviceMessageInfoReadingStatesSerializer() : base(new DeviceMessageInfoReadingStates())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadingStates model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ SerializeWithHeader(
+ writer,
+ headerLength: 1,
+ writeHeaderAction: w => w.Write([model?.Count ?? 0]),
+ serializeContentAction: w =>
+ {
+ foreach (var state in model.StateArray ?? [])
+ {
+ new DeviceMessageInfoReadingStateSerializer().Serializer(w, state);
+ }
+ });
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3661c1e2d873b38700c8681380a8626ba29fa6cb
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs
@@ -0,0 +1,32 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageInfoReadingsSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingsSerializer
+ {
+ public DeviceMessageInfoReadingsSerializer() : base(new DeviceMessageInfoReadings())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadings model)
+ {
+ ArgumentNullException.ThrowIfNull(model);
+
+ SerializeWithHeader(
+ writer,
+ headerLength: 1,
+ writeHeaderAction: w => w.Write([model?.Count ?? 0]),
+ serializeContentAction: w =>
+ {
+ foreach (var reading in model.ReadingArray ?? [])
+ {
+ new DeviceMessageInfoReadingSerializer().Serializer(writer, reading);
+ }
+ });
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..70122ae7088dcc632927ba38c8e0e23c9260b62c
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs
@@ -0,0 +1,38 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+using System.Buffers;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public class DeviceMessageInfoSerializer : AbstractMessageSerializer, IDeviceMessageInfoSerializer
+ {
+ public DeviceMessageInfoSerializer() : base(new DeviceMessageInfo())
+ {
+ }
+
+ public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model);
+
+ public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfo model)
+ {
+ if (string.IsNullOrEmpty(model.DID))
+ throw new ArgumentNullException(nameof(model.DID), "设备ID不能为空");
+
+ if (model.Reading == null)
+ throw new ArgumentNullException(nameof(model.Reading), "读数信息不能为空");
+
+ SerializeWithHeader(
+ writer,
+ headerLength: model.Length + 2,
+ writeHeaderAction: w =>
+ {
+ w.Write([model.Length]);
+ w.Write(model.DIDBytes);
+ w.Write([model.DeviceType]);
+ },
+ serializeContentAction: w =>
+ {
+ new DeviceMessageInfoReadingsSerializer().Serializer(w, model.Reading);
+ });
+ }
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0f503be38b4984435baafb0917ef8b15e9c9c2fc
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageChildSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..57f1a940e093ef6633ca84308734ffb26434cc6b
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageHeaderSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c827406c0799bcc473c48b21f030f47b8a330687
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageInfoReadingSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b976f581dbd7af176f0363057280b98841501a8d
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageInfoReadingStateSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..79738c6c45977e615d0b5b337d4c416116f0c645
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageInfoReadingStatesSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7d962efc73439bfbba4468efc8245dd9379cebb4
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageInfoReadingsSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b12a71cbd108a387493d4249b2ae1d6cf99df15
--- /dev/null
+++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs
@@ -0,0 +1,9 @@
+using DeviceCommons.DeviceMessages.Abstractions;
+using DeviceCommons.DeviceMessages.Models.V1;
+
+namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers
+{
+ public interface IDeviceMessageInfoSerializer : IMessageSerializer
+ {
+ }
+}
diff --git a/DeviceCommons/Exceptions/InvalidMessageException.cs b/DeviceCommons/Exceptions/InvalidMessageException.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c3280e00e2171e246ffb2a6f1f1c2ac975bab4b9
--- /dev/null
+++ b/DeviceCommons/Exceptions/InvalidMessageException.cs
@@ -0,0 +1,12 @@
+namespace DeviceCommons.Exceptions
+{
+ public class InvalidMessageException : Exception
+ {
+ public InvalidMessageException() { }
+
+ public InvalidMessageException(string? message) : base(message) { }
+
+ public InvalidMessageException(string? message, Exception? innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/DeviceCommons/Security/AesEncryptor.cs b/DeviceCommons/Security/AesEncryptor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f81509d844d063f88dfbdaf261a6b7a43b2465a2
--- /dev/null
+++ b/DeviceCommons/Security/AesEncryptor.cs
@@ -0,0 +1,192 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace DeviceCommons.Security
+{
+ public class AesEncryptor
+ {
+ private static readonly int DefaultKeySize = 256;
+ private static readonly int DefaultDerivationIterations = 60000;
+ private static readonly int DefaultSaltSize = 16;
+ private static readonly int DefaultIvSize = 16;
+ private readonly int keySize;
+ private readonly int derivationIterations;
+ private readonly int saltSize;
+ private readonly int ivSize;
+
+ public AesEncryptor()
+ : this(DefaultKeySize, DefaultDerivationIterations, DefaultSaltSize, DefaultIvSize)
+ {
+ }
+
+ public AesEncryptor(int keySize, int derivationIterations, int saltSize, int ivSize)
+ {
+ if (keySize != 128 && keySize != 192 && keySize != 256)
+ throw new ArgumentOutOfRangeException(nameof(keySize), "Key size must be 128, 192, or 256 bits.");
+ if (derivationIterations <= 0)
+ throw new ArgumentOutOfRangeException(nameof(derivationIterations), "Derivation iterations must be greater than zero.");
+ if (saltSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(saltSize), "Salt size must be greater than zero.");
+ if (ivSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(ivSize), "IV size must be greater than zero.");
+
+ this.keySize = keySize;
+ this.derivationIterations = derivationIterations;
+ this.saltSize = saltSize;
+ this.ivSize = ivSize;
+ }
+
+ public string Encrypt(string plainText, string passPhrase)
+ {
+ if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException(nameof(plainText), "Plain text cannot be empty");
+ if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase), "Passphrase cannot be empty");
+
+ var salt = GenerateRandomBytes(saltSize);
+ var iv = GenerateRandomBytes(ivSize);
+
+ var keyBytes = DeriveKeyBytes(passPhrase, salt);
+
+ using (var symmetricKey = CreateAesSymmetricKey(keyBytes, iv))
+ using (var memoryStream = new MemoryStream())
+ {
+
+ memoryStream.Write(salt, 0, salt.Length);
+ memoryStream.Write(iv, 0, iv.Length);
+
+ using (var cryptoStream = new CryptoStream(memoryStream,
+ symmetricKey.CreateEncryptor(),
+ CryptoStreamMode.Write))
+ using (var writer = new StreamWriter(cryptoStream, Encoding.UTF8))
+ {
+ writer.Write(plainText);
+ writer.Flush();
+ }
+
+ return Convert.ToBase64String(memoryStream.ToArray());
+ }
+ }
+
+ public string Decrypt(string cipherText, string passPhrase)
+ {
+ if (string.IsNullOrEmpty(cipherText)) throw new ArgumentNullException(nameof(cipherText), "Cipher text cannot be empty");
+ if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase), "Passphrase cannot be empty");
+
+ try
+ {
+ var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
+
+ if (cipherTextBytesWithSaltAndIv.Length < saltSize + ivSize)
+ throw new ArgumentException($"Invalid cipher text format. Expected at least {saltSize + ivSize} bytes, but got {cipherTextBytesWithSaltAndIv.Length} bytes.");
+
+ var salt = cipherTextBytesWithSaltAndIv.Take(saltSize).ToArray();
+ var iv = cipherTextBytesWithSaltAndIv.Skip(saltSize).Take(ivSize).ToArray();
+ var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(saltSize + ivSize).ToArray();
+
+ var keyBytes = DeriveKeyBytes(passPhrase, salt);
+
+ using (var symmetricKey = CreateAesSymmetricKey(keyBytes, iv))
+ using (var memoryStream = new MemoryStream(cipherTextBytes))
+ using (var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateDecryptor(), CryptoStreamMode.Read))
+ using (var reader = new StreamReader(cryptoStream, Encoding.UTF8))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ catch (CryptographicException ex)
+ {
+ throw new InvalidOperationException("Invalid passphrase or corrupted data.", ex);
+ }
+ catch (FormatException ex)
+ {
+ throw new ArgumentException("Invalid base64 string.", nameof(cipherText), ex);
+ }
+ }
+
+ public async ValueTask EncryptAsync(string plainText, string passPhrase, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException(nameof(plainText));
+ if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase));
+
+ var salt = GenerateRandomBytes(saltSize);
+ var iv = GenerateRandomBytes(ivSize);
+ var keyBytes = DeriveKeyBytes(passPhrase, salt);
+
+ using var symmetricKey = CreateAesSymmetricKey(keyBytes, iv);
+ using (var memoryStream = new MemoryStream())
+ {
+ await memoryStream.WriteAsync(salt, cancellationToken).ConfigureAwait(false);
+ await memoryStream.WriteAsync(iv, cancellationToken).ConfigureAwait(false);
+
+ using (var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateEncryptor(), CryptoStreamMode.Write))
+ using (var writer = new StreamWriter(cryptoStream, Encoding.UTF8))
+ {
+ await writer.WriteAsync(plainText).ConfigureAwait(false);
+ await writer.FlushAsync().ConfigureAwait(false);
+ }
+
+ return Convert.ToBase64String(memoryStream.ToArray());
+ }
+ }
+
+ public async ValueTask DecryptAsync(string cipherText, string passPhrase, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(cipherText)) throw new ArgumentNullException(nameof(cipherText));
+ if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase));
+
+ try
+ {
+ var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
+
+ if (cipherTextBytesWithSaltAndIv.Length < saltSize + ivSize)
+ throw new ArgumentException("Invalid cipher text format");
+
+ var salt = cipherTextBytesWithSaltAndIv.Take(saltSize).ToArray();
+ var iv = cipherTextBytesWithSaltAndIv.Skip(saltSize).Take(ivSize).ToArray();
+ var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(saltSize + ivSize).ToArray();
+
+ var keyBytes = DeriveKeyBytes(passPhrase, salt);
+
+ using var symmetricKey = CreateAesSymmetricKey(keyBytes, iv);
+ using var memoryStream = new MemoryStream(cipherTextBytes);
+ using var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateDecryptor(), CryptoStreamMode.Read);
+ using var reader = new StreamReader(cryptoStream, Encoding.UTF8);
+
+ return await reader.ReadToEndAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("Decryption failed", ex);
+ }
+ }
+
+ private byte[] DeriveKeyBytes(string passPhrase, byte[] salt)
+ {
+ using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(passPhrase)))
+
+ using (var pbkdf2 = new Rfc2898DeriveBytes(passPhrase, salt, derivationIterations))
+ {
+ return pbkdf2.GetBytes(keySize / 8);
+ }
+ }
+
+ private Aes CreateAesSymmetricKey(byte[] keyBytes, byte[] iv)
+ {
+ var aes = Aes.Create();
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.PKCS7;
+ aes.Key = keyBytes;
+ aes.IV = iv;
+ return aes;
+ }
+
+ private byte[] GenerateRandomBytes(int size)
+ {
+ var randomBytes = new byte[size];
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(randomBytes);
+ }
+ return randomBytes;
+ }
+ }
+}
diff --git a/DeviceCommons/Security/CrcCalculator.cs b/DeviceCommons/Security/CrcCalculator.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2baa8a99c8bf36817cd3358225d88ea8106b4889
--- /dev/null
+++ b/DeviceCommons/Security/CrcCalculator.cs
@@ -0,0 +1,84 @@
+using DeviceCommons.DeviceMessages.Enums;
+
+namespace DeviceCommons.Security
+{
+ public class CrcCalculator
+ {
+ public static int GetCrcLength(CRCTypeEnum crcType)
+ {
+ return crcType switch
+ {
+ CRCTypeEnum.CRC8 => 1,
+ CRCTypeEnum.CRC16 => 2,
+ CRCTypeEnum.CRC32 => 4,
+ _ => 0
+ };
+ }
+
+ public static byte[] CalculateCrcBytes(CRCTypeEnum CRCType, byte[] data)
+ {
+ if (CRCType == CRCTypeEnum.None)
+ return [];
+
+ return CRCType switch
+ {
+ CRCTypeEnum.CRC8 => new byte[] { CrcCalculator.ComputeCrc8(data) },
+ CRCTypeEnum.CRC16 => BitConverter.GetBytes(CrcCalculator.ComputeCrc16(data)),
+ CRCTypeEnum.CRC32 => BitConverter.GetBytes(CrcCalculator.ComputeCrc32(data)),
+ _ => []
+ };
+
+ }
+
+ public static byte ComputeCrc8(ReadOnlySpan data)
+ {
+ byte crc = 0;
+ foreach (byte b in data)
+ {
+ crc ^= b;
+ for (int i = 0; i < 8; i++)
+ {
+ if ((crc & 0x80) != 0)
+ crc = (byte)((crc << 1) ^ 0x31);
+ else
+ crc <<= 1;
+ }
+ }
+ return crc;
+ }
+
+ public static ushort ComputeCrc16(ReadOnlySpan data)
+ {
+ ushort crc = 0x0000;
+ foreach (byte b in data)
+ {
+ crc ^= (ushort)(b << 8);
+ for (int i = 0; i < 8; i++)
+ {
+ if ((crc & 0x8000) != 0)
+ crc = (ushort)((crc << 1) ^ 0x8005);
+ else
+ crc <<= 1;
+ }
+ }
+ return crc;
+ }
+
+ public static uint ComputeCrc32(ReadOnlySpan data)
+ {
+ uint crc = 0xFFFFFFFF;
+ foreach (byte b in data)
+ {
+ crc ^= b;
+ for (int i = 0; i < 8; i++)
+ {
+ if ((crc & 1) != 0)
+ crc = (crc >> 1) ^ 0xEDB88320;
+ else
+ crc >>= 1;
+ }
+ }
+ return crc ^ 0xFFFFFFFF;
+ }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d0d277be46872a4a8b1cf3cc2b1fd3fdfdafb0f1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,405 @@
+# DeviceCommons
+
+#### 介绍
+DeviceCommons 是一个专为 IoT 场景设计的高性能设备消息处理库,提供完整的序列化/反序列化解决方案。支持多种数据类型、压缩加密、CRC校验,以及灵活的扩展结构,特别适合资源受限的嵌入式设备和高效的后端数据处理。
+
+#### 消息整体结构
+DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组成:
+
+
+```
++-----------------------------------------------+
+| 设备消息 (DeviceMessage) |
+| +-------------------------------------------+ |
+| | 消息头 (Header) | |
+| +-------------------------------------------+ |
+| | 主设备 (MainDevice) | |
+| | +---------------------------------------+ | |
+| | | 设备信息 (DeviceInfo) | | |
+| | | +-----------------------------------+ | | |
+| | | | 读数集合 | | | |
+| | | | +-------------------------------+ | | | |
+| | | | | 读数数组 | | | | |
+| | | | | +---------------------------+ | | | | |
+| | | | | | 状态数组 | | | | | |
+| | | | | | +-----------------------+ | | | | | |
+| | | | | | | 状态值 | | | | | | |
+| | | | | | +-----------------------+ | | | | | |
+| | | | | +---------------------------+ | | | | |
+| | | | +-------------------------------+ | | | |
+| | | +-----------------------------------+ | | |
+| | +---------------------------------------+ | |
+| +-------------------------------------------+ |
+| | 子设备集合 (ChildDevices) | |
+| | +---------------------------------------+ | |
+| | | 子设备信息数组 | | |
+| | | +-----------------------------------+ | | |
+| | | | 设备信息 (DeviceInfo) | | | |
+| | | | (与主设备结构相同) | | | |
+| | | +-----------------------------------+ | | |
+| | +---------------------------------------+ | |
+| +-------------------------------------------+ |
++-----------------------------------------------+
+```
+
+#### 详细组件说明
+ **1. 消息头 (DeviceMessageHeader)**
+
+消息头包含协议的元数据信息:
+
+| 字段 | 类型 | 长度 | 描述 |
+|-------------------|--------|-----|--------------------------------------|
+| Header | byte[] | 2字节 | 固定头标识 (0xC0, 0xBF) |
+| Version | byte | 1字节 | 协议版本号 |
+| Mark | byte | 1字节 | 标志位组合字段 |
+| └ TimeStampFormat | 位0 | 1位 | 时间戳格式 (0:毫秒, 1:秒) |
+| └ ValueType | 位1 | 1位 | 值类型格式 (0:标准, 1:扩展) |
+| └ Reserve1 | 位2 | 1位 | 保留位1 |
+| └ Reserve2 | 位3 | 1位 | 保留位2 |
+| └ CRCType | 位4-7 | 4位 | CRC校验类型 (0:无,1:CRC8,2:CRC16,3:CRC32) |
+
+ **2. 设备信息 (DeviceMessageInfo)**
+
+设备信息描述单个设备的基本属性和数据:
+
+| 字段 | 类型 | 长度 | 描述 |
+|------------|---------------------------|-----|-----------|
+| Length | byte | 1字节 | 设备ID长度 |
+| DIDBytes | byte[] | 变长 | 设备ID的字节表示 |
+| DID | string | 变长 | 设备ID字符串 |
+| DeviceType | byte | 1字节 | 设备类型标识 |
+| Reading | DeviceMessageInfoReadings | 变长 | 设备读数集合 |
+
+ **3. 读数集合 (DeviceMessageInfoReadings)**
+
+读数集合包含设备的多个读数记录:
+
+| 字段 | 类型 | 长度 | 描述 |
+|--------------|----------------------------|-----|------|
+| Count | byte | 1字节 | 读数数量 |
+| ReadingArray | DeviceMessageInfoReading[] | 变长 | 读数数组 |
+
+ **4. 单个读数 (DeviceMessageInfoReading)**
+
+单个读数表示设备在特定时间点的数据记录:
+
+| 字段 | 类型 | 长度 | 描述 |
+|--------------|----------------------------|-----|------|
+| Offset | byte[] | 2字节 | 时间偏移量 (大端序) |
+| TimeOffset | short | 2字节 | 时间偏移量 (数值形式) |
+| State | DeviceMessageInfoReadingStates | 变长 | 状态数据集合 |
+
+ **5. 状态集合 (DeviceMessageInfoReadingStates)**
+
+状态集合包含单个读数中的多个状态值:
+
+| 字段 | 类型 | 长度 | 描述 |
+|--------------|----------------------------|-----|------|
+| Count | byte | 1字节 | 状态数量 |
+| StateArray | DeviceMessageInfoReadingState[] | 变长 | 状态数组 |
+
+ **6. 单个状态 (DeviceMessageInfoReadingState)**
+
+单个状态表示设备的一个特定测量值或状态:
+
+| 字段 | 类型 | 长度 | 描述 |
+|-----------|--------------------|-----|----------|
+| SID | byte | 1字节 | 状态标识符 |
+| Type | byte | 1字节 | 值类型标识 |
+| ValueType | StateValueTypeEnum | 1字节 | 值类型枚举 |
+| Value | byte[] | 变长 | 值的字节表示 |
+| ValueText | object | 变长 | 值的对象表示 |
+| Metadata | string | 变长 | 元数据 (可选) |
+
+7. 子设备集合 (DeviceMessageChild)
+
+子设备集合包含多个子设备信息:
+
+| 字段 | 类型 | 长度 | 描述 |
+|------------|---------------------|-----|-------|
+| Count | byte | 1字节 | 子设备数量 |
+| ChildArray | DeviceMessageInfo[] | 变长 | 子设备数组 |
+
+ **值类型支持**
+
+DeviceCommons 支持多种数据类型:
+
+| 值类型 | 枚举值 | 长度 | 描述 |
+|-----------|-----|-----|------------------|
+| Float32 | 1 | 4字节 | 单精度浮点数 |
+| Int32 | 2 | 4字节 | 32位整数 |
+| String | 3 | 变长 | 字符串 (长度前缀:1字节) |
+| Bool | 4 | 1字节 | 布尔值 |
+| UInt16 | 6 | 2字节 | 16位无符号整数 |
+| Int16 | 7 | 2字节 | 16位有符号整数 |
+| Timestamp | 8 | 8字节 | 时间戳 (64位) |
+| Binary | 9 | 变长 | 二进制数据 (长度前缀:2字节) |
+| Double | 10 | 8字节 | 双精度浮点数 |
+
+#### 消息序列化格式
+二进制格式布局
+
+```
++---------------------------------------------------+
+| 消息头 (4字节) |
+| +-----------------------------------------------+ |
+| | 固定头 (0xC0, 0xBF) (2字节) | |
+| | 版本号 (1字节) | |
+| | 标志位 (1字节) | |
+| +-----------------------------------------------+ |
+| 主设备数据 (变长) |
+| +-----------------------------------------------+ |
+| | 设备ID长度 (1字节) | |
+| | 设备ID (变长) | |
+| | 设备类型 (1字节) | |
+| | 读数集合 (变长) | |
+| | +-------------------------------------------+ | |
+| | | 读数数量 (1字节) | | |
+| | | 读数数组 (变长) | | |
+| | | +---------------------------------------+ | | |
+| | | | 时间偏移 (2字节) | | | |
+| | | | 状态集合 (变长) | | | |
+| | | | +-----------------------------------+ | | | |
+| | | | | 状态数量 (1字节) | | | | |
+| | | | | 状态数组 (变长) | | | | |
+| | | | | +-------------------------------+ | | | | |
+| | | | | | 状态ID (1字节) | | | | | |
+| | | | | | 值类型 (1字节) | | | | | |
+| | | | | | 值数据 (变长) | | | | | |
+| | | | | +-------------------------------+ | | | | |
+| | | | +-----------------------------------+ | | | |
+| | | +---------------------------------------+ | | |
+| | +-------------------------------------------+ | |
+| +-----------------------------------------------+ |
+| 子设备集合 (变长) |
+| +-----------------------------------------------+ |
+| | 子设备数量 (1字节) | |
+| | 子设备数组 (变长) | |
+| | +-------------------------------------------+ | |
+| | | 设备信息 (与主设备结构相同) | | |
+| | +-------------------------------------------+ | |
+| +-----------------------------------------------+ |
+| CRC校验码 (变长,取决于CRC类型) |
++---------------------------------------------------+
+```
+
+#### 十六进制字符串格式
+当使用十六进制字符串格式时,消息可以包含压缩和加密信息:
+
+```
+[加密状态],[压缩状态]|[十六进制数据]
+```
+
+示例:
+
+```
+dec,raw|C0BF01054D61696E... // 未加密未压缩
+enc,gzip|1F8B080000000000... // 加密并压缩
+```
+
+
+
+#### 软件架构
+DeviceCommons 采用分层架构设计:
+
+ **1.核心层 (Core)**
+
+- 基础数据类型定义 (Enums)
+- 消息模型 (Models)
+- 工具类和扩展方法
+
+
+ **2.序列化层 (Serialization)**
+
+- 解析器体系 (Parsers)
+- 序列化器体系 (Serializers)
+- 支持版本化管理
+
+
+ **3.安全层 (Security)**
+
+- CRC 校验计算
+- AES 加密/解密
+- 可扩展的加密接口
+
+
+ **4.工具层 (Utilities)**
+
+- 内存池和对象池
+- 压缩/解压缩
+- 十六进制转换
+
+
+ **5.构建层 (Builders)**
+
+- 流畅接口构建复杂消息
+- 工厂模式创建特定类型对象
+
+
+#### 安装教程
+
+ **1. 通过 NuGet 安装**
+
+```
+dotnet add package DeviceCommons
+```
+
+ **2. 手动编译安装**
+
+```
+git clone https://gitee.com/ruan-yong/device-commons.git
+cd DeviceCommons
+dotnet build
+```
+
+ **3. 项目引用**
+- 在您的项目中添加对 DeviceCommons.dll 的引用
+
+
+#### 使用说明
+
+ **1. 基本用法**
+
+```
+// 创建简单的设备消息
+var message = DeviceMessageBuilder.Create()
+ .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16)
+ .WithMainDevice("device-001", 0x01, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, 25.5f, StateValueTypeEnum.Float32);
+ reading.AddState(2, "正常运行", StateValueTypeEnum.String);
+ reading.AddState(3, true, StateValueTypeEnum.Bool);
+ });
+ })
+ .Build();
+
+// 序列化为字节数组
+var bytes = message.BuildBytes();
+
+// 序列化为十六进制字符串(带压缩和加密)
+var hexString = message.BuildHex(compress: true, encrypt: true);
+```
+
+ **2. 高级功能**
+
+```
+// 使用AES加密
+var message = DeviceMessageBuilder.Create()
+ .WithAesEncryption("your-secret-password")
+ .WithMainDevice("secure-device", 0x01)
+ .BuildHex(compress: true, encrypt: true);
+
+// 自定义加密算法
+var message = DeviceMessageBuilder.Create()
+ .WithEncryptFunc(myEncryptFunction)
+ .WithDecryptFunc(myDecryptFunction)
+ .WithMainDevice("custom-crypto-device", 0x01)
+ .BuildHex(encrypt: true);
+
+// 注册自定义设备类型处理
+StateFactoryRegistry.RegisterFactory(0x99, () => new CustomStateFactory());
+```
+
+ **3. 解析消息**
+
+```
+// 从字节数组解析
+var parser = new DeviceMessageParser();
+var message = parser.Parser(bytes);
+
+// 从十六进制字符串解析(支持自动解压和解密)
+var message = parser.Parser(hexString);
+
+// 访问解析后的数据
+var deviceId = message.MainDevice.DID;
+var temperature = message.MainDevice.Reading.ReadingArray[0].State.StateArray[0].ValueText;
+```
+
+#### 典型消息示例
+简单温度传感器消息
+
+```
+var message = DeviceMessageBuilder.Create()
+ .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16)
+ .WithMainDevice("temp-sensor-001", 0x20, config =>
+ {
+ config.AddReading(0, reading =>
+ {
+ reading.AddState(1, 25.3f, StateValueTypeEnum.Float32); // 温度
+ reading.AddState(2, 45.0f, StateValueTypeEnum.Float32); // 湿度
+ reading.AddState(3, true, StateValueTypeEnum.Bool); // 在线状态
+ });
+ })
+ .Build();
+```
+复杂多设备消息
+
+```
+var message = DeviceMessageBuilder.Create()
+ .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC32)
+ .WithMainDevice("gateway-001", 0x10, config =>
+ {
+ config.AddReading(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), reading =>
+ {
+ reading.AddState(1, "正常运行", StateValueTypeEnum.String);
+ reading.AddState(2, 85.5f, StateValueTypeEnum.Float32); // CPU使用率
+ });
+ })
+ .WithChildDevice("sensor-001", 0x20, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, 23.5f, StateValueTypeEnum.Float32); // 温度
+ reading.AddState(2, 1024.5f, StateValueTypeEnum.Float32); // 气压
+ });
+ })
+ .WithChildDevice("sensor-002", 0x20, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, 65.2f, StateValueTypeEnum.Float32); // 湿度
+ reading.AddState(2, 120.0f, StateValueTypeEnum.Float32); // 光照
+ });
+ })
+ .Build();
+```
+
+#### 扩展性设计
+DeviceCommons 的消息结构设计具有良好的扩展性:
+
+1. 版本兼容:通过版本号字段支持协议演进
+2. 保留字段:预留位和保留字段用于未来扩展
+3. 设备类型注册:通过工厂模式支持自定义设备类型处理
+4. 值类型扩展:值类型枚举可扩展新的数据类型
+5. 元数据支持:状态值可包含额外的元数据信息
+
+#### 贡献指南:
+
+- 遵循现有的代码风格和命名约定
+- 添加适当的单元测试
+- 更新相关文档
+- 确保所有测试通过
+
+
+
+#### 特技
+
+1. 🌟 **高性能设计:** 使用 ArrayPool 和 MemoryPool 优化内存使用,减少GC压力
+2. 🔒 **多重安全:** 支持CRC校验、AES加密和自定义加密算法
+3. 📦 **智能压缩:** 自动判断何时使用压缩优化数据传输
+4. 🔧 **灵活扩展:** 通过工厂模式支持自定义设备类型处理
+5. ⚡ **异步支持:** 所有操作都提供异步版本,适合高并发场景
+6. 🌐 **多格式支持:** 支持二进制、十六进制字符串多种数据格式
+7. 🧩 **模块化设计:** 各组件解耦,可按需使用部分功能
+
+ **性能基准**
+
+| **操作类型** | **数据大小** | **平均耗时** | **内存分配** |
+|---------|------|------|------|
+| 简单消息序列化 | | | |
+| 复杂消息序列化 | | | |
+| 带压缩序列化 | | | |
+| 消息解析 | | | |
+
diff --git a/TestProject1/BaseUnitTest.cs b/TestProject1/BaseUnitTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e0fa52369a6a4e1e77a9d87f01645f6ccfabfebf
--- /dev/null
+++ b/TestProject1/BaseUnitTest.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TestProject1
+{
+ public abstract class BaseUnitTest
+ {
+ protected void LogWrite(string message)
+ {
+ Debug.WriteLine("");
+ Debug.WriteLine("-----------------------------------------------------------------------");
+ Debug.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} =>");
+ Debug.WriteLine($"{message}");
+ }
+ }
+}
diff --git a/TestProject1/BasicStructureUnitTest.cs b/TestProject1/BasicStructureUnitTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fb8fd3b202a941a8d88701d5fd8535b26fd3d634
--- /dev/null
+++ b/TestProject1/BasicStructureUnitTest.cs
@@ -0,0 +1,170 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.DeviceMessages.Serialization;
+using DeviceCommons.DeviceMessages.Serialization.V1.Parsers;
+using DeviceCommons.DeviceMessages.Serialization.V1.Serializers;
+using System.Buffers;
+
+namespace TestProject1
+{
+ public class BasicStructureUnitTest : BaseUnitTest
+ {
+ private readonly ArrayBufferWriter BufferWriter = new ArrayBufferWriter();
+ [Fact]
+ public void Test1() => State(BufferWriter, 1, StateValueTypeEnum.String, "Value");
+
+ [Fact]
+ public void Test2() => States(BufferWriter, 2);
+
+ [Fact]
+ public void Test3() => Reading(BufferWriter, 1000);
+
+ [Fact]
+ public void Test4() => Readings(BufferWriter, 3);
+
+ [Fact]
+ public void Test5() => Info(BufferWriter, "Device", 1);
+
+ [Fact]
+ public void Test6() => Child(BufferWriter, 2);
+
+ [Fact]
+ public void Test7() => Header(BufferWriter);
+
+ [Fact]
+ public void Test8() => Message();
+
+ private IDeviceMessageInfoReadingState State(ArrayBufferWriter _BufferWriter, byte sid, StateValueTypeEnum type, object val)
+ {
+ IDeviceMessageInfoReadingStateSerializer stateSerializer = new DeviceMessageInfoReadingStateSerializer();
+ stateSerializer.Model.SID = sid;
+ stateSerializer.Model.ValueType = type;
+ stateSerializer.Model.ValueText = val;
+ stateSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ IDeviceMessageInfoReadingStateParser stateParser = new DeviceMessageInfoReadingStateParser();
+ _ = stateParser.Parser(bytes);
+
+
+ return stateSerializer.Model;
+ }
+
+ private IDeviceMessageInfoReadingStates States(ArrayBufferWriter _BufferWriter, byte count)
+ {
+ IDeviceMessageInfoReadingStatesSerializer statesSerializer = new DeviceMessageInfoReadingStatesSerializer();
+ statesSerializer.Model.StateArray = new IDeviceMessageInfoReadingState[count];
+ for (byte i = 0; i < count; i++)
+ {
+ statesSerializer.Model.StateArray[i] = State(_BufferWriter, (byte)(i + 1), StateValueTypeEnum.String, "Value");
+ }
+ statesSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageInfoReadingStatesParser().Parser(bytes);
+
+ return statesSerializer.Model;
+ }
+
+ private IDeviceMessageInfoReading Reading(ArrayBufferWriter _BufferWriter, short timeOffset)
+ {
+ IDeviceMessageInfoReadingSerializer readingSerializer = new DeviceMessageInfoReadingSerializer();
+ readingSerializer.Model.TimeOffset = timeOffset;
+ readingSerializer.Model.State = States(_BufferWriter, 4);
+ readingSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageInfoReadingParser().Parser(bytes);
+
+ return readingSerializer.Model;
+ }
+
+ private IDeviceMessageInfoReadings Readings(ArrayBufferWriter _BufferWriter, byte count)
+ {
+ IDeviceMessageInfoReadingsSerializer readingsSerializer = new DeviceMessageInfoReadingsSerializer();
+ readingsSerializer.Model.ReadingArray = new IDeviceMessageInfoReading[count];
+ for (byte i = 0; i < count; i++)
+ {
+ readingsSerializer.Model.ReadingArray[i] = Reading(_BufferWriter, (short)((i + 1) * 100));
+ }
+ readingsSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageInfoReadingsParser().Parser(bytes);
+
+ return readingsSerializer.Model;
+ }
+
+ private IDeviceMessageInfo Info(ArrayBufferWriter _BufferWriter, string did, byte type)
+ {
+ IDeviceMessageInfoSerializer infoSerializer = new DeviceMessageInfoSerializer();
+ infoSerializer.Model.DID = did;
+ infoSerializer.Model.DeviceType = type;
+ infoSerializer.Model.Reading = Readings(_BufferWriter, 3);
+ infoSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageInfoParser().Parser(bytes);
+
+ return infoSerializer.Model;
+ }
+
+ private IDeviceMessageChild Child(ArrayBufferWriter _BufferWriter, byte count)
+ {
+ IDeviceMessageChildSerializer childSerializer = new DeviceMessageChildSerializer();
+ IDeviceMessageInfo[] childs = new IDeviceMessageInfo[count];
+ for (byte i = 0; i < count; i++)
+ {
+ childs[i] = Info(_BufferWriter, $"Device{(i + 1).ToString().PadLeft(2, '0')}", 148);
+ }
+ childSerializer.Model.ChildArray = childs;
+ childSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+ _ = new DeviceMessageChildParser().Parser(bytes);
+
+ return childSerializer.Model;
+ }
+
+ private IDeviceMessageHeader Header(ArrayBufferWriter _BufferWriter)
+ {
+ IDeviceMessageHeaderSerializer headerSerializer = new DeviceMessageHeaderSerializer();
+ headerSerializer.Serializer(_BufferWriter);
+ byte[] bytes = _BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageHeaderParser().Parser(bytes);
+
+ return headerSerializer.Model;
+ }
+
+ private IDeviceMessage Message()
+ {
+ IDeviceMessageSerializer messageSerializer = new DeviceMessageSerializer();
+ messageSerializer.Model.ChildDevice = Child(BufferWriter, 2);
+ messageSerializer.Model.MainDevice = Info(BufferWriter, "Device", 1);
+ messageSerializer.Model.Header = Header(BufferWriter);
+
+ messageSerializer.Serializer(BufferWriter);
+ byte[] bytes = BufferWriter.WrittenSpan.ToArray();
+
+ LogWrite(Convert.ToHexString(bytes));
+
+ _ = new DeviceMessageParser().Parser(bytes);
+
+ return messageSerializer.Model;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestProject1/BoundaryUnitTest.cs b/TestProject1/BoundaryUnitTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..750d6cd93b483ca940c3b08896965df52d1da150
--- /dev/null
+++ b/TestProject1/BoundaryUnitTest.cs
@@ -0,0 +1,408 @@
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.DeviceMessages.Serialization;
+using DeviceCommons.DeviceMessages.Serialization.V1.Parsers;
+using DeviceCommons.DeviceMessages.Serialization.V1.Serializers;
+using DeviceCommons.Exceptions;
+using System.Buffers;
+
+namespace TestProject1
+{
+ public class BoundaryUnitTest
+ {
+ private readonly ArrayBufferWriter BufferWriter = new ArrayBufferWriter();
+
+ [Fact]
+ public void TestEmptyData_ShouldThrowException()
+ {
+ // Arrange
+ var parser = new DeviceMessageParser();
+ var emptyData = Array.Empty();
+
+ // Act & Assert
+ Assert.Throws(() => parser.Parser(emptyData));
+ }
+
+ [Fact]
+ public void TestMinimalValidMessage_ShouldParseSuccessfully()
+ {
+ // Arrange
+ var parser = new DeviceMessageParser();
+ var serializer = new DeviceMessageSerializer();
+
+ // 创建最小有效消息
+ var message = new DeviceMessage
+ {
+ Header = new DeviceMessageHeader
+ {
+ Version = 0x01,
+ CRCType = CRCTypeEnum.None
+ },
+ MainDevice = new DeviceMessageInfo
+ {
+ DID = "test",
+ DeviceType = 1,
+ Reading = new DeviceMessageInfoReadings()
+ }
+ };
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ serializer.Serializer(writer, message);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Header);
+ Assert.NotNull(result.MainDevice);
+ Assert.Equal("test", result.MainDevice.DID);
+ }
+
+ [Fact]
+ public void TestMaxStringLength_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var stateParser = new DeviceMessageInfoReadingStateParser();
+ var stateSerializer = new DeviceMessageInfoReadingStateSerializer();
+
+ // 创建最大长度的字符串
+ var maxString = new string('A', byte.MaxValue);
+ var state = new DeviceMessageInfoReadingState
+ {
+ SID = 1,
+ ValueType = StateValueTypeEnum.String,
+ ValueText = maxString
+ };
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ stateSerializer.Serializer(writer, state);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = stateParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(maxString, result.ValueText);
+ }
+
+ [Fact]
+ public void TestExceedMaxStringLength_ShouldThrowException()
+ {
+ // Arrange
+ var stateSerializer = new DeviceMessageInfoReadingStateSerializer();
+
+ // 创建超过最大长度的字符串
+ var exceedString = new string('A', byte.MaxValue + 1);
+ var state = new DeviceMessageInfoReadingState
+ {
+ SID = 1,
+ ValueType = StateValueTypeEnum.String,
+ ValueText = exceedString
+ };
+
+ // Act & Assert
+ var writer = new ArrayBufferWriter();
+ Assert.Throws(() => stateSerializer.Serializer(writer, state));
+ }
+
+ [Fact]
+ public void TestInvalidValueType_ShouldHandleGracefully()
+ {
+ // Arrange
+ var stateParser = new DeviceMessageInfoReadingStateParser();
+
+ // 创建无效类型的数据
+ var invalidData = new byte[] { 1, 255, 0 }; // SID=1, Type=255(无效), ValueLength=0
+
+ // Act & Assert
+ Assert.Throws(() => stateParser.Parser(invalidData));
+ }
+
+ [Fact]
+ public void TestCRCValidation_WithMismatch_ShouldThrowException()
+ {
+ // Arrange
+ var parser = new DeviceMessageParser();
+ var serializer = new DeviceMessageSerializer();
+
+ // 创建带CRC的消息
+ var message = new DeviceMessage
+ {
+ Header = new DeviceMessageHeader
+ {
+ Version = 0x01,
+ CRCType = CRCTypeEnum.CRC16
+ },
+ MainDevice = new DeviceMessageInfo
+ {
+ DID = "test",
+ DeviceType = 1,
+ Reading = new DeviceMessageInfoReadings()
+ }
+ };
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ serializer.Serializer(writer, message);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // 修改数据以破坏CRC
+ bytes[bytes.Length - 1] ^= 0xFF; // 修改最后一个字节
+
+ // Act & Assert
+ Assert.Throws(() => parser.Parser(bytes));
+ }
+
+ [Fact]
+ public void TestLargeNumberOfReadings_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var readingsParser = new DeviceMessageInfoReadingsParser();
+ var readingsSerializer = new DeviceMessageInfoReadingsSerializer();
+
+ // 创建大量读数
+ var readings = new DeviceMessageInfoReadings
+ {
+ ReadingArray = new IDeviceMessageInfoReading[byte.MaxValue]
+ };
+
+ for (int i = 0; i < byte.MaxValue; i++)
+ {
+ readings.ReadingArray[i] = new DeviceMessageInfoReading
+ {
+ TimeOffset = (short)i,
+ State = new DeviceMessageInfoReadingStates
+ {
+ StateArray = new IDeviceMessageInfoReadingState[1]
+ {
+ new DeviceMessageInfoReadingState
+ {
+ SID = 1,
+ ValueType = StateValueTypeEnum.Int32,
+ ValueText = i
+ }
+ }
+ }
+ };
+ }
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ readingsSerializer.Serializer(writer, readings);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = readingsParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(byte.MaxValue, result.Count);
+ }
+
+ [Fact]
+ public void TestLargeNumberOfStates_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var statesParser = new DeviceMessageInfoReadingStatesParser();
+ var statesSerializer = new DeviceMessageInfoReadingStatesSerializer();
+
+ // 创建大量状态
+ var states = new DeviceMessageInfoReadingStates
+ {
+ StateArray = new IDeviceMessageInfoReadingState[byte.MaxValue]
+ };
+
+ for (int i = 0; i < byte.MaxValue; i++)
+ {
+ states.StateArray[i] = new DeviceMessageInfoReadingState
+ {
+ SID = (byte)i,
+ ValueType = StateValueTypeEnum.Int32,
+ ValueText = i
+ };
+ }
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ statesSerializer.Serializer(writer, states);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = statesParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(byte.MaxValue, result.Count);
+ }
+
+ [Fact]
+ public void TestEmptyDeviceID_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var infoParser = new DeviceMessageInfoParser();
+ var infoSerializer = new DeviceMessageInfoSerializer();
+
+ var deviceInfo = new DeviceMessageInfo
+ {
+ DID = "", // 空设备ID
+ DeviceType = 1,
+ Reading = new DeviceMessageInfoReadings()
+ };
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ infoSerializer.Serializer(writer, deviceInfo);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = infoParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("", result.DID);
+ Assert.Equal(0, result.Length);
+ }
+
+ [Fact]
+ public void TestLongDeviceID_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var infoParser = new DeviceMessageInfoParser();
+ var infoSerializer = new DeviceMessageInfoSerializer();
+
+ // 创建长设备ID
+ var longDeviceId = new string('A', byte.MaxValue);
+ var deviceInfo = new DeviceMessageInfo
+ {
+ DID = longDeviceId,
+ DeviceType = 1,
+ Reading = new DeviceMessageInfoReadings()
+ };
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ infoSerializer.Serializer(writer, deviceInfo);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = infoParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(longDeviceId, result.DID);
+ Assert.Equal(byte.MaxValue, result.Length);
+ }
+
+ [Fact]
+ public void TestMalformedData_ShouldThrowException()
+ {
+ // Arrange
+ var parser = new DeviceMessageParser();
+
+ // 创建格式错误的数据
+ var malformedData = new byte[] { 0xC0, 0xBF, 0x01, 0x20 }; // 只有头部,缺少必要数据
+
+ // Act & Assert
+ Assert.Throws(() => parser.Parser(malformedData));
+ }
+
+ [Fact]
+ public void TestInsufficientBuffer_ShouldThrowException()
+ {
+ // Arrange
+ var stateParser = new DeviceMessageInfoReadingStateParser();
+
+ // 创建不足的数据
+ var insufficientData = new byte[] { 1, (byte)StateValueTypeEnum.String }; // 只有SID和类型,缺少长度和值
+
+ // Act & Assert
+ Assert.Throws(() => stateParser.Parser(insufficientData));
+ }
+
+ [Fact]
+ public void TestAllValueTypes_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var stateParser = new DeviceMessageInfoReadingStateParser();
+ var stateSerializer = new DeviceMessageInfoReadingStateSerializer();
+
+ // 测试所有值类型
+ var valueTypes = Enum.GetValues(typeof(StateValueTypeEnum)).Cast();
+
+ foreach (var valueType in valueTypes)
+ {
+ var state = new DeviceMessageInfoReadingState
+ {
+ SID = 1,
+ ValueType = valueType
+ };
+
+ // 设置合适的值
+ switch (valueType)
+ {
+ case StateValueTypeEnum.Float32:
+ state.ValueText = 3.14f;
+ break;
+ case StateValueTypeEnum.Int32:
+ state.ValueText = 42;
+ break;
+ case StateValueTypeEnum.String:
+ state.ValueText = "test";
+ break;
+ case StateValueTypeEnum.Bool:
+ state.ValueText = true;
+ break;
+ case StateValueTypeEnum.UInt16:
+ state.ValueText = (ushort)123;
+ break;
+ case StateValueTypeEnum.Int16:
+ state.ValueText = (short)-123;
+ break;
+ case StateValueTypeEnum.Timestamp:
+ state.ValueText = (ulong)1234567890;
+ break;
+ case StateValueTypeEnum.Binary:
+ state.ValueText = "binary data";
+ break;
+ case StateValueTypeEnum.Double:
+ state.ValueText = 3.14159265358979;
+ break;
+ }
+
+ // 序列化
+ var writer = new ArrayBufferWriter();
+ stateSerializer.Serializer(writer, state);
+ var bytes = writer.WrittenSpan.ToArray();
+
+ // Act
+ var result = stateParser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(valueType, result.ValueType);
+ }
+ }
+
+ [Fact]
+ public void TestNullValues_ShouldHandleGracefully()
+ {
+ // Arrange
+ var infoSerializer = new DeviceMessageInfoSerializer();
+ var deviceInfo = new DeviceMessageInfo
+ {
+ DID = null, // null设备ID
+ DeviceType = 1,
+ Reading = null // null读数
+ };
+
+ // Act & Assert
+ var writer = new ArrayBufferWriter();
+ Assert.Throws(() => infoSerializer.Serializer(writer, deviceInfo));
+ }
+ }
+}
diff --git a/TestProject1/BuildUnitTest.cs b/TestProject1/BuildUnitTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..74614d86ce01f8ead4cb09dec64fe1e566b13232
--- /dev/null
+++ b/TestProject1/BuildUnitTest.cs
@@ -0,0 +1,157 @@
+using BenchmarkDotNet.Attributes;
+using DeviceCommons.DeviceMessages.Builders;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Serialization;
+using System.Diagnostics;
+using System.Text;
+
+namespace TestProject1
+{
+ public class BuildUnitTest : BaseUnitTest
+ {
+ private static readonly IDeviceMessageParser PAR = new DeviceMessageParser();
+ private static readonly Stopwatch SW = new Stopwatch();
+ [Fact]
+ public void Test1()
+ {
+ IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create().WithMainDevice("Device", 148);
+ var builder = deviceMessage.BuildHex().ToUpper();
+ LogWrite(builder);
+ PAR.Parser(builder);
+ }
+
+ [Fact]
+ public void Test2()
+ {
+ IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create()
+ .WithHeader(version: 2, crcType: CRCTypeEnum.CRC8)
+ .WithMainDevice("Device", 148);
+ var builder = deviceMessage.BuildHex().ToUpper();
+ LogWrite(builder);
+ PAR.Parser(builder);
+ }
+
+ [Fact]
+ public void Test3()
+ {
+ IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create()
+ .WithHeader(version: 2, crcType: CRCTypeEnum.CRC8)
+ .WithMainDevice("Device", 148,
+ config: rs =>
+ {
+ rs.AddReading(260 * 1, config: r =>
+ {
+ for (int i = 1; i <= 3; i++)
+ {
+ r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ }
+ });
+ rs.AddReading(260 * 2, config: r =>
+ {
+ for (int i = 1; i <= 3; i++)
+ {
+ r.AddState(9, Encoding.UTF8.GetBytes("Binary"), StateValueTypeEnum.Binary);
+ }
+ });
+ }
+ );
+ var model = deviceMessage.Build();
+ var builder = deviceMessage.BuildHex().ToUpper();
+ LogWrite(builder);
+ PAR.Parser(builder);
+ }
+
+ [Fact]
+ public void Test4()
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create()
+ .WithHeader(version: 2, crcType: CRCTypeEnum.CRC16)
+ .WithMainDevice("Device", 148,
+ config: rs =>
+ {
+ rs.AddReading(260 * 1, config: r =>
+ {
+ for (int i = 1; i <= 3; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ rs.AddReading(260 * 2, config: r =>
+ {
+ for (int i = 1; i <= 3; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ }
+ )
+ .WithChildDevice("Device1", 149, (rs) =>
+ {
+ rs.AddReading(260 * 1, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ rs.AddReading(260 * 2, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ rs.AddReading(260 * 3, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ })
+ .WithChildDevice("Device2", 149, (rs) =>
+ {
+ rs.AddReading(260 * 1, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ rs.AddReading(260 * 2, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ rs.AddReading(260 * 3, config: r =>
+ {
+ for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String);
+ });
+ });
+ var model1 = deviceMessage.Build();
+ var bytes = deviceMessage.BuildBytes();
+ sw.Stop();
+ _ = sw.ElapsedMilliseconds;
+ var builder = deviceMessage.BuildHex().ToUpper();
+ LogWrite(builder);
+ }
+
+
+ [Theory]
+ [InlineData(new byte[] { 0x01, 0x02, 0x03 })]
+ public void Test5(byte[] builder)
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ sw.Restart();
+ PAR.Parser(builder);
+ sw.Stop();
+ _ = sw.ElapsedMilliseconds;
+ }
+
+ [Fact]
+ public void Test6()
+ {
+ string hex = "DEC,RAW|c0bf0220074d61696e446576940203e8010103064f6e6c696e6507d0010203024f4b01064368696c643195010bb80201030454656d7002030432352e33302d";
+ SW.Restart();
+ PAR.Parser(hex);
+ SW.Stop();
+ _ = SW.ElapsedMilliseconds;
+ }
+
+ [Fact]
+ public void Test7()
+ {
+ var msg = DeviceMessageBuilder.Create()
+ .WithMainDevice("D001", 0x01)
+ .AddReading(100,
+ (1, 25.3f, null), // 自动推断 Float32
+ (2, "Running", null), // 自动推断 String
+ (3, true, null), // 自动推断 Bool
+ (4, (short)-5, StateValueTypeEnum.Int16)) // 手动指定
+ .BuildHex();
+ }
+
+ }
+}
diff --git a/TestProject1/ComprehensiveUnitTest.cs b/TestProject1/ComprehensiveUnitTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4777766d03ca6649b6ed3199d5a41c9a8d5977c
--- /dev/null
+++ b/TestProject1/ComprehensiveUnitTest.cs
@@ -0,0 +1,348 @@
+using DeviceCommons.DeviceMessages.Builders;
+using DeviceCommons.DeviceMessages.Enums;
+using DeviceCommons.DeviceMessages.Models.V1;
+using DeviceCommons.DeviceMessages.Serialization;
+using DeviceCommons.Exceptions;
+using System.Buffers;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace DeviceCommons.Tests
+{
+ public class ComprehensiveUnitTest
+ {
+ private readonly ITestOutputHelper _output;
+ private readonly IDeviceMessageParser _parser;
+ private readonly IDeviceMessageSerializer _serializer;
+
+ public ComprehensiveUnitTest(ITestOutputHelper output)
+ {
+ _output = output;
+ _parser = new DeviceMessageParser();
+ _serializer = new DeviceMessageSerializer();
+ }
+
+ [Fact]
+ public void FullMessageRoundTrip_ShouldWorkCorrectly()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16)
+ .WithMainDevice("MainDevice001", 0x01, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, 25.5f, StateValueTypeEnum.Float32);
+ reading.AddState(2, "正常", StateValueTypeEnum.String);
+ reading.AddState(3, true, StateValueTypeEnum.Bool);
+ });
+ config.AddReading(200, reading =>
+ {
+ reading.AddState(1, 26.1f, StateValueTypeEnum.Float32);
+ reading.AddState(4, 123, StateValueTypeEnum.Int32);
+ });
+ })
+ .WithChildDevice("ChildDevice001", 0x02, config =>
+ {
+ config.AddReading(50, reading =>
+ {
+ reading.AddState(1, 22.3f, StateValueTypeEnum.Float32);
+ reading.AddState(5, 456, StateValueTypeEnum.Int16);
+ });
+ });
+
+ var originalMessage = builder.Build();
+
+ // Act - Serialize
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, originalMessage);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ // Act - Deserialize
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.NotNull(parsedMessage.Header);
+ Assert.NotNull(parsedMessage.MainDevice);
+ Assert.NotNull(parsedMessage.ChildDevice);
+
+ Assert.Equal(originalMessage.Header.Version, parsedMessage.Header.Version);
+ Assert.Equal(originalMessage.Header.CRCType, parsedMessage.Header.CRCType);
+ Assert.Equal(originalMessage.MainDevice.DID, parsedMessage.MainDevice.DID);
+ Assert.Equal(originalMessage.MainDevice.DeviceType, parsedMessage.MainDevice.DeviceType);
+ Assert.Equal(originalMessage.ChildDevice.Count, parsedMessage.ChildDevice.Count);
+
+ _output.WriteLine($"Original: {originalMessage.MainDevice.DID}");
+ _output.WriteLine($"Parsed: {parsedMessage.MainDevice.DID}");
+ _output.WriteLine($"Bytes length: {bytes.Length}");
+ }
+
+ [Fact]
+ public void MessageWithAllValueTypes_ShouldSerializeCorrectly()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithMainDevice("TestDevice", 0x01, config =>
+ {
+ config.AddReading(0, reading =>
+ {
+ // Test all supported value types
+ reading.AddState(1, 3.14f, StateValueTypeEnum.Float32);
+ reading.AddState(2, 42, StateValueTypeEnum.Int32);
+ reading.AddState(3, "Test String", StateValueTypeEnum.String);
+ reading.AddState(4, true, StateValueTypeEnum.Bool);
+ reading.AddState(6, (ushort)123, StateValueTypeEnum.UInt16);
+ reading.AddState(7, (short)-123, StateValueTypeEnum.Int16);
+ reading.AddState(8, (ulong)1234567890, StateValueTypeEnum.Timestamp);
+ reading.AddState(9, Encoding.UTF8.GetBytes("Binary"), StateValueTypeEnum.Binary);
+ reading.AddState(10, 3.1415926535, StateValueTypeEnum.Double);
+ });
+ });
+
+ var message = builder.Build();
+
+ // Act
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ var reading = parsedMessage.MainDevice.Reading.ReadingArray[0];
+ Assert.Equal(9, reading.State.Count);
+
+ _output.WriteLine($"Value types test completed successfully");
+ _output.WriteLine($"Serialized size: {bytes.Length} bytes");
+ }
+
+ [Fact]
+ public void EmptyMessage_ShouldHandleGracefully()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithHeader()
+ .WithMainDevice("", 0x00); // Empty device
+
+ var message = builder.Build();
+
+ // Act
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.Equal(string.Empty, parsedMessage.MainDevice.DID);
+ Assert.Equal(0, parsedMessage.MainDevice.DeviceType);
+ }
+
+ [Fact]
+ public void MessageWithMultipleChildren_ShouldHandleCorrectly()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithMainDevice("Main", 0x01)
+ .WithChildDevice("Child1", 0x02, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, "Child1-Reading1", StateValueTypeEnum.String);
+ });
+ })
+ .WithChildDevice("Child2", 0x03, config =>
+ {
+ config.AddReading(200, reading =>
+ {
+ reading.AddState(1, "Child2-Reading1", StateValueTypeEnum.String);
+ });
+ })
+ .WithChildDevice("Child3", 0x04, config =>
+ {
+ config.AddReading(300, reading =>
+ {
+ reading.AddState(1, "Child3-Reading1", StateValueTypeEnum.String);
+ });
+ });
+
+ var message = builder.Build();
+
+ // Act
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.NotNull(parsedMessage.ChildDevice);
+ Assert.Equal(3, parsedMessage.ChildDevice.Count);
+ Assert.Equal("Child1", parsedMessage.ChildDevice.ChildArray[2].DID);
+ Assert.Equal("Child2", parsedMessage.ChildDevice.ChildArray[1].DID);
+ Assert.Equal("Child3", parsedMessage.ChildDevice.ChildArray[0].DID);
+ }
+
+ [Fact]
+ public void MessageWithCRC_ShouldValidateCorrectly()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithHeader(crcType: CRCTypeEnum.CRC16)
+ .WithMainDevice("TestDevice", 0x01, config =>
+ {
+ config.AddReading(0, reading =>
+ {
+ reading.AddState(1, "Test Value", StateValueTypeEnum.String);
+ });
+ });
+
+ var message = builder.Build();
+
+ // Act - Serialize with CRC
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ // Should not throw when CRC is valid
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Corrupt the CRC and verify it throws
+ bytes[bytes.Length - 1] ^= 0xFF; // Flip last byte to corrupt CRC
+
+ // Assert
+ Assert.ThrowsAny(() => _parser.Parser(bytes));
+ }
+
+ [Theory]
+ [InlineData(CRCTypeEnum.None)]
+ [InlineData(CRCTypeEnum.CRC8)]
+ [InlineData(CRCTypeEnum.CRC16)]
+ [InlineData(CRCTypeEnum.CRC32)]
+ public void DifferentCRCTypes_ShouldWorkCorrectly(CRCTypeEnum crcType)
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithHeader(crcType: crcType)
+ .WithMainDevice("TestDevice", 0x01, config =>
+ {
+ config.AddReading(0, reading =>
+ {
+ reading.AddState(1, "Test Data", StateValueTypeEnum.String);
+ });
+ });
+
+ var message = builder.Build();
+
+ // Act
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ // Should not throw for any valid CRC type
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.Equal(crcType, parsedMessage.Header.CRCType);
+ }
+
+ [Fact]
+ public void LargeMessage_ShouldHandleCorrectly()
+ {
+ // Arrange - Create a message with many readings and states
+ var builder = DeviceMessageBuilder.Create()
+ .WithMainDevice("LargeDevice", 0x01, config =>
+ {
+ for (int i = 0; i < 50; i++)
+ {
+ config.AddReading((short)(i * 10), reading =>
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ reading.AddState((byte)j, $"Value{i}-{j}", StateValueTypeEnum.String);
+ }
+ });
+ }
+ });
+
+ var message = builder.Build();
+
+ // Act
+ var arrayBufferWriter = new ArrayBufferWriter();
+ _serializer.Serializer(arrayBufferWriter, message);
+ var bytes = arrayBufferWriter.WrittenSpan.ToArray();
+
+ var parsedMessage = _parser.Parser(bytes);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.Equal(50, parsedMessage.MainDevice.Reading.Count);
+ _output.WriteLine($"Large message size: {bytes.Length} bytes");
+ }
+
+ [Fact]
+ public void HexSerialization_ShouldWorkCorrectly()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithMainDevice("HexTest", 0x01, config =>
+ {
+ config.AddReading(100, reading =>
+ {
+ reading.AddState(1, "Test Value", StateValueTypeEnum.String);
+ });
+ });
+
+ // Act - Serialize to hex
+ var hexString = builder.BuildHex();
+
+ // Act - Parse from hex
+ var parsedMessage = _parser.Parser(hexString);
+
+ // Assert
+ Assert.NotNull(parsedMessage);
+ Assert.Equal("HexTest", parsedMessage.MainDevice.DID);
+ _output.WriteLine($"Hex string: {hexString}");
+ }
+
+ [Fact]
+ public void Compression_ShouldReduceSize()
+ {
+ // Arrange
+ var builder = DeviceMessageBuilder.Create()
+ .WithMainDevice("CompressTest", 0x01, config =>
+ {
+ for (int i = 0; i < 100; i++)
+ {
+ config.AddReading((short)(i * 10), reading =>
+ {
+ for (int j = 0; j < 5; j++)
+ {
+ reading.AddState((byte)j, $"Long value data {i}-{j} that should compress well", StateValueTypeEnum.String);
+ }
+ });
+ }
+ });
+
+ // Act - Serialize without compression
+ var normalHex = builder.BuildHex(compress: false);
+
+ // Act - Serialize with compression
+ var compressedHex = builder.BuildHex(compress: true);
+
+ // Assert
+ Assert.True(compressedHex.Length < normalHex.Length,
+ $"Compressed size ({compressedHex.Length}) should be smaller than normal size ({normalHex.Length})");
+
+ _output.WriteLine($"Normal: {normalHex.Length} chars");
+ _output.WriteLine($"Compressed: {compressedHex.Length} chars");
+ _output.WriteLine($"Ratio: {(double)compressedHex.Length / normalHex.Length:P}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestProject1/TestProject1.csproj b/TestProject1/TestProject1.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..18c97ee9d293de0969bfe0311420590f0a906462
--- /dev/null
+++ b/TestProject1/TestProject1.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+