86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
# Клиентское представление образа и подготовки передачи прошивки. Парсинг и проверки образа
|
|
# отделены от физического программатора; этап передачи использует уже проверенные данные и
|
|
# метаданные.
|
|
|
|
"""@file firmware.py
|
|
@brief Проверка образов и переносимая машина блочной передачи прошивки.
|
|
|
|
Реализует загрузку и валидацию образов прошивки из различных форматов файлов,
|
|
проверку целостности данных, и логику передачи образов на устройство.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from firmware_image.core import FirmwareImageError, contiguous_image, image_digest
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FirmwareImage:
|
|
"""@brief Загруженный образ прошивки с метаданными.
|
|
|
|
@param path Путь к исходному файлу образа
|
|
@param data Бинарные данные образа
|
|
@param base_address Адрес начала загрузки (для Intel HEX формата)
|
|
@param version Номер версии прошивки
|
|
"""
|
|
path: Path
|
|
data: bytes
|
|
base_address: int = 0
|
|
version: int = 0
|
|
|
|
@property
|
|
def crc32(self) -> int:
|
|
return int(image_digest(self.data)[0], 16)
|
|
|
|
@property
|
|
def sha256(self) -> str:
|
|
return image_digest(self.data)[1].lower()
|
|
|
|
@classmethod
|
|
def load(cls, path: str | Path, maximum_size: int = 2 * 1024 * 1024) -> "FirmwareImage":
|
|
file_path = Path(path)
|
|
raw = file_path.read_bytes()
|
|
if file_path.suffix.lower() == ".hex":
|
|
data, base = _parse_intel_hex(raw, maximum_size)
|
|
elif file_path.suffix.lower() == ".bin":
|
|
data, base = raw, 0
|
|
else:
|
|
raise FirmwareImageError("Поддерживаются только .bin и Intel HEX")
|
|
if not data:
|
|
raise FirmwareImageError("Образ пуст")
|
|
if len(data) > maximum_size:
|
|
raise FirmwareImageError(f"Размер образа превышает {maximum_size} байт")
|
|
return cls(file_path, data, base)
|
|
|
|
|
|
def _parse_intel_hex(text: str, maximum_size=2 * 1024 * 1024) -> tuple[bytes, int]:
|
|
return contiguous_image(text, maximum_size)
|
|
|
|
|
|
@dataclass
|
|
class FirmwareTransfer:
|
|
image: FirmwareImage
|
|
block_size: int = 480
|
|
offset: int = 0
|
|
|
|
def next_block(self) -> tuple[int, bytes] | None:
|
|
if self.offset >= len(self.image.data):
|
|
return None
|
|
offset = self.offset
|
|
return offset, self.image.data[offset : offset + self.block_size]
|
|
|
|
def acknowledge(self, next_offset: int) -> None:
|
|
current = self.next_block()
|
|
if current is None or next_offset != current[0] + len(current[1]):
|
|
raise FirmwareImageError("МК подтвердил неожиданный offset")
|
|
self.offset = next_offset
|
|
|
|
@property
|
|
def percent(self) -> int:
|
|
return int(self.offset * 100 / len(self.image.data))
|