diff --git a/bootloader.py b/bootloader.py index 8e96ced..098a47c 100644 --- a/bootloader.py +++ b/bootloader.py @@ -19,9 +19,11 @@ # CAN command message IDs. ERASE_SECTOR_CAN_ID_LOWBITS = 0x4 ERASE_SECTOR_COMPLETE_CAN_ID_LOWBITS = 0x5 -PROGRAM_CAN_ID_LOWBITS = 0x6 +NAK_ID = 0x6 VERIFY_CAN_ID_LOWBITS = 0x7 APP_VALIDITY_CAN_ID_LOWBITS = 0x8 +APP_START_PROGRAM_ID = 0xA # this is BOARD LOW BIT + 0xA -- we have until boot_id_range_start + 0xFFFFFF +FINAL_PROGRAM_ID = 0xFFFFFF # Verify response options. # Keep in sync with: @@ -32,7 +34,7 @@ # The minimum amount of data the microcontroller can program at a time. MIN_PROG_SIZE_BYTES = 32 - +NAK_TIMEOUT = 0.1 class Bootloader: bus: can.Bus @@ -54,7 +56,7 @@ def __init__( self.board: boards.Board = board self.timeout: int = timeout self.ui_callback: Callable = ui_callback - + self.dropped_packets = set() def goto_bootloader(self) -> bool: """ Pushes all boards to bootloader mode. @@ -182,30 +184,89 @@ def program(self) -> None: Program the binary into flash. There is no CAN handshake here to reduce latency during programming. Also, the bootloader will verify the app's code is valid by computing a checksum. - """ - for i, address in enumerate( - range(self.ih.minaddr(), self.ih.minaddr() + self.size_bytes(), 8) - ): - if self.ui_callback and i % 128 == 0: - self.ui_callback("Programming data", self.size_bytes(), i * 8) - data = [self.ih[address + i] for i in range(0, 8)] + self.__transmission_address = self.ih.minaddr() + self.__transmission_id = self.board.boot_id_range_start | APP_START_PROGRAM_ID + self.__index = 0 + + def _validator(msg: can.Message): + # Ignore all known status messages + print(f"Can ID that come through {msg.arbitration_id}\n") + if msg.arbitration_id == (self.board.boot_id_range_start | NAK_ID): + dropped_packet_address = int.from_bytes(msg.data, "little") + self.__transmission_address = (dropped_packet_address // 8) * 8 + self.__index = dropped_packet_address % 8 + self.__transmission_id = self.board.boot_id_range_start | APP_START_PROGRAM_ID + return False + else: + return True + + while self.__transmission_address < self.ih.minaddr() + self.size_bytes(): + + if self.ui_callback and self.__index % 128 == 0: + self.ui_callback("Programming data", self.size_bytes(), self.__index * 8) + self.__index += 1 + + data = [self.ih[self.__transmission_address + i] for i in range(0, 8)] success = False - while not success: - try: - self.bus.send( - can.Message( - arbitration_id=self.board.boot_id_range_start - | PROGRAM_CAN_ID_LOWBITS, - data=data, - is_extended_id=True, + if(self.__transmission_id <= (self.board.boot_id_range_start | FINAL_PROGRAM_ID)): + while not success: + try: + self.bus.send( + can.Message( + arbitration_id=self.__transmission_id, + data=data, + is_extended_id=True, + ) ) - ) - success = True - except can.interfaces.vector.exceptions.VectorOperationError: - pass + success = True + except can.interfaces.vector.exceptions.VectorOperationError: + pass + + nak = ~self._await_can_msg(_validator, timeout=NAK_TIMEOUT) + + if nak == False: + print(f"WE LIT!!!!! PACKET {self.__transmission_address} recieved\n") + self.__transmission_address += 8 + elif nak == True: + print(f"PACKET DROPPED, Re-transmission starting at address {self.__transmission_address}\n") + print(f"PACKET DROPPED, Re-transmission starting at index {self.__index}\n") + else: + print("TIMEOUT\n") + + + # for i, address in enumerate( + # range(self.ih.minaddr(), self.ih.minaddr() + self.size_bytes(), 8) + # ): + # self.__transmission_address = address #use an internal pointer + + # if self.ui_callback and i % 128 == 0: + # self.ui_callback("Programming data", self.size_bytes(), i * 8) + # data = [self.ih[address + i] for i in range(0, 8)] + + # success = False + # if(self.__transmission_id <= (self.board.boot_id_range_start | FINAL_PROGRAM_ID)): + # while not success: + # try: + # self.bus.send( + # can.Message( + # arbitration_id=self.__transmission_id, + # data=data, + # is_extended_id=True, + # ) + # ) + # success = True + # except can.interfaces.vector.exceptions.VectorOperationError: + # pass + # else: + # print(f"RAN OUT OF BOARD IDS, current can ID {self.__transmission_id}") + + + # Empirically, this tiny delay between messages seems to improve reliability. + time.sleep(0.0005) + if self.ui_callback: self.ui_callback("Programming data", self.size_bytes(), self.size_bytes()) @@ -251,7 +312,6 @@ def update(self) -> None: Run the update procedure for this bootloader. """ - def _intersect(a_min, a_max, b_min, b_max): """1-D intersection to check if an app's hex and a flash sector share any addresses.""" return a_max >= b_min and b_max >= a_min @@ -368,4 +428,4 @@ def size_bytes(self) -> int: return int( math.ceil((self.ih.maxaddr() - self.ih.minaddr()) / MIN_PROG_SIZE_BYTES) * MIN_PROG_SIZE_BYTES - ) + ) \ No newline at end of file