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)