Repair project layout and fix Lab024B spectrum detection
This commit is contained in:
254
protocol/packet.py
Normal file
254
protocol/packet.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
Пакетный протокол SDR Rover Link.
|
||||
|
||||
Модуль содержит функции:
|
||||
- build_packet() — формирование пакета;
|
||||
- parse_packet() — проверка и разбор пакета.
|
||||
|
||||
Версия протокола: 0.1
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Константы протокола
|
||||
# ============================================================
|
||||
|
||||
SYNC_WORD = 0xAA55
|
||||
PROTOCOL_VERSION = 1
|
||||
|
||||
MESSAGE_TYPE_TEXT = 1
|
||||
MESSAGE_TYPE_TELEMETRY = 2
|
||||
MESSAGE_TYPE_IMAGE_FRAGMENT = 3
|
||||
MESSAGE_TYPE_ACK = 4
|
||||
|
||||
# Структура заголовка:
|
||||
#
|
||||
# H — SYNC, 2 байта
|
||||
# B — версия, 1 байт
|
||||
# B — тип сообщения, 1 байт
|
||||
# H — порядковый номер, 2 байта
|
||||
# H — длина полезной нагрузки, 2 байта
|
||||
HEADER_FORMAT = ">HBBHH"
|
||||
|
||||
# CRC-32 хранится как беззнаковое 32-битное число.
|
||||
CRC_FORMAT = ">I"
|
||||
|
||||
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
||||
CRC_SIZE = struct.calcsize(CRC_FORMAT)
|
||||
|
||||
MAX_PAYLOAD_SIZE = 65535
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Ошибки протокола
|
||||
# ============================================================
|
||||
|
||||
class PacketError(ValueError):
|
||||
"""Общая ошибка структуры пакета."""
|
||||
|
||||
|
||||
class CRCError(PacketError):
|
||||
"""Ошибка контрольной суммы CRC."""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Результат разбора пакета
|
||||
# ============================================================
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ParsedPacket:
|
||||
"""
|
||||
Представление успешно принятого пакета.
|
||||
|
||||
Атрибуты:
|
||||
version: версия протокола;
|
||||
message_type: тип сообщения;
|
||||
sequence_number: порядковый номер;
|
||||
payload: полезная нагрузка в байтах.
|
||||
"""
|
||||
|
||||
version: int
|
||||
message_type: int
|
||||
sequence_number: int
|
||||
payload: bytes
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Формирование пакета
|
||||
# ============================================================
|
||||
|
||||
def build_packet(
|
||||
payload: bytes,
|
||||
message_type: int,
|
||||
sequence_number: int,
|
||||
) -> bytes:
|
||||
"""
|
||||
Сформировать пакет SDR Rover Link.
|
||||
|
||||
Параметры:
|
||||
payload:
|
||||
Полезная нагрузка в байтах.
|
||||
|
||||
message_type:
|
||||
Тип сообщения от 0 до 255.
|
||||
|
||||
sequence_number:
|
||||
Порядковый номер от 0 до 65535.
|
||||
|
||||
Возвращает:
|
||||
Полный пакет в виде bytes.
|
||||
"""
|
||||
|
||||
if not isinstance(payload, (bytes, bytearray)):
|
||||
raise TypeError(
|
||||
"payload должен иметь тип bytes или bytearray"
|
||||
)
|
||||
|
||||
payload = bytes(payload)
|
||||
|
||||
if not 0 <= message_type <= 255:
|
||||
raise ValueError(
|
||||
"message_type должен находиться в диапазоне 0...255"
|
||||
)
|
||||
|
||||
if not 0 <= sequence_number <= 65535:
|
||||
raise ValueError(
|
||||
"sequence_number должен находиться в диапазоне 0...65535"
|
||||
)
|
||||
|
||||
payload_length = len(payload)
|
||||
|
||||
if payload_length > MAX_PAYLOAD_SIZE:
|
||||
raise ValueError(
|
||||
f"Полезная нагрузка превышает {MAX_PAYLOAD_SIZE} байт"
|
||||
)
|
||||
|
||||
header = struct.pack(
|
||||
HEADER_FORMAT,
|
||||
SYNC_WORD,
|
||||
PROTOCOL_VERSION,
|
||||
message_type,
|
||||
sequence_number,
|
||||
payload_length,
|
||||
)
|
||||
|
||||
packet_without_crc = header + payload
|
||||
|
||||
crc_value = (
|
||||
zlib.crc32(packet_without_crc) & 0xFFFFFFFF
|
||||
)
|
||||
|
||||
crc_bytes = struct.pack(
|
||||
CRC_FORMAT,
|
||||
crc_value,
|
||||
)
|
||||
|
||||
return packet_without_crc + crc_bytes
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Разбор пакета
|
||||
# ============================================================
|
||||
|
||||
def parse_packet(packet: bytes) -> ParsedPacket:
|
||||
"""
|
||||
Проверить и разобрать пакет SDR Rover Link.
|
||||
|
||||
Проверяются:
|
||||
- минимальный размер;
|
||||
- маркер SYNC;
|
||||
- версия протокола;
|
||||
- длина пакета;
|
||||
- CRC-32.
|
||||
|
||||
Возвращает:
|
||||
ParsedPacket.
|
||||
|
||||
Вызывает:
|
||||
PacketError — при ошибке структуры;
|
||||
CRCError — при несовпадении CRC.
|
||||
"""
|
||||
|
||||
if not isinstance(packet, (bytes, bytearray)):
|
||||
raise TypeError(
|
||||
"packet должен иметь тип bytes или bytearray"
|
||||
)
|
||||
|
||||
packet = bytes(packet)
|
||||
|
||||
minimum_packet_size = HEADER_SIZE + CRC_SIZE
|
||||
|
||||
if len(packet) < minimum_packet_size:
|
||||
raise PacketError(
|
||||
"Пакет слишком короткий"
|
||||
)
|
||||
|
||||
header = packet[:HEADER_SIZE]
|
||||
|
||||
(
|
||||
sync_word,
|
||||
version,
|
||||
message_type,
|
||||
sequence_number,
|
||||
payload_length,
|
||||
) = struct.unpack(
|
||||
HEADER_FORMAT,
|
||||
header,
|
||||
)
|
||||
|
||||
if sync_word != SYNC_WORD:
|
||||
raise PacketError(
|
||||
f"Неверный SYNC: 0x{sync_word:04X}"
|
||||
)
|
||||
|
||||
if version != PROTOCOL_VERSION:
|
||||
raise PacketError(
|
||||
f"Неподдерживаемая версия протокола: {version}"
|
||||
)
|
||||
|
||||
expected_packet_size = (
|
||||
HEADER_SIZE
|
||||
+ payload_length
|
||||
+ CRC_SIZE
|
||||
)
|
||||
|
||||
if len(packet) != expected_packet_size:
|
||||
raise PacketError(
|
||||
"Длина пакета не соответствует полю LENGTH: "
|
||||
f"ожидалось {expected_packet_size} байт, "
|
||||
f"получено {len(packet)} байт"
|
||||
)
|
||||
|
||||
data_without_crc = packet[:-CRC_SIZE]
|
||||
received_crc_bytes = packet[-CRC_SIZE:]
|
||||
|
||||
(received_crc,) = struct.unpack(
|
||||
CRC_FORMAT,
|
||||
received_crc_bytes,
|
||||
)
|
||||
|
||||
calculated_crc = (
|
||||
zlib.crc32(data_without_crc) & 0xFFFFFFFF
|
||||
)
|
||||
|
||||
if received_crc != calculated_crc:
|
||||
raise CRCError(
|
||||
"CRC не совпадает: "
|
||||
f"получено 0x{received_crc:08X}, "
|
||||
f"вычислено 0x{calculated_crc:08X}"
|
||||
)
|
||||
|
||||
payload = packet[
|
||||
HEADER_SIZE:-CRC_SIZE
|
||||
]
|
||||
|
||||
return ParsedPacket(
|
||||
version=version,
|
||||
message_type=message_type,
|
||||
sequence_number=sequence_number,
|
||||
payload=payload,
|
||||
)
|
||||
Reference in New Issue
Block a user