Complete typed COM decoding

This commit is contained in:
2026-04-11 10:10:43 +08:00
parent 47a2afa3a0
commit 4317deda4b
3 changed files with 346 additions and 0 deletions

View File

@@ -9,6 +9,39 @@ constexpr quint8 COM_CDC_CONST_PACKET_HEAD2 = 0x55;
constexpr int COM_CDC_CONST_FRAME_OVERHEAD = 5;
constexpr int COM_CDC_CONST_MAX_PAYLOAD_LENGTH = 64;
quint16 Com_Cdc_Func_ReadLe16(const QByteArray& ByteArray, int Offset)
{
return static_cast<quint16>(
static_cast<quint8>(ByteArray.at(Offset)) |
(static_cast<quint16>(static_cast<quint8>(ByteArray.at(Offset + 1))) << 8));
}
quint32 Com_Cdc_Func_ReadLe32(const QByteArray& ByteArray, int Offset)
{
quint32 Value = 0;
for (int Index = 0; Index < 4; ++Index)
{
Value |= static_cast<quint32>(static_cast<quint8>(ByteArray.at(Offset + Index))) <<
(Index * 8);
}
return Value;
}
quint64 Com_Cdc_Func_ReadLe64(const QByteArray& ByteArray, int Offset)
{
quint64 Value = 0;
for (int Index = 0; Index < 8; ++Index)
{
Value |= static_cast<quint64>(static_cast<quint8>(ByteArray.at(Offset + Index))) <<
(Index * 8);
}
return Value;
}
bool Com_Cdc_Func_IsKnownLengthValid(Packet_Type Type, quint8 DataLength)
{
switch (Type)
@@ -48,6 +81,15 @@ bool Com_Cdc_Func_IsKnownLengthValid(Packet_Type Type, quint8 DataLength)
}
}
bool Com_Cdc_Func_IsExpectedPacket(
const Packet& PacketData,
Packet_Type Type,
quint8 ExpectedLength)
{
return (PacketData.type == Type) &&
(PacketData.data.size() == ExpectedLength);
}
bool Com_Cdc_Func_ParseFrameAtStart(const QByteArray& ByteArray, Packet* p_Packet)
{
if ((p_Packet == nullptr) || (ByteArray.size() < COM_CDC_CONST_FRAME_OVERHEAD))
@@ -196,3 +238,187 @@ bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet)
return true;
}
}
bool Com_Cdc_Func_ParseHelloReq(
const Packet& PacketData,
Packet_HelloReq* p_HelloReq)
{
if ((p_HelloReq == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_HelloReq,
Packet_len::Com_Len_HelloReq))
{
return false;
}
Packet_HelloReq HelloReq;
HelloReq.ProtocolVersion = static_cast<quint8>(PacketData.data.at(0));
*p_HelloReq = HelloReq;
return true;
}
bool Com_Cdc_Func_ParseHelloRsp(
const Packet& PacketData,
Packet_HelloRsp* p_HelloRsp)
{
if ((p_HelloRsp == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_HelloRsp,
Packet_len::Com_Len_HelloRsp))
{
return false;
}
Packet_HelloRsp HelloRsp;
HelloRsp.ProtocolVersion = static_cast<quint8>(PacketData.data.at(0));
HelloRsp.VendorId = Com_Cdc_Func_ReadLe16(PacketData.data, 1);
HelloRsp.ProductId = Com_Cdc_Func_ReadLe16(PacketData.data, 3);
HelloRsp.FirmwareMajor = static_cast<quint8>(PacketData.data.at(5));
HelloRsp.FirmwareMinor = static_cast<quint8>(PacketData.data.at(6));
HelloRsp.CapabilityFlags = Com_Cdc_Func_ReadLe16(PacketData.data, 7);
*p_HelloRsp = HelloRsp;
return true;
}
bool Com_Cdc_Func_ParseBitmap(
const Packet& PacketData,
Packet_Bitmap* p_Bitmap)
{
if ((p_Bitmap == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_Bitmap,
Packet_len::Com_Len_Bitmap))
{
return false;
}
Packet_Bitmap Bitmap;
Bitmap.UsageBitmap = PacketData.data;
*p_Bitmap = Bitmap;
return true;
}
bool Com_Cdc_Func_ParseFunctionKeyEvent(
const Packet& PacketData,
Packet_FunctionKeyEvent* p_Event)
{
if ((p_Event == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_FunctionKeyEvent,
Packet_len::Com_Len_FunctionKeyEvent))
{
return false;
}
Packet_FunctionKeyEvent EventData;
EventData.Usage = Com_Cdc_Func_ReadLe16(PacketData.data, 0);
EventData.Action = static_cast<quint8>(PacketData.data.at(2));
*p_Event = EventData;
return true;
}
bool Com_Cdc_Func_ParseLedState(
const Packet& PacketData,
Packet_LedState* p_LedState)
{
if ((p_LedState == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_LedState,
Packet_len::Com_Len_LedState))
{
return false;
}
Packet_LedState LedState;
LedState.LedMask = static_cast<quint8>(PacketData.data.at(0));
*p_LedState = LedState;
return true;
}
bool Com_Cdc_Func_ParseTimeSync(
const Packet& PacketData,
Packet_TimeSync* p_TimeSync)
{
if ((p_TimeSync == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_TimeSync,
Packet_len::Com_Len_TimeSync))
{
return false;
}
Packet_TimeSync TimeSync;
TimeSync.Version = static_cast<quint8>(PacketData.data.at(0));
TimeSync.Flags = static_cast<quint8>(PacketData.data.at(1));
TimeSync.TimezoneMin = Com_Cdc_Func_ReadLe16(PacketData.data, 2);
TimeSync.UtcMs = Com_Cdc_Func_ReadLe64(PacketData.data, 4);
TimeSync.AccuracyMs = Com_Cdc_Func_ReadLe32(PacketData.data, 12);
*p_TimeSync = TimeSync;
return true;
}
bool Com_Cdc_Func_ParseThemeRgb(
const Packet& PacketData,
Packet_ThemeRgb* p_ThemeRgb)
{
if ((p_ThemeRgb == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_ThemeRgb,
Packet_len::Com_Len_ThemeRgb))
{
return false;
}
Packet_ThemeRgb ThemeRgb;
ThemeRgb.Red = static_cast<quint8>(PacketData.data.at(0));
ThemeRgb.Green = static_cast<quint8>(PacketData.data.at(1));
ThemeRgb.Blue = static_cast<quint8>(PacketData.data.at(2));
*p_ThemeRgb = ThemeRgb;
return true;
}
bool Com_Cdc_Func_ParseAck(
const Packet& PacketData,
Packet_Ack* p_Ack)
{
if ((p_Ack == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_Ack,
Packet_len::Com_Len_Ack))
{
return false;
}
Packet_Ack Ack;
Ack.AckedType = static_cast<quint8>(PacketData.data.at(0));
*p_Ack = Ack;
return true;
}
bool Com_Cdc_Func_ParseError(
const Packet& PacketData,
Packet_Error* p_Error)
{
if ((p_Error == nullptr) ||
!Com_Cdc_Func_IsExpectedPacket(
PacketData,
Com_Type_Error,
Packet_len::Com_Len_Error))
{
return false;
}
Packet_Error Error;
Error.ErrorType = static_cast<quint8>(PacketData.data.at(0));
Error.ErrorCode = static_cast<quint8>(PacketData.data.at(1));
*p_Error = Error;
return true;
}

View File

@@ -6,3 +6,31 @@
bool Com_Cdc_Func_ParseFrame(const QByteArray& ByteArray, Packet* p_Packet);
bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet);
bool Com_Cdc_Func_ParseHelloReq(
const Packet& PacketData,
Packet_HelloReq* p_HelloReq);
bool Com_Cdc_Func_ParseHelloRsp(
const Packet& PacketData,
Packet_HelloRsp* p_HelloRsp);
bool Com_Cdc_Func_ParseBitmap(
const Packet& PacketData,
Packet_Bitmap* p_Bitmap);
bool Com_Cdc_Func_ParseFunctionKeyEvent(
const Packet& PacketData,
Packet_FunctionKeyEvent* p_Event);
bool Com_Cdc_Func_ParseLedState(
const Packet& PacketData,
Packet_LedState* p_LedState);
bool Com_Cdc_Func_ParseTimeSync(
const Packet& PacketData,
Packet_TimeSync* p_TimeSync);
bool Com_Cdc_Func_ParseThemeRgb(
const Packet& PacketData,
Packet_ThemeRgb* p_ThemeRgb);
bool Com_Cdc_Func_ParseAck(
const Packet& PacketData,
Packet_Ack* p_Ack);
bool Com_Cdc_Func_ParseError(
const Packet& PacketData,
Packet_Error* p_Error);

92
docs/host_com_rebuild.md Normal file
View File

@@ -0,0 +1,92 @@
# Host COM Rebuild
## Goal
Keep the COM layer small, explicit, and easy to teach.
The COM layer should:
- define packet types and payload structures
- build full wire frames
- parse full wire frames
- parse typed packet payloads
The COM layer should not:
- enumerate devices
- open ports
- own handshake state
- own UI state
## Current file roles
### `Com_Cdc.h`
Owns the packet model:
- packet types
- fixed payload lengths
- payload structs
### `Com_CdcEncode.h/.cpp`
Owns the write direction:
- checksum
- full frame build
- typed payload encode
### `Com_CdcDecode.h/.cpp`
Owns the read direction:
- full frame parse
- stream extraction
- typed payload decode
## Completed nodes
### Node 1: legacy frame encode baseline
Already present before this step:
- build full frame
- build all typed packets
### Node 2: typed packet decode completion
Files updated in this step:
- `KeyBorad/KeyBorad/COM/Com_CdcDecode.h`
- `KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp`
Design notes:
- keep decode logic flat and explicit
- one helper for little-endian reads
- one helper for type + length validation
- one decode function per packet type
Implemented behavior:
- parse frame header and checksum
- extract frames from a byte stream
- decode:
- `HelloReq`
- `HelloRsp`
- `Bitmap`
- `FunctionKeyEvent`
- `LedState`
- `TimeSync`
- `ThemeRgb`
- `Ack`
- `Error`
## COM done criteria
For the current project stage, COM is considered done when:
1. every supported packet can be built
2. every supported packet can be parsed
3. stream extraction is stable
4. higher layers no longer need to know byte offsets