Index: scripts/update_package_script/update_package.py =================================================================== diff -u -r988824ec54f3d9d4ff92d60d2be09fe8ad09ee29 -r239e4187b87deffb844cf29a350a980252cba042 --- scripts/update_package_script/update_package.py (.../update_package.py) (revision 988824ec54f3d9d4ff92d60d2be09fe8ad09ee29) +++ scripts/update_package_script/update_package.py (.../update_package.py) (revision 239e4187b87deffb844cf29a350a980252cba042) @@ -1,7 +1,9 @@ import os import math -from scripts.update_package_script.utilities import Utilities, SWUpdateCommands, CanCommStatus, UpdateStacks, UpdateStacksTargets +import time +import threading +from scripts.update_package_script.utilities import Utilities, SWUpdateCommands, CanCommStatus, UpdateStacks, UpdateStacksDestinations class SoftwareUpdateScript: """ @@ -10,8 +12,9 @@ The class runs the firmware and FPGA updates """ _SW_UPDATE_PAYLOAD_BYTES = 256 + _BROADCAST_MESSAGE_INTERVAL_S = 0.2 _SW_UPDATE_STACK = 'stack_name' - _SW_UPDATE_TARGET = 'stack_target' + _SW_UPDATE_DEST = 'stack_target' _BINARY_FILE_SIZE = 'file_size' _WRITTEN_BYTE_COUNT = 'byte_count' _WRITE_COUNTER = 'write_counter' @@ -22,6 +25,8 @@ """ self._sw_update_status = dict() + self._thread = None + self._thread_event = None self._utilities = Utilities() def _reset_variables(self): @@ -31,7 +36,7 @@ @return none """ self._sw_update_status[self._SW_UPDATE_STACK] = 0 - self._sw_update_status[self._SW_UPDATE_TARGET] = 0 + self._sw_update_status[self._SW_UPDATE_DEST] = 0 self._sw_update_status[self._BINARY_FILE_SIZE] = 0 self._sw_update_status[self._WRITTEN_BYTE_COUNT] = 0 self._sw_update_status[self._WRITE_COUNTER] = 0 @@ -54,11 +59,15 @@ # TODO how to read from a manifest file. Right now it is passed directly # TODO if stack to update and stack target are not provided, then just read through the manifest files if stack_to_update is not None and destination is not None: + print("I", stack_to_update, int(stack_to_update)) stack_to_update = stack_to_update.upper() destination = destination.upper() - if stack_to_update in UpdateStacks.__members__: self._sw_update_status[self._SW_UPDATE_STACK] = UpdateStacks[stack_to_update].value - if destination in UpdateStacksTargets.__members__: self._sw_update_status[self._SW_UPDATE_TARGET] = UpdateStacksTargets[destination].value + self._sw_update_status[self._SW_UPDATE_STACK] = UpdateStacks(int(stack_to_update)).value + self._sw_update_status[self._SW_UPDATE_DEST] = UpdateStacksDestinations(int(destination)).value + #if stack_to_update in UpdateStacks.__members__: self._sw_update_status[self._SW_UPDATE_STACK] = UpdateStacks(stack_to_update).value + #if destination in UpdateStacksDestinations.__members__: self._sw_update_status[self._SW_UPDATE_DEST] = UpdateStacksDestinations(destination).value + # TODo manifest #self._sw_update_status[self._SW_UPDATE_STACK] = stack_to_update #self._sw_update_status[self._SW_UPDATE_TARGET] = stack_target @@ -69,7 +78,7 @@ if send_ack_status == CanCommStatus.CAN_COMM_NOT_STARTED.value: self._utilities.send_command_msg(SWUpdateCommands.SW_UPDATE_START.value, self._sw_update_status[self._SW_UPDATE_STACK], - self._sw_update_status[self._SW_UPDATE_TARGET]) + self._sw_update_status[self._SW_UPDATE_DEST]) send_ack_status = self._utilities.get_msg_ack_nack_status(self._utilities.SEND_MSG_ACK_STATUS_KEY_NAME) # Once the CAN communication was ready it means the command went through successfully, and it is ready for the next command. if send_ack_status == CanCommStatus.CAN_COMM_READY.value: status = True @@ -88,7 +97,9 @@ write_counter = self._sw_update_status[self._WRITE_COUNTER] binary_file_size_bytes = self._sw_update_status[self._BINARY_FILE_SIZE] target_stack = self._sw_update_status[self._SW_UPDATE_STACK] - target_stack =UpdateStacksTargets(target_stack).name + target_stack = UpdateStacksDestinations(target_stack).name + destination = self._sw_update_status[self._SW_UPDATE_DEST] + destination = UpdateStacks(destination).name bytes_written = self._sw_update_status[self._WRITTEN_BYTE_COUNT] # Get number of payloads needed to update the binary but since this is for display only, it is scaled down 10 times num_of_payloads_needed = math.ceil(binary_file_size_bytes / (self._SW_UPDATE_PAYLOAD_BYTES * progress_bar_scale)) @@ -105,10 +116,10 @@ text = '#' * write_counter + ' ' * (num_of_payloads_needed - write_counter) # Print the update, end='\r' so the carriage return is returned to the beginning of the current line # add flush=True so the data is not buffered and is immediately printed - print("Updating: {} {} {:.1f}%".format(target_stack, text, percent), end='\r', flush=True) + print("Updating: {} {} {} {:.1f}%".format(destination, target_stack, text, percent), end='\r', flush=True) self._sw_update_status[self._WRITTEN_BYTE_COUNT] = bytes_written self._sw_update_status[self._WRITE_COUNTER] = write_counter - # Once all the bytes of a binary are written, print and empty print to bread from the progress print that was overwriting + # Once all the bytes of a binary are written, print an empty print to bread from the progress print that was overwriting if bytes_written >= binary_file_size_bytes: print() def _process_binary_file(self, file_path: str): @@ -122,7 +133,6 @@ # TODO add a timeout (e.g. 10 minutes that is very long) to make sure we are not stuck # Open the file as read the binary f = open(file_path, 'rb') - while True: line = None update_ack_status = self._utilities.get_msg_ack_nack_status(self._utilities.UPDATE_MSG_ACK_STATUS_KEY_NAME) @@ -139,14 +149,29 @@ # Read line was b'' meaning the binary file was read completely, send the verification command to the bootloader self._utilities.send_command_msg(SWUpdateCommands.SW_UPDATE_VERIFY.value, self._sw_update_status[self._SW_UPDATE_STACK], - self._sw_update_status[self._SW_UPDATE_TARGET]) - print("Leave file") + self._sw_update_status[self._SW_UPDATE_DEST]) break + elif update_ack_status == CanCommStatus.CAN_COMM_TIME_OUT.value: + print("Timeout") # TODO send again? + break if line == b'': print("Leave file 2"); break # TODO remove this line # Close the binary file f.close() + def _handle_broadcast_message_thread(self, start_thread: bool): + + if start_thread: + self._thread_event = threading.Event() + self._thread = threading.Thread(target=self._utilities.send_update_available_broadcast_message, + args=(self._thread_event, self._BROADCAST_MESSAGE_INTERVAL_S)) + self._thread.start() + else: + self._thread_event.set() + self._thread.join() + + ######################## PUBLIC METHOD(S) ############################## + def update_software_packages(self, packages_dir: str, stack_to_update: str = None, stack_target: str = None): """ Publicly accessible method to update the software packages (firmware and FPGA) @@ -162,10 +187,16 @@ # Broadcast I have an update # Wait for the bootloader to come up # Start updating + + # TODO check if the update folder is empty then send the thread + self._handle_broadcast_message_thread(start_thread=True) + for file in os.listdir(packages_dir): - if not file.endswith(".bin"): continue + if not file.endswith(".bin") and not file.endswith(".hex"): continue path = os.path.join(packages_dir, file) if self._prepare_for_sw_update(stack_to_update, stack_target, path): self._process_binary_file(path) + # Done with update binary files, stop sending the broadcast message + self._handle_broadcast_message_thread(start_thread=False) Index: scripts/update_package_script/utilities.py =================================================================== diff -u -r988824ec54f3d9d4ff92d60d2be09fe8ad09ee29 -r239e4187b87deffb844cf29a350a980252cba042 --- scripts/update_package_script/utilities.py (.../utilities.py) (revision 988824ec54f3d9d4ff92d60d2be09fe8ad09ee29) +++ scripts/update_package_script/utilities.py (.../utilities.py) (revision 239e4187b87deffb844cf29a350a980252cba042) @@ -3,9 +3,19 @@ import time import can import struct +from time import sleep from enum import Enum, unique from can.interfaces.socketcan.socketcan import SocketcanBus +class CANMessageListener(can.Listener): + + def __init__(self): + self.raw_can_messages = list() + + def on_message_received(self, msg: can.Message): + self.raw_can_messages.append(msg) + + class SWUpdateEnum(Enum): @classmethod def has_value(cls, value): @@ -35,7 +45,7 @@ NUM_OF_STACKS = 2 @unique -class UpdateStacksTargets(SWUpdateEnum): +class UpdateStacksDestinations(SWUpdateEnum): TARGET_FIRMWARE = 0 TARGET_FPGA = 1 NUM_OF_TARGETS = 2 @@ -87,6 +97,7 @@ _NUM_OF_BYTES_PER_CAN_FRAME = 8 _CAN_INTERFACE = 'can0' _SEND_CMD_MAIL_BOX = 0x601 + _BROADCAST_UPDATE_MAIL_BOX = 0x606 _CAN_MSG_WAIT_FOR_RESP_S = 3.0 _RESP_MSG_ACK = 1 _RESP_MSG_NACK = 0 @@ -112,12 +123,15 @@ self._total_msg_count = 0 self._stack = 0 - self._msg_ack_nack_status[self.SEND_MSG_ACK_STATUS_KEY_NAME] = CanCommStatus.CAN_COMM_NOT_STARTED.value - self._msg_ack_nack_status[self.UPDATE_MSG_ACK_STATUS_KEY_NAME] = CanCommStatus.CAN_COMM_NOT_STARTED.value + self._msg_ack_nack_status[self.SEND_MSG_ACK_STATUS_KEY_NAME] = [CanCommStatus.CAN_COMM_NOT_STARTED.value, 0] + self._msg_ack_nack_status[self.UPDATE_MSG_ACK_STATUS_KEY_NAME] = [CanCommStatus.CAN_COMM_NOT_STARTED.value, 0] self.file_handle = open(os.path.join(os.getcwd(), 'ack_log.log'), 'w') self._prepare_update_mail_boxes(self._update_mail_boxes) + self._can_listener = CANMessageListener() + self._notifier = can.Notifier(self._can_bus, [self._can_listener]) + def get_msg_ack_nack_status(self, msg_type: str): """ Publicly accessible method to get the ack or nack status of a message @@ -126,7 +140,7 @@ @return The status of the provided message type (e.g. ready, failed, ...) """ - return self._msg_ack_nack_status[msg_type] + return self._msg_ack_nack_status[msg_type][0] def clear_msg_ack_nack_status(self, msg_type: str): """ @@ -136,7 +150,7 @@ @return none """ - self._msg_ack_nack_status[msg_type] = CanCommStatus.CAN_COMM_NOT_STARTED.value + self._msg_ack_nack_status[msg_type][0] = CanCommStatus.CAN_COMM_NOT_STARTED.value def send_command_msg(self, cmd: int, stack: int, destination: int): """ @@ -155,6 +169,7 @@ # 4 bytes as message CRC self._stack = stack message_id_count = self._get_current_message_count() + self._msg_ack_nack_status[self.SEND_MSG_ACK_STATUS_KEY_NAME][1] = message_id_count can_msg_bytes = self._convert_data_to_bytes(' self._CAN_MSG_WAIT_FOR_RESP_S: + can_comm_status = CanCommStatus.CAN_COMM_TIME_OUT.value + break + if len(self._can_listener.raw_can_messages) == 0: continue + message = self._can_listener.raw_can_messages.pop(0) + # print("Popped message", message) can_comm_status = CanCommStatus.CAN_COMM_IN_PROGRESS.value - if message is None: continue # If received the message from the corresponding response message and the message ID count is the same as was sent # then check the CRC of the received message. If passed, check whether the message as acked or not and update the status resp_mail_box = self._update_mail_boxes[UpdateStacks(self._stack).name][self._STACK_RCV_RESP_MAIL_BOX] msg_id_back = self._convert_bytes_to_data(' self._CAN_MSG_WAIT_FOR_RESP_S: - # can_comm_status = CanCommStatus.CAN_COMM_TIME_OUT.value - # break - self._msg_ack_nack_status[msg_type] = can_comm_status + self._msg_ack_nack_status[msg_type][0] = can_comm_status if msg_type == self.SEND_MSG_ACK_STATUS_KEY_NAME: # If the message that was acked, was the command, enable the update message that it is ready to be used - self._msg_ack_nack_status[self.UPDATE_MSG_ACK_STATUS_KEY_NAME] = CanCommStatus.CAN_COMM_READY.value \ + self._msg_ack_nack_status[self.UPDATE_MSG_ACK_STATUS_KEY_NAME][0] = CanCommStatus.CAN_COMM_READY.value \ if can_comm_status == CanCommStatus.CAN_COMM_READY.value else CanCommStatus.CAN_COMM_NOT_STARTED.value self.file_handle.write("Msg ID: {}, Msg Type: {}, Comm Status: {}\r".format(self._msg_id_count, msg_type, CanCommStatus(can_comm_status).name))