Windows EXE 업로더 대신 Python으로 직접 만들자. Linux에서도 쓸 수 있고 수정도 가능.


의존성

pip install python-can

전체 코드

#!/usr/bin/env python3
import can
import struct
import time
import sys

class BMSUploader:
    RX_ID = 0x5FF  # PC → BMS
    TX_ID = 0x5FE  # BMS → PC
    
    def __init__(self, channel='can0', bustype='socketcan'):
        self.bus = can.interface.Bus(channel=channel, bustype=bustype)
    
    def send(self, data):
        msg = can.Message(arbitration_id=self.RX_ID, data=data, is_extended_id=False)
        self.bus.send(msg)
    
    def recv(self, timeout=5):
        msg = self.bus.recv(timeout)
        if msg and msg.arbitration_id == self.TX_ID:
            return list(msg.data)
        return None
    
    def calc_key(self, fw_chk, fw_date):
        key = fw_chk[0] ^ fw_chk[1]
        key = (key << 8) | (fw_chk[2] ^ fw_chk[3])
        key ^= (fw_date[0] << 16)
        key ^= (fw_date[1] << 8)
        key ^= fw_date[2]
        key = (key * 0x1234 + 0x5678) & 0xFFFFFFFF
        key = (key >> 16) ^ (key & 0xFFFF)
        return key
    
    def connect(self):
        print("Connecting...")
        self.send([0x30, 0, 0, 0, 0, 0, 0, 0])
        
        resp = self.recv()
        if not resp or resp[0] != 0x40:
            raise Exception("Connection failed")
        
        fw_chk = resp[1:5]
        fw_date = resp[5:8]
        print(f"FwChk: {bytes(fw_chk).hex()}, Date: 20{fw_date[0]:02d}-{fw_date[1]:02d}-{fw_date[2]:02d}")
        
        key = self.calc_key(fw_chk, fw_date)
        print(f"Key: {key:04X}")
        
        self.send([0x31, key & 0xFF, (key >> 8) & 0xFF, 0, 0, 0, 0, 0])
        
        resp = self.recv()
        if not resp or resp[0] != 0x41:
            raise Exception("Auth failed")
        
        print("Connected!")
    
    def upload(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        
        size = len(data)
        print(f"Firmware size: {size} bytes")
        
        # Size 전송
        self.send([0x32, size & 0xFF, (size >> 8) & 0xFF, 
                   (size >> 16) & 0xFF, (size >> 24) & 0xFF, 0, 0, 0])
        self.recv()
        
        # 데이터 전송
        seq = 0
        for i in range(0, len(data), 7):
            chunk = list(data[i:i+7])
            while len(chunk) < 7:
                chunk.append(0xFF)
            
            self.send([seq & 0xFF] + chunk)
            seq += 1
            
            if seq % 256 == 0:
                resp = self.recv(timeout=10)
                if resp and resp[0] == 0x43:
                    print(f"Page {seq // 256} done")
        
        # Verify
        crc = self.calc_crc(data)
        self.send([0x35, crc & 0xFF, (crc >> 8) & 0xFF,
                   (crc >> 16) & 0xFF, (crc >> 24) & 0xFF, 0, 0, 0])
        
        resp = self.recv(timeout=30)
        if not resp or resp[0] != 0x45:
            raise Exception("Verify failed")
        
        print("Verified!")
        
        # Jump
        self.send([0x36, 0, 0, 0, 0, 0, 0, 0])
        print("Done! Device rebooting...")
    
    def calc_crc(self, data):
        # STM32 CRC-32 (polynomial: 0x04C11DB7)
        crc = 0xFFFFFFFF
        for i in range(0, len(data), 4):
            word = struct.unpack('<I', data[i:i+4].ljust(4, b'\xFF'))[0]
            crc ^= word
            for _ in range(32):
                if crc & 0x80000000:
                    crc = (crc << 1) ^ 0x04C11DB7
                else:
                    crc <<= 1
                crc &= 0xFFFFFFFF
        return crc

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <firmware.bin>")
        sys.exit(1)
    
    uploader = BMSUploader()
    uploader.connect()
    uploader.upload(sys.argv[1])

사용법

# CAN 인터페이스 설정
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

# 업로드
python3 uploader.py firmware.bin

다음 글에서 실제 장비 테스트.

#33 - 실제 장비 테스트