mirror of
https://github.com/andreili/katapult.git
synced 2025-08-24 03:44:06 +02:00
flash_can: protocol updates
Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
beb06d600d
commit
5e7d632c4f
@ -50,13 +50,7 @@ BOOTLOADER_CMDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ACK_SUCCESS = 0xa0
|
ACK_SUCCESS = 0xa0
|
||||||
NACK_RESPONSES = {
|
NACK = 0xf1
|
||||||
0xf0: "Invalid Command",
|
|
||||||
0xf1: "CRC Mismatch",
|
|
||||||
0xf2: "Invalid Payload",
|
|
||||||
0xf3: "Invalid Trailer",
|
|
||||||
0xf4: "Invalid Payload Length"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Klipper Admin Defs (for jumping to bootloader)
|
# Klipper Admin Defs (for jumping to bootloader)
|
||||||
KLIPPER_ADMIN_ID = 0x3f0
|
KLIPPER_ADMIN_ID = 0x3f0
|
||||||
@ -87,20 +81,26 @@ class CanFlasher:
|
|||||||
self.file_size = 0
|
self.file_size = 0
|
||||||
self.block_size = 64
|
self.block_size = 64
|
||||||
self.block_count = 0
|
self.block_count = 0
|
||||||
|
self.app_start_addr = 0
|
||||||
|
|
||||||
async def connect_btl(self):
|
async def connect_btl(self):
|
||||||
output_line("Attempting to connect to bootloader")
|
output_line("Attempting to connect to bootloader")
|
||||||
ret = await self.send_command('CONNECT')
|
ret = await self.send_command('CONNECT')
|
||||||
self.block_size, = struct.unpack(">H", ret[2:])
|
ver_bytes, start_addr, self.block_size = struct.unpack("<4sII", ret)
|
||||||
|
self.app_start_addr = start_addr
|
||||||
|
proto_version = ".".join([str(v) for v in reversed(ver_bytes[:3])])
|
||||||
if self.block_size not in [64, 128, 256, 512]:
|
if self.block_size not in [64, 128, 256, 512]:
|
||||||
raise FlashCanError("Invalid Block Size: %d" % (self.block_size,))
|
raise FlashCanError("Invalid Block Size: %d" % (self.block_size,))
|
||||||
output_line("Connected, Block Size: %d bytes" % (self.block_size,))
|
output_line(
|
||||||
|
f"CanBoot Connected\nProtocol Version: {proto_version}\n"
|
||||||
|
f"Block Size: {self.block_size} bytes\n"
|
||||||
|
f"Application Start: 0x{self.app_start_addr:4X}"
|
||||||
|
)
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
cmdname: str,
|
cmdname: str,
|
||||||
payload: bytes = b"",
|
payload: bytes = b"",
|
||||||
response_length: int = 12,
|
|
||||||
tries: int = 5
|
tries: int = 5
|
||||||
) -> bytearray:
|
) -> bytearray:
|
||||||
word_cnt = (len(payload) // 4) & 0xFF
|
word_cnt = (len(payload) // 4) & 0xFF
|
||||||
@ -110,31 +110,37 @@ class CanFlasher:
|
|||||||
out_cmd.append(word_cnt)
|
out_cmd.append(word_cnt)
|
||||||
if payload:
|
if payload:
|
||||||
out_cmd.extend(payload)
|
out_cmd.extend(payload)
|
||||||
|
crc = crc16_ccitt(out_cmd[2:])
|
||||||
|
out_cmd.extend(struct.pack("<H", crc))
|
||||||
out_cmd.extend(CMD_TRAILER)
|
out_cmd.extend(CMD_TRAILER)
|
||||||
crc = crc16_ccitt(out_cmd)
|
|
||||||
out_cmd.extend(struct.pack(">H", crc))
|
|
||||||
while tries:
|
while tries:
|
||||||
|
data = bytearray()
|
||||||
|
recd_len = 0
|
||||||
try:
|
try:
|
||||||
ret = await self.node.write_with_response(
|
self.node.write(out_cmd)
|
||||||
out_cmd, response_length
|
read_done = False
|
||||||
)
|
while not read_done:
|
||||||
data = bytearray(ret)
|
ret = await self.node.readuntil()
|
||||||
|
data.extend(ret)
|
||||||
|
while len(data) > 7:
|
||||||
|
if data[:2] != CMD_HEADER:
|
||||||
|
data = data[1:]
|
||||||
|
continue
|
||||||
|
recd_len = data[3] * 4
|
||||||
|
read_done = len(data) == recd_len + 8
|
||||||
|
break
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Can Read Error")
|
logging.exception("Can Read Error")
|
||||||
else:
|
else:
|
||||||
header = data[:2]
|
trailer = data[-2:]
|
||||||
trailer = data[-4:-2]
|
recd_crc, = struct.unpack("<H", data[-4:-2])
|
||||||
recd_crc, = struct.unpack(">H", data[-2:])
|
calc_crc = crc16_ccitt(data[2:-4])
|
||||||
calc_crc = crc16_ccitt(data[:-2])
|
|
||||||
recd_ack = data[2]
|
recd_ack = data[2]
|
||||||
recd_len = data[3] * 4
|
cmd_response = 0
|
||||||
cmd_response = data[4]
|
if recd_len:
|
||||||
if header != CMD_HEADER:
|
cmd_response, = struct.unpack("<I", data[4:8])
|
||||||
logging.info(
|
if trailer != CMD_TRAILER:
|
||||||
f"Command '{cmdname}': Invalid Header Received "
|
|
||||||
f"0x{header.hex()}"
|
|
||||||
)
|
|
||||||
elif trailer != CMD_TRAILER:
|
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Command '{cmdname}': Invalid Trailer Received "
|
f"Command '{cmdname}': Invalid Trailer Received "
|
||||||
f"0x{trailer.hex()}"
|
f"0x{trailer.hex()}"
|
||||||
@ -145,26 +151,21 @@ class CanFlasher:
|
|||||||
f"{calc_crc}, received {recd_crc}"
|
f"{calc_crc}, received {recd_crc}"
|
||||||
)
|
)
|
||||||
elif recd_ack != ACK_SUCCESS:
|
elif recd_ack != ACK_SUCCESS:
|
||||||
nack_string = NACK_RESPONSES.get(recd_ack, "Unknown Error")
|
logging.info(f"Command '{cmdname}': Received NACK")
|
||||||
msg = f"Command '{cmdname}': Received NACK, {nack_string}"
|
|
||||||
if recd_ack == 0xf0:
|
|
||||||
msg += f", 0x{cmd_response:2x}"
|
|
||||||
elif recd_ack == 0xf1:
|
|
||||||
mcu_crc, = struct.unpack(">H", data[6:8])
|
|
||||||
msg += f", sent crc: {crc}, mcu_crc: {mcu_crc}"
|
|
||||||
logging.info(msg)
|
|
||||||
elif cmd_response != cmd:
|
elif cmd_response != cmd:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Command '{cmdname}': Acknowledged wrong command, "
|
f"Command '{cmdname}': Acknowledged wrong command, "
|
||||||
f"expected: {cmd:2x}, received: {cmd_response:2x}"
|
f"expected: {cmd:2x}, received: {cmd_response:2x}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Validation passed, return payload
|
# Validation passed, return payload sans command
|
||||||
return data[4:recd_len + 4]
|
if recd_len <= 4:
|
||||||
|
return bytearray()
|
||||||
|
return data[8:recd_len + 4]
|
||||||
tries -= 1
|
tries -= 1
|
||||||
# clear the read buffer
|
# clear the read buffer
|
||||||
try:
|
try:
|
||||||
await self.node.read(256, timeout=.1)
|
await self.node.read(1024, timeout=.1)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
await asyncio.sleep(.1)
|
await asyncio.sleep(.1)
|
||||||
@ -179,6 +180,7 @@ class CanFlasher:
|
|||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
self.file_size = f.tell()
|
self.file_size = f.tell()
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
|
flash_address = self.app_start_addr
|
||||||
while True:
|
while True:
|
||||||
buf = f.read(self.block_size)
|
buf = f.read(self.block_size)
|
||||||
if not buf:
|
if not buf:
|
||||||
@ -186,21 +188,22 @@ class CanFlasher:
|
|||||||
if len(buf) < self.block_size:
|
if len(buf) < self.block_size:
|
||||||
buf += b"\xFF" * (self.block_size - len(buf))
|
buf += b"\xFF" * (self.block_size - len(buf))
|
||||||
self.fw_sha.update(buf)
|
self.fw_sha.update(buf)
|
||||||
prefix = struct.pack(">I", self.block_count)
|
prefix = struct.pack("<I", flash_address)
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
resp = await self.send_command('SEND_BLOCK', prefix + buf)
|
resp = await self.send_command('SEND_BLOCK', prefix + buf)
|
||||||
recd_block, = struct.unpack(">H", resp[2:])
|
recd_addr, = struct.unpack("<I", resp)
|
||||||
if recd_block == self.block_count:
|
if recd_addr == flash_address:
|
||||||
break
|
break
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Block write mismatch: expected: {self.block_count}, "
|
f"Block write mismatch: expected: {flash_address:4X}, "
|
||||||
f"received: {recd_block}"
|
f"received: {recd_addr:4X}"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(.1)
|
await asyncio.sleep(.1)
|
||||||
else:
|
else:
|
||||||
raise FlashCanError(
|
raise FlashCanError(
|
||||||
f"Flash write failed, block {self.block_count}"
|
f"Flash write failed, block address 0x{recd_addr:4X}"
|
||||||
)
|
)
|
||||||
|
flash_address += self.block_size
|
||||||
self.block_count += 1
|
self.block_count += 1
|
||||||
uploaded = self.block_count * self.block_size
|
uploaded = self.block_count * self.block_size
|
||||||
pct = int(uploaded / float(self.file_size) * 100 + .5)
|
pct = int(uploaded / float(self.file_size) * 100 + .5)
|
||||||
@ -208,7 +211,7 @@ class CanFlasher:
|
|||||||
last_percent += 2.
|
last_percent += 2.
|
||||||
output("#")
|
output("#")
|
||||||
resp = await self.send_command('SEND_EOF')
|
resp = await self.send_command('SEND_EOF')
|
||||||
page_count, = struct.unpack(">H", resp[2:])
|
page_count, = struct.unpack("<I", resp)
|
||||||
output_line("]\n\nWrite complete: %d pages" % (page_count))
|
output_line("]\n\nWrite complete: %d pages" % (page_count))
|
||||||
|
|
||||||
async def verify_file(self):
|
async def verify_file(self):
|
||||||
@ -217,15 +220,16 @@ class CanFlasher:
|
|||||||
output("\n[")
|
output("\n[")
|
||||||
ver_sha = hashlib.sha1()
|
ver_sha = hashlib.sha1()
|
||||||
for i in range(self.block_count):
|
for i in range(self.block_count):
|
||||||
|
flash_address = i * self.block_size + self.app_start_addr
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
payload = struct.pack(">I", i)
|
payload = struct.pack("<I", flash_address)
|
||||||
resp = await self.send_command("REQUEST_BLOCK", payload, 76)
|
resp = await self.send_command("REQUEST_BLOCK", payload)
|
||||||
recd_block, = struct.unpack(">H", resp[2:4])
|
recd_addr, = struct.unpack("<I", resp[:4])
|
||||||
if recd_block == i:
|
if recd_addr == flash_address:
|
||||||
break
|
break
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Block read mismatch: expected: {i}, "
|
f"Block read mismatch: expected: 0x{flash_address:4X}, "
|
||||||
f"received: {recd_block}"
|
f"received: 0x{recd_addr}"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(.1)
|
await asyncio.sleep(.1)
|
||||||
else:
|
else:
|
||||||
@ -263,6 +267,11 @@ class CanNode:
|
|||||||
) -> bytes:
|
) -> bytes:
|
||||||
return await asyncio.wait_for(self._reader.readexactly(n), timeout)
|
return await asyncio.wait_for(self._reader.readexactly(n), timeout)
|
||||||
|
|
||||||
|
async def readuntil(
|
||||||
|
self, sep: bytes = b"\x03", timeout: Optional[float] = 2
|
||||||
|
) -> bytes:
|
||||||
|
return await asyncio.wait_for(self._reader.readuntil(sep), timeout)
|
||||||
|
|
||||||
def write(self, payload: Union[bytes, bytearray]) -> None:
|
def write(self, payload: Union[bytes, bytearray]) -> None:
|
||||||
if isinstance(payload, bytearray):
|
if isinstance(payload, bytearray):
|
||||||
payload = bytes(payload)
|
payload = bytes(payload)
|
||||||
@ -491,8 +500,14 @@ def main():
|
|||||||
"-q", "--query", action="store_true",
|
"-q", "--query", action="store_true",
|
||||||
help="Query Bootloader Device IDs"
|
help="Query Bootloader Device IDs"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose", action="store_true",
|
||||||
|
help="Enable verbose responses"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if not args.verbose:
|
||||||
|
logging.getLogger().setLevel(logging.CRITICAL)
|
||||||
intf = args.interface
|
intf = args.interface
|
||||||
fpath = pathlib.Path(args.firmware).expanduser().resolve()
|
fpath = pathlib.Path(args.firmware).expanduser().resolve()
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user