Files
LFP_48V_mBMS_FW/package_multi_fw.py

303 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
KT mBMS 3종 통합 펌웨어 패키지 생성 스크립트 (Unified Multi-FW Packager)
=============================================================================
기능:
- 3종의 mBMS 펌웨어 이미지(V1, V2, V3)를 하나의 통합 .bin 파일로 병합
- 패키지 헤더(256 바이트)에 각 펌웨어의 타겟 HW 구분, 버전, 크기, CRC32,
체크섬(Sum32), 오프셋 메타데이터 패킹
- GUI(LFP_Manager_GL)에서 통합 bin 파일을 읽어 보드 버전 확인 및 자동 다운로드 지원
작성일자: 2026-10-23
"""
import os
import sys
import struct
# ---------------------------------------------------------------------------
# 상 수 정 의
# ---------------------------------------------------------------------------
MULTI_FW_MAGIC = 0x4D4D4657 # 'MMFW' (Multi mBMS FirmWare)
HEADER_VERSION = 0x0001 # 헤더 버전 1.0
TOTAL_HEADER_SIZE = 256 # 전체 헤더 크기 (64B Master + 64B * 3 Entries)
ENTRY_COUNT = 3
HW_TYPE_V1 = 0x11 # STM32F407 + BQ76PL455 + Relay
HW_TYPE_V2 = 0x22 # HC32F460 + BQ76940 + FET
HW_TYPE_V3 = 0x33 # HC32F460 + ADBMS1818 + FET
POLYNOMIAL_CRC32 = 0x04C11DB7
# ---------------------------------------------------------------------------
# CRC32 및 CheckSum 계산기 (GUI의 C# csMultiFwPackage와 100% 동일)
# ---------------------------------------------------------------------------
_crc_table = None
def init_crc_table():
global _crc_table
if _crc_table is not None:
return
_crc_table = []
for i in range(256):
crc = (i << 24) & 0xFFFFFFFF
for _ in range(8):
if crc & 0x80000000:
crc = ((crc << 1) ^ POLYNOMIAL_CRC32) & 0xFFFFFFFF
else:
crc = (crc << 1) & 0xFFFFFFFF
_crc_table.append(crc)
def calculate_crc32(data: bytes) -> int:
init_crc_table()
crc = 0
for b in data:
idx = ((crc >> 24) ^ b) & 0xFF
crc = (((crc << 8) & 0xFFFFFFFF) ^ _crc_table[idx]) & 0xFFFFFFFF
return crc
def calculate_checksum32(data: bytes) -> int:
return sum(data) & 0xFFFFFFFF
# ---------------------------------------------------------------------------
# 패키지 빌더
# ---------------------------------------------------------------------------
def build_multi_fw_package(
v1_bin_path: str,
v2_bin_path: str,
v3_bin_path: str,
output_bin_path: str,
version: tuple = (4, 0, 0, 1),
package_name: str = "mBMS_KT_UNIFIED_FW_V4.0.0.1"
):
print("=" * 70)
print(" KT mBMS 통합 펌웨어 패키지 생성 시작")
print("=" * 70)
# 1. 각 바이너리 파일 로드 (보드 버전 판별 규칙: x1xx -> V1, x2xx -> V2, x3xx -> V3)
inputs = [
{
"hw_type": HW_TYPE_V1,
"hw_name": "HW1:STM32+PL455", # x1xx (V1)
"path": v1_bin_path,
"data": b""
},
{
"hw_type": HW_TYPE_V2,
"hw_name": "HW2:HC32+BQ76940", # x2xx (V2)
"path": v2_bin_path,
"data": b""
},
{
"hw_type": HW_TYPE_V3,
"hw_name": "HW3:HC32+ADBMS18", # x3xx (V3)
"path": v3_bin_path,
"data": b""
}
]
for item in inputs:
if not os.path.exists(item["path"]):
raise FileNotFoundError(f"펌웨어 파일이 존재하지 않습니다: {item['path']}")
with open(item["path"], "rb") as f:
item["data"] = f.read()
item["size"] = len(item["data"])
item["crc32"] = calculate_crc32(item["data"])
item["chksum"] = calculate_checksum32(item["data"])
item["filename"] = os.path.basename(item["path"])
print(f"[*] {item['hw_name']:<18}: {item['filename']} ({item['size']:,} bytes)")
print(f" - CRC32: 0x{item['crc32']:08X}, CheckSum(Sum32): 0x{item['chksum']:08X}")
# 2. 오프셋 계산
current_offset = TOTAL_HEADER_SIZE
for item in inputs:
item["offset"] = current_offset
current_offset += item["size"]
total_package_size = current_offset
print("-" * 70)
print(f"[*] 통합 헤더 크기 : {TOTAL_HEADER_SIZE} bytes")
print(f"[*] 최종 패키지 크기 : {total_package_size:,} bytes")
# 3. 마스터 헤더 (64 바이트) 패킹
# 구조:
# Magic (4B, uint32)
# HeaderVersion (2B, uint16)
# ImageCount (2B, uint16)
# TotalPackageSize (4B, uint32)
# HeaderCrc32 (4B, uint32) - 임시 0
# PackageVer (4B, 4 bytes)
# PackageName (32B, char[32])
# Reserved (12B, 12 bytes)
pkg_ver_bytes = bytes([version[0], version[1], version[2], version[3]])
pkg_name_bytes = package_name.encode("ascii")[:31].ljust(32, b"\x00")
reserved12 = b"\x00" * 12
master_header = struct.pack(
"<IIHHII4s32s12s",
MULTI_FW_MAGIC,
HEADER_VERSION,
ENTRY_COUNT,
0, # TotalPackageSize (포맷 통일을 위해 아래서 정합)
0, # HeaderCrc32 (임시)
total_package_size, # TotalPackageSize
pkg_ver_bytes,
pkg_name_bytes,
reserved12
)
# C# 구조체:
# UInt32 Magic; (4B)
# UInt16 HeaderVersion; (2B)
# UInt16 ImageCount; (2B)
# UInt32 TotalPackageSize; (4B)
# UInt32 HeaderCrc32; (4B)
# byte[4] PackageVer; (4B)
# byte[32] PackageName; (32B)
# byte[12] Reserved; (12B)
master_header = struct.pack(
"<IHHII4s32s12s",
MULTI_FW_MAGIC,
HEADER_VERSION,
ENTRY_COUNT,
total_package_size,
0, # HeaderCrc32 (추후 계산)
pkg_ver_bytes,
pkg_name_bytes,
reserved12
)
assert len(master_header) == 64, f"Master header size mismatch: {len(master_header)}"
# 4. 서브 펌웨어 엔트리 3개 (각 64 바이트, 총 192 바이트) 패킹
# C# 구조체:
# byte HwType; (1B)
# byte HwSubType; (1B)
# UInt16 EntryFlags; (2B)
# byte[16] HwName; (16B)
# byte[4] FwVer; (4B)
# UInt32 FileOffset; (4B)
# UInt32 ImageSize; (4B)
# UInt32 Crc32; (4B)
# UInt32 CheckSum; (4B)
# byte[20] OrigFileName; (20B)
# byte[4] Reserved; (4B)
entries_bytes = bytearray()
for item in inputs:
hw_name_bytes = item["hw_name"].encode("ascii")[:15].ljust(16, b"\x00")
fn_bytes = item["filename"].encode("ascii")[:19].ljust(20, b"\x00")
entry = struct.pack(
"<BBH16s4sIIII20s4s",
item["hw_type"],
0, # HwSubType
0x0001, # EntryFlags (Active)
hw_name_bytes,
pkg_ver_bytes, # FwVer
item["offset"],
item["size"],
item["crc32"],
item["chksum"],
fn_bytes,
b"\x00" * 4 # Reserved
)
assert len(entry) == 64, f"Entry size mismatch: {len(entry)}"
entries_bytes.extend(entry)
# 5. 헤더 결합 및 헤더 CRC32 계산
header_block = bytearray(master_header + entries_bytes)
assert len(header_block) == TOTAL_HEADER_SIZE, f"Total header size mismatch: {len(header_block)}"
header_crc = calculate_crc32(bytes(header_block))
# HeaderCrc32 필드는 offset 12 (4 + 2 + 2 + 4)
struct.pack_into("<I", header_block, 12, header_crc)
# 6. 최종 파일 쓰기 (헤더 + V1 데이터 + V2 데이터 + V3 데이터)
os.makedirs(os.path.dirname(os.path.abspath(output_bin_path)), exist_ok=True)
with open(output_bin_path, "wb") as f_out:
f_out.write(header_block)
for item in inputs:
f_out.write(item["data"])
print("-" * 70)
print(f"[SUCCESS] 통합 bin 파일 생성 완료:")
print(f" ▶ 파일 경로: {output_bin_path}")
print(f" ▶ 파일 크기: {os.path.getsize(output_bin_path):,} bytes")
print(f" ▶ 헤더 CRC : 0x{header_crc:08X}")
print("=" * 70)
# 7. 자체 검증 테스트 (Self-Verification)
verify_package(output_bin_path)
def verify_package(package_path: str):
print("\n[검증 테스트] 생성된 통합 bin 파일 검증 중...")
with open(package_path, "rb") as f:
data = f.read()
assert len(data) >= TOTAL_HEADER_SIZE, "파일 크기가 헤더보다 작습니다."
magic, hdr_ver, img_cnt, total_sz, hdr_crc = struct.unpack_from("<IHHII", data, 0)
assert magic == MULTI_FW_MAGIC, f"Magic mismatch: 0x{magic:08X} != 0x{MULTI_FW_MAGIC:08X}"
assert hdr_ver == HEADER_VERSION, f"Header version mismatch: {hdr_ver}"
assert img_cnt == ENTRY_COUNT, f"Image count mismatch: {img_cnt}"
assert total_sz == len(data), f"Total size mismatch: {total_sz} != {len(data)}"
for i in range(img_cnt):
offset = 64 + (i * 64)
hw_type, hw_sub, flags, hw_name, fw_ver, f_offset, img_sz, crc, chksum, fn, _ = struct.unpack_from(
"<BBH16s4sIIII20s4s", data, offset
)
hw_name_str = hw_name.decode("ascii").rstrip("\x00")
fn_str = fn.decode("ascii").rstrip("\x00")
ver_str = f"V{fw_ver[0]}.{fw_ver[1]}.{fw_ver[2]}.{fw_ver[3]}"
fw_payload = data[f_offset : f_offset + img_sz]
calc_crc = calculate_crc32(fw_payload)
calc_sum = calculate_checksum32(fw_payload)
assert calc_crc == crc, f"CRC32 mismatch on entry {i}: 0x{calc_crc:08X} != 0x{crc:08X}"
assert calc_sum == chksum, f"CheckSum mismatch on entry {i}: 0x{calc_sum:08X} != 0x{chksum:08X}"
print(f" [OK] 엔트리 #{i+1}: HW 0x{hw_type:02X} ({hw_name_str}) - {ver_str}, {img_sz:,}B, CRC 검증 통과")
print("[SUCCESS] 패키지 무결성 및 바이너리 검증 100% 완료!\n")
# ---------------------------------------------------------------------------
# 메인 진입점
# ---------------------------------------------------------------------------
if __name__ == "__main__":
base_dir = os.path.dirname(os.path.abspath(__file__))
# 기본 경로 설정 (V1, V2, V3 모두 V4001 적용)
v1_path = os.path.join(base_dir, "mBMS_APP_V3117", "USER", "FLASH", "mBMS_APP_V4001.bin")
if not os.path.exists(v1_path):
v1_path = os.path.join(base_dir, "mBMS_APP_V3117", "USER", "FLASH", "mBMS_APP_V3117.bin")
v2_path = os.path.join(base_dir, "APP_R3221", "App", "MDK", "output", "mBMS2_KT_APP_V4001.bin")
if not os.path.exists(v2_path):
v2_path = os.path.join(base_dir, "APP_R3221", "App", "MDK", "output", "mBMS2_KT_APP_V3221.bin")
v3_path = os.path.join(base_dir, "APP_R3323", "App", "MDK", "output", "mBMS3_KT_APP_V4001.bin")
# 출력 경로: 루트 디렉토리 (mBMS_KT_V4001.bin)
root_out_path = os.path.join(base_dir, "mBMS_KT_V4001.bin")
build_multi_fw_package(
v1_bin_path=v1_path,
v2_bin_path=v2_path,
v3_bin_path=v3_path,
output_bin_path=root_out_path,
version=(4, 0, 0, 1),
package_name="mBMS_KT_UNIFIED_FW_V4.0.0.1"
)
import shutil
# GUI 디렉토리가 남아있을 경우에만 복사
gui_rel_bin = os.path.join(base_dir, "gui", "LFP_Manager_GL(V3.3.0.16)_AG", "LFP_Manager", "bin", "Release", "mBMS_KT_V4001.bin")
if os.path.exists(os.path.dirname(gui_rel_bin)):
shutil.copyfile(root_out_path, gui_rel_bin)
print(f"[*] GUI Release 디렉토리 복사 완료: {gui_rel_bin}")
debug_path = os.path.join(base_dir, "gui", "LFP_Manager_GL(V3.3.0.16)_AG", "LFP_Manager", "bin", "Debug", "mBMS_KT_V4001.bin")
if os.path.exists(os.path.dirname(debug_path)):
shutil.copyfile(root_out_path, debug_path)
print(f"[*] GUI Debug 디렉토리 복사 완료: {debug_path}")