diff --git a/tc/obsw_tc_service23.py b/tc/obsw_tc_service23.py index 9a14f9880b800cc90d35abd4e0776f6a40f73126..9f384fe3d9ac06437a79048b1c5d446550fed99a 100644 --- a/tc/obsw_tc_service23.py +++ b/tc/obsw_tc_service23.py @@ -5,19 +5,193 @@ Created: 21.01.2020 07:48 @author: Jakob Meier """ import math +from enum import Enum + import config.obsw_config as g from typing import Deque -from tc.obsw_pus_tc_packer import PusTelecommand +from tc.obsw_pus_tc_packer import PusTelecommand, TcQueueT +from tmtc_core.utility.obsw_logger import get_logger + +LOGGER = get_logger() + + +class FileTransferHelper: + """ + This helper class fills the provided TC queue with appropriate PUS telecommands + to transfer a large file. + There are three modes which determine which telecommands will be generated: + 1. NORMAL: Generate telecommand to create a new file and append data packets if + the file data is too large. This will be the default mode. + 2. DELETE_OLD: Generate telecommand to delete old file and then perform same steps as the + normal mode + 3. RENAME_OLD: Rename old file and then perform same steps as in normal mode. + + Please note that the setter functions set_data have to be used to assign data, otherwise + an empty file will be created. The mode is set with setter commands as well. + """ + class TransferMode(Enum): + # Normal mode + NORMAL = 1 + # Generate a command to delete the old file first + DELETE_OLD = 2 + # Generate a command to rename the old file first. + RENAME_OLD = 3 + + def __init__(self, tc_queue: TcQueueT, max_size_of_app_data: int, + target_repository: str, target_filename: str, + object_id=g.SD_CARD_HANDLER_ID): + """ + @param tc_queue: TC queue which will be filled + @param max_size_of_app_data: Maximum allowed app data size. Number of generated packets + will depend on this value + @param target_repository: Repository path on target. + @param target_filename: Filename on target + @param object_id: + """ + self.transfer_mode = self.TransferMode.NORMAL + self.max_size_of_app_data = max_size_of_app_data + self.allowed_file_data_size = calculate_allowed_file_data_size( + max_size_of_app_data, target_filename, target_repository) + self.target_filename = target_filename + self.target_repository = target_repository + self.renamed_name = self.target_filename + "old" + self.object_id = object_id + self.tc_queue = tc_queue + self.file_data = bytearray() + self.local_filename = "" + # This will generate a telecommand to delete the old file, if it exists + self.delete_old_file = False + # This will generater a telecommand to rename the old file, if it exists + self.rename_old_file = False + + def set_data(self, local_filename: str): + with open(local_filename, 'rb') as file: + self.file_data = file.read() + + def set_data(self, tc_data: bytearray): + self.file_data = tc_data + + def set_to_delete_old_file(self): + self.transfer_mode = self.TransferMode.DELETE_OLD + + def set_to_rename_old_file(self, renamed_name: str): + self.transfer_mode = self.TransferMode.RENAME_OLD + self.renamed_name = renamed_name + + def generate_packets(self, ssc: int): + if self.transfer_mode == self.TransferMode.DELETE_OLD: + command = generate_rm_file_srv23_2_packet(self.target_filename, self.target_repository, + ssc, self.object_id) + ssc += 1 + self.tc_queue.appendleft(command.pack_command_tuple()) + elif self.transfer_mode == self.TransferMode.RENAME_OLD: + # not implemented yet + pass + if len(self.file_data) > self.allowed_file_data_size: + # Large file, create file with init_data + init_data = self.file_data[0:self.allowed_file_data_size] + else: + # Small file, one packet for file creation sufficient + command = generate_create_file_srv23_1_packet( + self.target_filename, self.target_repository, ssc, self.max_size_of_app_data, + self.file_data) + ssc += 1 + self.tc_queue.appendleft(command.pack_command_tuple()) + return + + # Create large file. + command = generate_create_file_srv23_1_packet( + self.target_filename, self.target_repository, ssc, self.max_size_of_app_data, + init_data) + ssc += 1 + self.tc_queue.appendleft(command.pack_command_tuple()) + + rest_of_data = self.file_data[self.allowed_file_data_size:] + # Generate the rest of the packets to write to large file + self.__generate_append_to_file_packets_automatically(rest_of_data, ssc) + + def __generate_append_to_file_packets_automatically(self, data: bytearray, init_ssc: int): + """ + This function generates PUS packets which is used to write data in a file. + A new file will be created if not already existing. If the file already exists, this might + lead to + + If the file data is larger than the maximum allowed size of application data, this function + will split the data into multiple packets and increment the initial SSC number by one for + each packet. + @param data: Data which will be split up. + @param init_ssc: First SSC, which will be incremented for each packet. + """ + header = self.object_id + header += bytearray(self.target_repository, 'utf-8') + # Add string terminator of repository path + header.append(0) + header += bytearray(self.target_filename, 'utf-8') + # Add string terminator of filename + header.append(0) + self.__split_large_file(header, self.max_size_of_app_data, data, init_ssc) + + def __split_large_file(self, header: bytearray, size_of_data_blocks: int, + data: bytearray, init_ssc: int): + """ + This function splits a large file in multiple packets and packs the generated packets + into the member deque. This is necessary because the packet size is limited. + @param header: Repository and file name which will always stay the same + @param size_of_data_blocks: The file data blocks will have this size + @param data: The data to pack in multiple packets + @param init_ssc: The ssc of the first command, will be incremented by one for each packet. + """ + number_of_packets = math.floor(len(data) / size_of_data_blocks) + packet_number = 0 + + for i in range(number_of_packets): + header.append(packet_number >> 8) + header.append(0xFF & packet_number) + header += data[i * size_of_data_blocks:(i + 1) * size_of_data_blocks] + + commands = PusTelecommand(service=23, subservice=128, ssc=init_ssc + i, + app_data=header) + self.tc_queue.appendleft(commands.pack_command_tuple()) + + # Remove everything except the header + header = header[:len(header) - size_of_data_blocks - 2] + packet_number = packet_number + 1 + header.append(packet_number >> 8) + header.append(0xFF & packet_number) + header += data[number_of_packets * size_of_data_blocks:len(data)] + commands = PusTelecommand(service=23, subservice=128, ssc=init_ssc + packet_number, + app_data=header) + self.tc_queue.appendleft(commands.pack_command_tuple()) -def generate_rm_file_srv23_2_packet(filename: str, repository_path: str = "", - object_id=bytearray([])): +def generate_create_file_srv23_1_packet( + filename: str, repository_path: str, ssc: int, max_size_of_app_data: int, + initial_data=bytearray([]), object_id=g.SD_CARD_HANDLER_ID): + if len(initial_data) > calculate_allowed_file_data_size( + max_size_of_app_data, filename, repository_path): + LOGGER.error("generate_create_file_srv23_1_packet: Initial data too large!") + return None + data_to_pack = object_id + data_to_pack += bytearray(repository_path, 'utf-8') + # Add string terminator to repository_path + data_to_pack.append(0) + data_to_pack += bytearray(filename, 'utf-8') + # Add string terminator to filename + data_to_pack.append(0) + + data_to_pack += initial_data + return PusTelecommand(service=23, subservice=1, ssc=ssc, app_data=data_to_pack) + + +def generate_rm_file_srv23_2_packet(filename: str, repository_path: str, + ssc: int, object_id=g.SD_CARD_HANDLER_ID): """ This function generates a packet which is used to delete a file on a file-system-capable on-board memory. @param filename: The name of the file to delete @param repository_path: The path where the directory shall be created + @param ssc: source sequence count @param object_id: The object ID of the memory handler which manages the file system :return The TC[23,2] PUS packet """ @@ -28,7 +202,7 @@ def generate_rm_file_srv23_2_packet(filename: str, repository_path: str = "", data_to_pack += bytearray(filename, 'utf-8') # Add string terminator to filename data_to_pack.append(0) - return PusTelecommand(service=23, subservice=2, ssc=21, app_data=data_to_pack) + return PusTelecommand(service=23, subservice=2, ssc=ssc, app_data=data_to_pack) def generate_mkdir_srv23_9_packet(directory_name: str, repository_path: str = "", @@ -69,27 +243,9 @@ def generate_rmdir_srv23_10_packet(directory_name: str, repository_path: str = " return PusTelecommand(service=23, subservice=10, ssc=21, app_data=data_to_pack) -def generate_service23_subservice128_packet( - tc_queue: Deque, repository_path: str, filename: str, max_size_of_app_data: int, - file_data: bytearray = bytearray([]), init_ssc: int = 0, - object_id: bytearray = g.SD_CARD_HANDLER_ID): - """ - This function generates PUS packets which is used to write data in a file. - A new file will be created if not already existing. If the file already exists, this might - lead to - - If the file data is larger than the maximum allowed size of application data, this function - will split the data into multiple packets and increment the initial SSC number by one for - each packet. - - @param tc_queue: Deque containing the PusTelecommand objects to send. - @param repository_path: The path of the target file - @param filename: Name of file from which the content shall be read - @param max_size_of_app_data: Maximum size of one data block. The file - @param file_data: The data to write in the file - @param init_ssc: First SSC, which will be incremented for each packet. - @param object_id: object ID of the memory handler (e.g. SD Card Handler) - """ +def generate_append_to_file_srv23_128_packet( + filename: str, repository_path: str, ssc: int, file_data: bytearray = bytearray([]), + object_id=g.SD_CARD_HANDLER_ID): data_to_pack = object_id data_to_pack += bytearray(repository_path, 'utf-8') # Add string terminator of repository path @@ -97,46 +253,12 @@ def generate_service23_subservice128_packet( data_to_pack += bytearray(filename, 'utf-8') # Add string terminator of filename data_to_pack.append(0) - split_large_file(tc_queue, data_to_pack, max_size_of_app_data, file_data, init_ssc) + data_to_pack += file_data + return PusTelecommand(service=23, subservice=128, ssc=ssc, app_data=data_to_pack) -def split_large_file(tc_queue: Deque, data_to_pack: bytearray, size_of_data_blocks: int, - data: bytearray, init_ssc: int): - """ - This function splits a large file in multiple packets. - This is necessary because the packet size is limited. - @param tc_queue: Deque which will contain a a PusTelecommand tuple. - @param data_to_pack: bytearray of data. data will be split and appended to this bytearray - @param size_of_data_blocks: The file is splitted in data blocks of this size - @param data: The data to pack in multiple packets - @param init_ssc: The ssc of the first command, will be incremented by one for each packet. - @return List containing the PUS packets generated for each data block - """ - number_of_packets = math.floor(len(data) / size_of_data_blocks) - packet_number = 0 - - for i in range(number_of_packets): - data_to_pack.append(packet_number >> 8) - data_to_pack.append(0xFF & packet_number) - data_to_pack += data[i * size_of_data_blocks:(i + 1) * size_of_data_blocks] - - commands = PusTelecommand(service=23, subservice=128, ssc=init_ssc + i, app_data=data_to_pack) - tc_queue.appendleft(commands.pack_command_tuple()) - - # Last data block, packet number and data block size is removed to create new command with - # next data block, packet number - data_to_pack = data_to_pack[:len(data_to_pack) - size_of_data_blocks - 2] - packet_number = packet_number + 1 - data_to_pack.append(packet_number >> 8) - data_to_pack.append(0xFF & packet_number) - data_to_pack += data[number_of_packets * size_of_data_blocks:len(data)] - commands = PusTelecommand(service=23, subservice=128, ssc=init_ssc + packet_number, - app_data=data_to_pack) - tc_queue.appendleft(commands.pack_command_tuple()) - - -def generate_service23_subservice129_packet(repository_path: str, filename: str, - object_id: bytearray = bytearray([])): +def generate_read_file_srv23_129_packet(repository_path: str, filename: str, + object_id: bytearray = bytearray([])): """ This function generates the application data field for a PUS packet with service 23 and subservie 129. Subservice 129 is a custom service to request the data of a file. @@ -183,3 +305,8 @@ def pack_service23_test_into(tc_queue: Deque) -> Deque: # tc_queue.appendleft(command.pack_command_tuple()) tc_queue.appendleft(("print", "\r")) return tc_queue + + +def calculate_allowed_file_data_size(max_app_data_size: int, filename: str, repository: str): + # Subtract two because of '\0' terminators + return max_app_data_size - len(filename) - len(repository) - 2 diff --git a/utility/obsw_binary_uploader.py b/utility/obsw_binary_uploader.py index 64cdde40a121499a84c0f67bc0e7a48a552deb5a..a731e443144d213a7eaea077f5e83b313e8f16f6 100644 --- a/utility/obsw_binary_uploader.py +++ b/utility/obsw_binary_uploader.py @@ -12,7 +12,7 @@ from tkinter import filedialog from collections import deque from tmtc_core.comIF.obsw_com_interface import CommunicationInterface -from tc.obsw_tc_service23 import generate_service23_subservice128_packet, \ +from tc.obsw_tc_service23 import generate_append_to_file_packets_automatically, \ generate_rmdir_srv23_10_packet import config.obsw_config as g @@ -52,7 +52,7 @@ def perform_binary_upload(comIF: CommunicationInterface): generate_rmdir_srv23_10_packet() # We have to split the binary here first - generate_service23_subservice128_packet(tc_queue, "BIN/AT91/BL", "bl.bin", 1024, data_to_read) + generate_append_to_file_packets_automatically(tc_queue, "BIN/AT91/BL", "bl.bin", 1024, data_to_read) while tc_queue: (tc_packet, tc_info) = tc_queue.pop() comIF.send_telecommand(tc_packet, tc_info)