From c9e9fb26832e8d19715ed208c6346a2d1764217c Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Tue, 13 Aug 2024 22:06:53 -0600 Subject: [PATCH 1/7] Add multiple variable bit length items to python --- .../lib/openc3/accessors/binary_accessor.rb | 4 +- openc3/lib/openc3/packets/packet.rb | 2 +- openc3/lib/openc3/packets/structure.rb | 1 + openc3/lib/openc3/packets/structure_item.rb | 2 +- .../openc3/accessors/binary_accessor.py | 168 +++- openc3/python/openc3/config/config_parser.py | 2 +- openc3/python/openc3/packets/packet.py | 21 +- openc3/python/openc3/packets/packet_config.py | 46 +- openc3/python/openc3/packets/structure.py | 70 +- .../python/openc3/packets/structure_item.py | 35 +- .../accessors/test_binary_accessor_write.py | 929 ++++++++---------- .../python/test/packets/test_packet_config.py | 57 +- openc3/python/test/packets/test_structure.py | 52 +- .../test/packets/test_structure_item.py | 43 +- openc3/spec/packets/packet_config_spec.rb | 6 +- openc3/spec/packets/packet_spec.rb | 27 +- openc3/spec/spec_helper.rb | 22 +- 17 files changed, 837 insertions(+), 650 deletions(-) diff --git a/openc3/lib/openc3/accessors/binary_accessor.rb b/openc3/lib/openc3/accessors/binary_accessor.rb index be2c8d9dfd..5dcd10e356 100644 --- a/openc3/lib/openc3/accessors/binary_accessor.rb +++ b/openc3/lib/openc3/accessors/binary_accessor.rb @@ -156,8 +156,10 @@ def handle_write_variable_bit_size(item, value, buffer) current_bit_size = 14 when 2 current_bit_size = 30 - else + when 3 current_bit_size = 62 + else + raise "Value #{item.variable_bit_size['length_item_name']} has unknown QUIC bit size encoding: #{length_item_value}" end if item.data_type == :UINT diff --git a/openc3/lib/openc3/packets/packet.rb b/openc3/lib/openc3/packets/packet.rb index c4facf6522..7df6ef01db 100644 --- a/openc3/lib/openc3/packets/packet.rb +++ b/openc3/lib/openc3/packets/packet.rb @@ -1072,7 +1072,7 @@ def to_config(cmd_or_tlm) config << " HAZARDOUS #{@hazardous_description.to_s.quote_if_necessary}\n" if @hazardous config << " DISABLE_MESSAGES\n" if @messages_disabled if @virtual - config << " VIRTUAL" + config << " VIRTUAL\n" elsif @disabled config << " DISABLED\n" elsif @hidden diff --git a/openc3/lib/openc3/packets/structure.rb b/openc3/lib/openc3/packets/structure.rb index 96e16ed4c0..3fb7b0196f 100644 --- a/openc3/lib/openc3/packets/structure.rb +++ b/openc3/lib/openc3/packets/structure.rb @@ -316,6 +316,7 @@ def append(item) item.bit_offset = 0 else item.bit_offset = @defined_length_bits + item.original_bit_offset = @defined_length_bits end return define(item) end diff --git a/openc3/lib/openc3/packets/structure_item.rb b/openc3/lib/openc3/packets/structure_item.rb index cfe37cef3e..cde42d93d4 100644 --- a/openc3/lib/openc3/packets/structure_item.rb +++ b/openc3/lib/openc3/packets/structure_item.rb @@ -48,7 +48,7 @@ class StructureItem # Will reflect the bit offset with all variable sized items at their # minimum size # @return [Integer] 0 based bit offset - attr_reader :original_bit_offset + attr_accessor :original_bit_offset # The number of bits which represent this StructureItem in the binary buffer. # @return [Integer] Size in bits diff --git a/openc3/python/openc3/accessors/binary_accessor.py b/openc3/python/openc3/accessors/binary_accessor.py index c3fe914b70..c8defb0666 100644 --- a/openc3/python/openc3/accessors/binary_accessor.py +++ b/openc3/python/openc3/accessors/binary_accessor.py @@ -18,6 +18,7 @@ import math import struct from .accessor import Accessor +from openc3.utilities.string import simple_formatted class BinaryAccessor(Accessor): @@ -102,6 +103,38 @@ def raise_buffer_error(cls, read_write, buffer, data_type, given_bit_offset, giv # Valid endianess ENDIANNESS = ["BIG_ENDIAN", "LITTLE_ENDIAN"] + def handle_read_variable_bit_size(self, item, _buffer): + length_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") + if item.array_size is not None: + item.array_size = (length_value * item.variable_bit_size["length_bits_per_count"]) + item.variable_bit_size[ + "length_value_bit_offset" + ] + else: + if item.data_type == "INT" or item.data_type == "UINT": + # QUIC encoding is currently assumed for individual variable sized integers + # see https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc + match length_value: + case 0: + item.bit_size = 6 + case 1: + item.bit_size = 14 + case 2: + item.bit_size = 30 + case _: + item.bit_size = 62 + else: + item.bit_size = ( + length_value * item.variable_bit_size["length_bits_per_count"] + ) + item.variable_bit_size["length_value_bit_offset"] + + def read_item(self, item, buffer): + if item.data_type == "DERIVED": + return None + if item.variable_bit_size: + self.handle_read_variable_bit_size(item, buffer) + return BinaryAccessor.class_read_item(item, buffer) + + # Note: do not use directly - use instance read_item @classmethod def class_read_item(cls, item, buffer): if item.data_type == "DERIVED": @@ -118,6 +151,133 @@ def class_read_item(cls, item, buffer): else: return cls.read(item.bit_offset, item.bit_size, item.data_type, buffer, item.endianness) + def handle_write_variable_bit_size(self, item, value, buffer): + # Update length field to new size + if (item.data_type == "INT" or item.data_type == "UINT") and item.original_array_size is None: + # QUIC encoding is currently assumed for individual variable sized integers + # see https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc + + # Calculate current bit size so we can preserve bytes after the item + length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") + match length_item_value: + case 0: + current_bit_size = 6 + case 1: + current_bit_size = 14 + case 2: + current_bit_size = 30 + case 3: + current_bit_size = 62 + case _: + raise RuntimeError( + f"Value {item.variable_bit_size['length_item_name']} has unknown QUIC bit size encoding: {length_item_value}" + ) + + if item.data_type == "UINT": + if value <= 63: + # Length = 0, value up to 6-bits + new_bit_size = 6 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 0) + elif value <= 16383: + # Length = 1, value up to 14-bits + new_bit_size = 14 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 1) + elif value <= 1073741823: + # Length = 2, value up to 30-bits + new_bit_size = 30 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 2) + else: + # Length = 3, value up to 62-bits + new_bit_size = 62 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 3) + else: + if value <= 31 and value >= -32: + # Length = 0, value up to 6-bits + new_bit_size = 6 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 0) + elif value <= 8191 and value >= -8192: + # Length = 1, value up to 14-bits + new_bit_size = 14 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 1) + elif value <= 536870911 and value >= -536870912: + # Length = 2, value up to 30-bits + new_bit_size = 30 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 2) + else: + # Length = 3, value up to 62-bits + new_bit_size = 62 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 3) + + # Later items need their bit_offset adjusted by the change in this item + adjustment = new_bit_size - current_bit_size + bytes = int(adjustment / 8) + item_offset = int(item.bit_offset / 8) + if bytes > 0: + original_length = len(buffer) + # Add extra bytes because we're adjusting larger + buffer += BinaryAccessor.ZERO_STRING * bytes + # We added bytes to the end so now we have to shift the buffer over + # We copy from the original offset with the original length + # to the new shifted offset all the way to the end of the buffer + buffer[(item_offset + bytes) :] = buffer[item_offset:original_length] + elif bytes < 0: + # Remove extra bytes because we're adjusting smaller + del buffer[item_offset + 1 : item_offset + 1 - bytes] + # Probably not possible to get this condition because we don't allow 0 sized floats + # but check for it just to cover all the possible data_types + elif item.data_type == "FLOAT": + raise "Variable bit size not currently supported for FLOAT data type" + else: + # STRING, BLOCK, or array types + + # Calculate current bit size so we can preserve bytes after the item + length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") + current_bit_size = ( + length_item_value * item.variable_bit_size["length_bits_per_count"] + ) + item.variable_bit_size["length_value_bit_offset"] + + # Calculate bits after this item + bits_with_item = item.bit_offset + current_bit_size + bits_after_item = (len(buffer) * 8) - bits_with_item + if item.original_array_size is not None: + item.array_size = -bits_after_item + else: + item.bit_size = -bits_after_item + + new_bit_size = len(value) * 8 + length_value = (new_bit_size - item.variable_bit_size["length_value_bit_offset"]) / item.variable_bit_size[ + "length_bits_per_count" + ] + self.packet.write(item.variable_bit_size["length_item_name"], length_value) + + # Later items need their bit_offset adjusted by the change in this item + adjustment = new_bit_size - current_bit_size + + # Recalculate bit offsets after this item + if adjustment != 0 and item.bit_offset >= 0: + for sitem in self.packet.sorted_items: + if sitem.data_type == "DERIVED" or sitem.bit_offset < item.bit_offset: + # Skip items before this item and derived items and items with negative bit offsets + continue + if sitem != item: + sitem.bit_offset += adjustment + + def write_item(self, item, value, buffer): + if item.data_type == "DERIVED": + return None + if item.variable_bit_size: + self.handle_write_variable_bit_size(item, value, buffer) + BinaryAccessor.class_write_item(item, value, buffer) + + # Note: do not use directly - use instance write_item @classmethod def class_write_item(cls, item, value, buffer): if item.data_type == "DERIVED": @@ -185,7 +345,7 @@ def read(cls, bit_offset, bit_size, data_type, buffer, endianness): try: buffer = buffer[lower_bound : (upper_bound + 1)] try: - return buffer[: buffer.index(b"\00")].decode(encoding="utf-8") + return buffer[: buffer.index(BinaryAccessor.ZERO_STRING)].decode(encoding="utf-8") except ValueError: return buffer.decode(encoding="utf-8") # If this 'STRING' contains binary buffer.decode will fail @@ -347,7 +507,7 @@ def write(cls, value, bit_offset, bit_size, data_type, buffer, endianness, overf # String was completely empty if end_bytes > 0: # Preserve bytes at end of buffer - buffer += b"\00" * len(value) + buffer += BinaryAccessor.ZERO_STRING * len(value) lower_index = lower_bound + len(value) buffer[lower_index : lower_index + end_bytes] = buffer[ lower_bound : lower_bound + end_bytes @@ -362,7 +522,7 @@ def write(cls, value, bit_offset, bit_size, data_type, buffer, endianness, overf elif (upper_bound > old_upper_bound) and (end_bytes > 0): # Preserve bytes at end of buffer diff = upper_bound - old_upper_bound - buffer += b"\00" * diff + buffer += BinaryAccessor.ZERO_STRING * diff buffer[upper_bound + 1 : upper_bound + 1 + end_bytes] = buffer[ old_upper_bound + 1 : old_upper_bound + 1 + end_bytes ] @@ -375,7 +535,7 @@ def write(cls, value, bit_offset, bit_size, data_type, buffer, endianness, overf ba.extend(value.encode(encoding="utf-8")) value = ba # Pad the requested size with zeros - temp = value.ljust(byte_size, b"\00") + temp = value.ljust(byte_size, BinaryAccessor.ZERO_STRING) elif len(value) > byte_size: if overflow == "TRUNCATE": # Resize the value to fit the field diff --git a/openc3/python/openc3/config/config_parser.py b/openc3/python/openc3/config/config_parser.py index 440d494bea..93cddf5643 100644 --- a/openc3/python/openc3/config/config_parser.py +++ b/openc3/python/openc3/config/config_parser.py @@ -128,7 +128,7 @@ def verify_num_parameters(self, min_num_params, max_num_params, usage=""): raise ConfigParser.Error(self, f"Not enough parameters for {self.keyword}.", usage, self.url) # If they pass None for max_params we don't check for a maximum number - if max_num_params and self.parameters[max_num_params : max_num_params + 1]: + if max_num_params is not None and self.parameters[max_num_params : max_num_params + 1]: raise ConfigParser.Error(self, f"Too many parameters for {self.keyword}.", usage, self.url) # Verifies the indicated parameter in the config doesn't start or end diff --git a/openc3/python/openc3/packets/packet.py b/openc3/python/openc3/packets/packet.py index 5ac09698da..a607bcb995 100644 --- a/openc3/python/openc3/packets/packet.py +++ b/openc3/python/openc3/packets/packet.py @@ -96,6 +96,7 @@ def __init__( self.screen = None self.related_items = None self.ignore_overlap = False + self.virtual = False @property def target_name(self): @@ -188,6 +189,17 @@ def received_count(self, received_count): self.__received_count = received_count self.read_conversion_cache = {} + @property + def virtual(self): + return self.__virtual + + @virtual.setter + def virtual(self, virtual): + self.__virtual = virtual + if virtual: + self.hidden = True + self.disabled = True + # Tries to identify if a buffer represents the currently defined packet. It: # does this by iterating over all the packet items that were created with # an ID value and checking whether that ID value is present at the correct @@ -203,6 +215,8 @@ def received_count(self, received_count): def identify(self, buffer): if not buffer: return False + if self.virtual: + return False if not self.id_items: return True @@ -952,7 +966,9 @@ def to_config(self, cmd_or_tlm): config += f" HAZARDOUS {quote_if_necessary(self.hazardous_description)}\n" if self.messages_disabled: config += " DISABLE_MESSAGES\n" - if self.disabled: + if self.virtual: + config += " VIRTUAL\n" + elif self.disabled: config += " DISABLED\n" elif self.hidden: config += " HIDDEN\n" @@ -1011,6 +1027,8 @@ def as_json(self): config["disabled"] = True if self.hidden: config["hidden"] = True + if self.virtual: + config["virtual"] = True config["accessor"] = self.accessor.__class__.__name__ # config["accessor_args"] = self.accessor.args if self.template: @@ -1059,6 +1077,7 @@ def from_json(cls, hash): packet.messages_disabled = hash.get("messages_disabled") packet.disabled = hash.get("disabled") packet.hidden = hash.get("hidden") + packet.virtual = hash.get("virtual") if "accessor" in hash: try: filename = class_name_to_filename(hash["accessor"]) diff --git a/openc3/python/openc3/packets/packet_config.py b/openc3/python/openc3/packets/packet_config.py index 3b7e783010..647dffc63c 100644 --- a/openc3/python/openc3/packets/packet_config.py +++ b/openc3/python/openc3/packets/packet_config.py @@ -207,6 +207,7 @@ def process_file(self, filename, process_target_name): | "DISABLE_MESSAGES" | "HIDDEN" | "DISABLED" + | "VIRTUAL" | "ACCESSOR" | "TEMPLATE" | "TEMPLATE_FILE" @@ -245,6 +246,7 @@ def process_file(self, filename, process_target_name): | "OVERFLOW" | "OVERLAP" | "KEY" + | "VARIABLE_BIT_SIZE" ): if not self.current_item: raise parser.error(f"No current item for {keyword}") @@ -320,18 +322,20 @@ def finish_packet(self): if self.current_cmd_or_tlm == PacketConfig.COMMAND: PacketParser.check_item_data_types(self.current_packet) self.commands[self.current_packet.target_name][self.current_packet.packet_name] = self.current_packet - hash = self.cmd_id_value_hash.get(self.current_packet.target_name) - if not hash: - hash = {} - self.cmd_id_value_hash[self.current_packet.target_name] = hash - self.update_id_value_hash(hash) + if not self.current_packet.virtual: + hash = self.cmd_id_value_hash.get(self.current_packet.target_name) + if not hash: + hash = {} + self.cmd_id_value_hash[self.current_packet.target_name] = hash + self.update_id_value_hash(hash) else: self.telemetry[self.current_packet.target_name][self.current_packet.packet_name] = self.current_packet - hash = self.tlm_id_value_hash.get(self.current_packet.target_name) - if not hash: - hash = {} - self.tlm_id_value_hash[self.current_packet.target_name] = hash - self.update_id_value_hash(hash) + if not self.current_packet.virtual: + hash = self.tlm_id_value_hash.get(self.current_packet.target_name) + if not hash: + hash = {} + self.tlm_id_value_hash[self.current_packet.target_name] = hash + self.update_id_value_hash(hash) self.current_packet = None self.current_item = None @@ -443,6 +447,13 @@ def process_current_packet(self, parser, keyword, params): self.current_packet.hidden = True self.current_packet.disabled = True + case "VIRTUAL": + usage = keyword + parser.verify_num_parameters(0, 0, usage) + self.current_packet.hidden = True + self.current_packet.disabled = True + self.current_packet.virtual = True + case "ACCESSOR": usage = f"{keyword} " parser.verify_num_parameters(1, None, usage) @@ -704,6 +715,21 @@ def process_current_item(self, parser, keyword, params): parser.verify_num_parameters(1, 1, "KEY ") self.current_item.key = params[0] + case "VARIABLE_BIT_SIZE": + parser.verify_num_parameters( + 1, + 3, + "VARIABLE_BIT_SIZE ", + ) + + variable_bit_size = {"length_bits_per_count": 8, "length_value_bit_offset": 0} + variable_bit_size["length_item_name"] = params[0].upper() + if len(params) > 1: + variable_bit_size["length_bits_per_count"] = int(params[1]) + if len(params) > 2: + variable_bit_size["length_value_bit_offset"] = int(params[2]) + self.current_item.variable_bit_size = variable_bit_size + def start_item(self, parser): self.finish_item() self.current_item = PacketItemParser.parse(parser, self.current_packet, self.current_cmd_or_tlm, self.warnings) diff --git a/openc3/python/openc3/packets/structure.py b/openc3/python/openc3/packets/structure.py index 90c1dc573b..52165d445d 100644 --- a/openc3/python/openc3/packets/structure.py +++ b/openc3/python/openc3/packets/structure.py @@ -54,7 +54,7 @@ def __init__( self.fixed_size = True self.short_buffer_allowed = False self.mutex = None - self.accessor = BinaryAccessor() + self.accessor = BinaryAccessor(self) else: raise AttributeError(f"Unknown endianness '{default_endianness}', must be 'BIG_ENDIAN' or 'LITTLE_ENDIAN'") @@ -267,8 +267,6 @@ def append_item( ): if not endianness: endianness = self.default_endianness - if not self.fixed_size: - raise AttributeError("Can't append an item after a variably sized item") if data_type == "DERIVED": return self.define_item(name, 0, bit_size, data_type, array_size, endianness, overflow) else: @@ -288,13 +286,15 @@ def append_item( # self.param item (see #define) # self.return (see #define) def append(self, item): - if not self.fixed_size: - raise AttributeError("Can't append an item after a variably sized item") - if item.data_type == "DERIVED": item.bit_offset = 0 else: + # We're appending a new item so set the bit_offset item.bit_offset = self.defined_length_bits + # Also set original_bit_offset because it's currently 0 + # due to PacketItemParser::create_packet_item + # get_bit_offset() returning 0 if append + item.original_bit_offset = self.defined_length_bits return self.define(item) @@ -311,6 +311,21 @@ def get_item(self, name): def set_item(self, item): if self.items.get(item.name): self.items[item.name] = item + + # Need to allocate space for the variable length item if its minimum size is greater than zero + if item.variable_bit_size: + minimum_data_bits = 0 + if (item.data_type == "INT" or item.data_type == "UINT") and not item.original_array_size: + # Minimum QUIC encoded integer, see https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc + minimum_data_bits = 6 + # STRING, BLOCK, or array item + elif item.variable_bit_size["length_value_bit_offset"] > 0: + minimum_data_bits = ( + item.variable_bit_size["length_value_bit_offset"] + * item.variable_bit_size["length_bits_per_count"] + ) + if minimum_data_bits > 0 and item.bit_offset >= 0 and self.defined_length_bits == item.bit_offset: + self.defined_length_bits += minimum_data_bits else: raise AttributeError(f"Unknown item: {item.name} - Ensure item name is uppercase") @@ -507,11 +522,54 @@ def synchronize_allow_reads(self, top=False): else: # if self.mutex_allow_reads == threading.get_ident() yield + def calculate_total_bit_size(self, item): + if item.variable_bit_size: + # Bit size is determined by length field + length_value = self.read(item.variable_bit_size["length_item_name"], "CONVERTED") + if item.data_type == "INT" or item.data_type == "UINT" and not item.original_array_size: + match length_value: + case 0: + return 6 + case 1: + return 14 + case 2: + return 30 + case _: + return 62 + else: + return (length_value * item.variable_bit_size["length_bits_per_count"]) + item.variable_bit_size[ + "length_value_bit_offset" + ] + elif item.original_bit_size <= 0: + # Bit size is full packet length - bits before item + negative bits saved at end + return (len(self._buffer) * 8) - item.bit_offset + item.original_bit_size + elif item.original_array_size and item.original_array_size <= 0: + # Bit size is full packet length - bits before item + negative bits saved at end + return (len(self._buffer) * 8) - item.bit_offset + item.original_array_size + else: + raise AttributeError("Unexpected use of calculate_total_bit_size for non-variable-sized item") + + def recalculate_bit_offsets(self): + adjustment = 0 + for item in self.sorted_items: + # Anything with a negative bit offset should be left alone + if item.original_bit_offset >= 0: + item.bit_offset = item.original_bit_offset + adjustment + if item.data_type != "DERIVED" and ( + item.variable_bit_size + or item.original_bit_size <= 0 + or (item.original_array_size and item.original_array_size <= 0) + ): + adjustment += self.calculate_total_bit_size(item) + def internal_buffer_equals(self, buffer): if not isinstance(buffer, (bytes, bytearray)): raise AttributeError(f"Buffer class is {buffer.__class__.__name__} but must be bytearray") self._buffer = bytearray(buffer[:]) + if not self.fixed_size: + self.recalculate_bit_offsets() + # self.buffer.force_encoding('ASCII-8BIT'.freeze) if self.accessor.enforce_length(): if len(self._buffer) != self.defined_length: diff --git a/openc3/python/openc3/packets/structure_item.py b/openc3/python/openc3/packets/structure_item.py index 7eabc5585f..4cb250fdf7 100644 --- a/openc3/python/openc3/packets/structure_item.py +++ b/openc3/python/openc3/packets/structure_item.py @@ -55,10 +55,14 @@ def __init__( self.endianness = endianness self.data_type = data_type self.bit_offset = bit_offset + self.original_bit_offset = bit_offset self.bit_size = bit_size + self.original_bit_size = bit_size self.array_size = array_size + self.original_array_size = array_size self.overflow = overflow self.overlap = False + self.variable_bit_size = False self.create_index = StructureItem.create_index StructureItem.create_index += 1 self.structure_item_constructed = True @@ -139,10 +143,8 @@ def bit_size(self, bit_size): raise AttributeError(f"{self.name}: bit_size must be an Integer") byte_multiple = (bit_size % 8) == 0 - if bit_size <= 0 and (self.data_type == "INT" or self.data_type == "UINT" or self.data_type == "FLOAT"): - raise AttributeError( - f"{self.name}: bit_size cannot be negative or zero for 'INT', 'UINT', and 'FLOAT' items: {bit_size}" - ) + if bit_size <= 0 and self.data_type == "FLOAT": + raise AttributeError(f"{self.name}: bit_size cannot be negative or zero for 'FLOAT' items: {bit_size}") if (self.data_type == "STRING" or self.data_type == "BLOCK") and not byte_multiple: raise AttributeError(f"{self.name}: bit_size for STRING and BLOCK items must be byte multiples") if self.data_type == "FLOAT" and bit_size != 32 and bit_size != 64: @@ -209,6 +211,25 @@ def overflow(self, overflow): if self.structure_item_constructed: self.verify_overall() + @property + def variable_bit_size(self): + return self.__variable_bit_size + + @variable_bit_size.setter + def variable_bit_size(self, variable_bit_size): + if variable_bit_size: + if type(variable_bit_size) != dict: + raise AttributeError(f"{self.name}: variable_bit_size must be a dict") + if type(variable_bit_size["length_item_name"]) != str: + raise AttributeError(f"{self.name}: variable_bit_size['length_item_name'] must be a String") + if type(variable_bit_size["length_value_bit_offset"]) != int: + raise AttributeError(f"{self.name}: variable_bit_size['length_value_bit_offset'] must be an Integer") + if type(variable_bit_size["length_bits_per_count"]) != int: + raise AttributeError(f"{self.name}: variable_bit_size['length_bits_per_count'] must be an Integer") + self.__variable_bit_size = variable_bit_size + if self.structure_item_constructed: + self.verify_overall() + def __eq__(self, other): # Sort by first bit_offset, then bit_size, then create_index if self.bit_offset == other.bit_offset: @@ -269,11 +290,11 @@ def as_json(self): hash = {} hash["name"] = self.name hash["key"] = self.key - hash["bit_offset"] = self.bit_offset - hash["bit_size"] = self.bit_size + hash["bit_offset"] = self.original_bit_offset + hash["bit_size"] = self.original_bit_size hash["data_type"] = self.data_type hash["endianness"] = self.endianness - hash["array_size"] = self.array_size + hash["array_size"] = self.original_array_size hash["overflow"] = self.overflow return hash diff --git a/openc3/python/test/accessors/test_binary_accessor_write.py b/openc3/python/test/accessors/test_binary_accessor_write.py index 23a61bc10d..c63bdd7099 100644 --- a/openc3/python/test/accessors/test_binary_accessor_write.py +++ b/openc3/python/test/accessors/test_binary_accessor_write.py @@ -20,16 +20,249 @@ from unittest.mock import * from test.test_helper import * from openc3.accessors.binary_accessor import BinaryAccessor +from openc3.packets.packet import Packet +from openc3.utilities.string import simple_formatted class TestBinaryAccessorWrite(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) - self.baseline_data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + self.baseline_data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") + + def test_can_write_QUIC_encoded_variable_sized_integers(self): + packet = Packet() + packet.append_item("marker", 16, "STRING") + packet.write("marker", "**") + self.assertEqual(packet.buffer, b"\x2A\x2A") + + packet.append_item("item1_length", 2, "UINT") + item1 = packet.append_item("item1", 0, "UINT") + item1.variable_bit_size = { + "length_item_name": "item1_length", + "length_value_bit_offset": 0, + "length_bits_per_count": 8, + } + # This is required to get the defined_length_bits set correctly + # It is normally called by packet_config when constructing the packet + packet.set_item(item1) + self.assertEqual(packet.buffer, b"\x2A\x2A\x00") + + packet.append_item("marker", 16, "STRING") + packet.write("marker", "##") + self.assertEqual(packet.buffer, b"\x2A\x2A\x00\x23\x23") + + packet.append_item("item2_length", 2, "UINT") + item2 = packet.append_item("item2", 0, "INT") + item2.variable_bit_size = { + "length_item_name": "item2_length", + "length_value_bit_offset": 0, + "length_bits_per_count": 8, + } + # This is required to get the defined_length_bits set correctly + # It is normally called by packet_config when constructing the packet + packet.set_item(item2) + self.assertEqual(packet.buffer, b"\x2A\x2A\x00\x23\x23\x00") + + packet.append_item("marker", 16, "STRING") + packet.write("marker", "$$") + self.assertEqual(packet.buffer, b"\x2A\x2A\x00\x23\x23\x00\x24\x24") + + # For all these example the 2 bits indicate the length + # 0 is 6, 1 is 14, 2 is 30, 4 is 62 + # The 2 bit length field itself makes up a full byte boundary + + packet.write("item1", 0) + packet.write("item2", 0) + self.assertEqual(packet.buffer, b"\x2A\x2A\x00\x23\x23\x00\x24\x24") + self.assertEqual(packet.read("item1"), 0x0) + self.assertEqual(packet.read("item2"), 0x0) + + # Loop through all the combinations of bits + for bits, format_string, length_bits in [ + [6, ">B", 0], + [14, ">H", 0x4000], + [30, ">L", 0x80000000], + [62, ">Q", 0xC000000000000000], + ]: + min_unsigned = 0 + max_unsigned = (2**bits) - 1 + mask = max_unsigned + min_signed = -(2 ** (bits - 1)) + max_signed = (2 ** (bits - 1)) - 1 + + packet.write("item1", min_unsigned) + packet.write("item2", min_signed) + # We know min unsigned is just 0 so pack that + buffer = b"\x2A\x2A\x00\x23\x23" + # Mask off the length bits then set them + buffer += struct.pack( + format_string, + (min_signed & mask) | length_bits, + ) + buffer += b"\x24\x24" + self.assertEqual(packet.buffer, buffer) + self.assertEqual(packet.read("item1"), min_unsigned) + self.assertEqual(packet.read("item2"), min_signed) + + packet.write("item1", max_unsigned) + packet.write("item2", max_signed) + buffer = b"\x2A\x2A" + # Mask off the length bits then set them + buffer += struct.pack( + format_string, + (max_unsigned & mask) | length_bits, + ) + buffer += b"\x23\x23" + # Mask off the length bits then set them + buffer += struct.pack( + format_string, + (max_signed & mask) | length_bits, + ) + buffer += b"\x24\x24" + self.assertEqual(packet.buffer, buffer) + self.assertEqual(packet.read("item1"), max_unsigned) + self.assertEqual(packet.read("item2"), max_signed) + + def test_can_define_multiple_variable_sized_items(self): + packet = Packet() + item1_length = packet.append_item("item1_length", 32, "INT") + self.assertEqual(item1_length.bit_offset, 0) + + item1 = packet.append_item("item1", 0, "STRING") + packet.write("item1_length", 0) + item1.variable_bit_size = { + "length_item_name": "item1_length", + "length_value_bit_offset": 0, + "length_bits_per_count": 8, + } + self.assertEqual(item1.bit_offset, 32) + + item2_length = packet.append_item("item2_length", 16, "UINT") + self.assertEqual(item2_length.bit_offset, 32) + + item2 = packet.append_item("item2", 0, "STRING") + packet.write("item2_length", 0) + item2.variable_bit_size = { + "length_item_name": "item2_length", + "length_value_bit_offset": 0, + "length_bits_per_count": 8, + } + self.assertEqual(item2.bit_offset, 48) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x00\x00\x00") + + item3_length = packet.append_item("item3_length", 8, "UINT") + self.assertEqual(item3_length.bit_offset, 48) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x00\x00\x00\x00") + + item3 = packet.append_item("item3", 8, "UINT", 0) + packet.write("item3_length", 0) + item3.variable_bit_size = { + "length_item_name": "item3_length", + "length_value_bit_offset": 0, + "length_bits_per_count": 8, + } + self.assertEqual(item3.bit_offset, 56) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x00\x00\x00\x00") + + packet.write("item1", "This is Awesome") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x0FThis is Awesome\x00\x00\x00") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) + self.assertEqual(item3_length.bit_offset, 168) + self.assertEqual(item3.bit_offset, 176) + + packet.write("item2", "So is this") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x0FThis is Awesome\x00\x0ASo is this\x00") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) + self.assertEqual(item3_length.bit_offset, 248) + self.assertEqual(item3.bit_offset, 256) + + packet.write("item3", [1, 2, 3, 4, 5]) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x0FThis is Awesome\x00\x0ASo is this\x05\x01\x02\x03\x04\x05") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) + self.assertEqual(item3_length.bit_offset, 248) + self.assertEqual(item3.bit_offset, 256) + + packet.write("item2", "Small") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x0FThis is Awesome\x00\x05Small\x05\x01\x02\x03\x04\x05") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) + self.assertEqual(item3_length.bit_offset, 208) + self.assertEqual(item3.bit_offset, 216) + + packet.write("item1", "Yo") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x02Yo\x00\x05Small\x05\x01\x02\x03\x04\x05") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 48) + self.assertEqual(item2.bit_offset, 64) + self.assertEqual(item3_length.bit_offset, 104) + self.assertEqual(item3.bit_offset, 112) + + # Recalculates offsets when buffer is written + packet.buffer = b"\x00\x00\x00\x03Ok!\x00\x04Cool\x02\xA5\x5A" + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 56) + self.assertEqual(item2.bit_offset, 72) + self.assertEqual(item3_length.bit_offset, 104) + self.assertEqual(item3.bit_offset, 112) + self.assertEqual(packet.read("item1_length"), 3) + self.assertEqual(packet.read("item1"), "Ok!") + self.assertEqual(packet.read("item2_length"), 4) + self.assertEqual(packet.read("item2"), "Cool") + self.assertEqual(packet.read("item3_length"), 2) + self.assertEqual(packet.read("item3"), [0xA5, 0x5A]) + + def test_can_define_multiple_variable_sized_items_with_nonzero_value_offsets(self): + packet = Packet() + item1_length = packet.append_item("item1_length", 32, "INT") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x00") + item1 = packet.append_item("item1", 0, "STRING") + packet.write("item1_length", 4) # Lengths must be initialized to zero equivalent + self.assertEqual(packet.buffer, b"\x00\x00\x00\x04") + item1.variable_bit_size = { + "length_item_name": "item1_length", + "length_value_bit_offset": -32, + "length_bits_per_count": 8, + } + self.assertEqual(item1.bit_offset, 32) + item2_length = packet.append_item("item2_length", 16, "UINT") + self.assertEqual(item2_length.bit_offset, 32) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x04\x00\x00") + item2 = packet.append_item("item2", 0, "STRING") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x04\x00\x00") + packet.write("item2_length", 8) # Lengths must be initialized to zero equivalent + item2.variable_bit_size = { + "length_item_name": "item2_length", + "length_value_bit_offset": -64, + "length_bits_per_count": 8, + } + self.assertEqual(item2.bit_offset, 48) + self.assertEqual(packet.buffer, b"\x00\x00\x00\x04\x00\x08") + packet.write("item1", "This is Awesome") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x13This is Awesome\x00\x08") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) + packet.write("item2", "So is this") + self.assertEqual(packet.buffer, b"\x00\x00\x00\x13This is Awesome\x00\x12So is this") + self.assertEqual(item1_length.bit_offset, 0) + self.assertEqual(item1.bit_offset, 32) + self.assertEqual(item2_length.bit_offset, 152) + self.assertEqual(item2.bit_offset, 168) def test_complains_about_unknown_data_types(self): self.assertRaisesRegex( @@ -129,9 +362,7 @@ def test_complains_about_negative_or_zero_bit_sizes_with_data_types_other_than_s def test_writes_aligned_strings(self): for bit_offset in range(0, (len(self.data) - 1) * 8, 8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") expected_data = self.baseline_data[:] first_byte_index = int(bit_offset / 8) if first_byte_index > 0: @@ -150,12 +381,8 @@ def test_writes_aligned_strings(self): def test_writes_variable_length_strings_with_a_zero_and_negative_bit_size(self): for bit_size in range(0, -(len(self.baseline_data)) * 8, -8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) - expected_data = self.baseline_data[:] + bytearray( - b"\x00" * -int(bit_size / 8) - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + expected_data = self.baseline_data[:] + bytearray(b"\x00" * -int(bit_size / 8)) BinaryAccessor.write( self.baseline_data, 0, @@ -195,9 +422,7 @@ def test_complains_about_unaligned_strings(self): def test_writes_aligned_blocks(self): for bit_offset in range(0, (len(self.data) - 1) * 8, 8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") expected_data = self.baseline_data[:] first_byte_index = int(bit_offset / 8) if first_byte_index > 0: @@ -216,9 +441,7 @@ def test_writes_aligned_blocks(self): def test_writes_variable_length_blocks_with_a_zero_and_negative_bit_size(self): for bit_size in range(0, (-len(self.data)) * 8, -8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") expected_data = self.baseline_data[:] + (b"\x00" * -int(bit_size / 8)) BinaryAccessor.write( self.baseline_data, @@ -307,9 +530,7 @@ def test_writes_a_block_to_another_small_buffer_preserving_the_end(self): self.assertEqual(buffer[0:2], b"\x00\x01") self.assertEqual(buffer[2:-4], data) self.assertEqual(buffer[-4:], preserve) - data = BinaryAccessor.read( - 0, 16 + len(data) * 8 + 32, "BLOCK", buffer, "BIG_ENDIAN" - ) + data = BinaryAccessor.read(0, 16 + len(data) * 8 + 32, "BLOCK", buffer, "BIG_ENDIAN") self.assertEqual(data, buffer) def test_writes_a_block_to_a_small_buffer_overwriting_end(self): @@ -333,9 +554,7 @@ def test_writes_a_smaller_block_in_the_middle_of_a_buffer(self): for index in range(512): buffer += struct.pack(">H", 0xDEAD) expected = buffer[:] - BinaryAccessor.write( - data, 128 * 8, -128 * 8, "BLOCK", buffer, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(data, 128 * 8, -128 * 8, "BLOCK", buffer, "BIG_ENDIAN", "ERROR") self.assertEqual(len(buffer), (128 + 512 + 128)) self.assertEqual(buffer[0:128], expected[0:128]) self.assertEqual(buffer[128:-128], data) @@ -349,9 +568,7 @@ def test_writes_a_larger_block_in_the_middle_of_a_buffer(self): for index in range(512): buffer += struct.pack(">H", 0xDEAD) expected = buffer[:] - BinaryAccessor.write( - data, 384 * 8, -384 * 8, "BLOCK", buffer, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(data, 384 * 8, -384 * 8, "BLOCK", buffer, "BIG_ENDIAN", "ERROR") self.assertEqual(len(buffer), (384 + 512 + 384)) self.assertEqual(buffer[0:384], expected[0:384]) self.assertEqual(buffer[384:-384], data) @@ -377,9 +594,7 @@ def test_complains_when_the_negative_index_exceeds_the_buffer_length(self): ) def test_writes_blocks_with_negative_bit_offsets(self): - BinaryAccessor.write( - self.baseline_data[0:2], -16, 16, "BLOCK", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(self.baseline_data[0:2], -16, 16, "BLOCK", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data[-2:], self.baseline_data[0:2]) def test_writes_a_blank_string_with_zero_bit_size(self): @@ -427,9 +642,7 @@ def test_writes_a_shorter_block_with_zero_bit_size(self): self.assertEqual(self.data, b"\x00\x00\x00\x00\x00\x00\x00\x00") def test_writes_a_shorter_string_and_zero_fill_to_the_given_bit_size(self): - self.data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") BinaryAccessor.write( b"\x01\x02\x03\x04\x05\x06\x07\x08", 0, @@ -445,9 +658,7 @@ def test_writes_a_shorter_string_and_zero_fill_to_the_given_bit_size(self): ) def test_writes_a_shorter_block_and_zero_fill_to_the_given_bit_size(self): - self.data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") BinaryAccessor.write( b"\x01\x02\x03\x04\x05\x06\x07\x08", 0, @@ -492,17 +703,13 @@ def test_complains_if_write_exceeds_the_size_of_the_buffer(self): def test_truncates_the_buffer_for_0_bitsize(self): self.assertEqual(len(self.data), 16) - BinaryAccessor.write( - b"\x01\x02\x03", 8, 0, "BLOCK", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(b"\x01\x02\x03", 8, 0, "BLOCK", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x00\x01\x02\x03") self.assertEqual(len(self.data), 4) def test_expands_the_buffer_for_0_bitsize(self): self.assertEqual(len(self.data), 16) - BinaryAccessor.write( - b"\x01\x02\x03", (14 * 8), 0, "BLOCK", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(b"\x01\x02\x03", (14 * 8), 0, "BLOCK", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03", @@ -513,17 +720,13 @@ def test_writes_a_frozen_string(self): buffer = bytearray(b"BLANKxxxWORLD") string = b"HELLO" # Specify 3 more bytes than given to exercise the padding logic - string = BinaryAccessor.write( - string, 0, (len(string) + 3) * 8, "STRING", buffer, "BIG_ENDIAN", "ERROR" - ) + string = BinaryAccessor.write(string, 0, (len(string) + 3) * 8, "STRING", buffer, "BIG_ENDIAN", "ERROR") self.assertEqual(buffer, b"HELLO\x00\x00\x00WORLD") self.assertEqual(string, b"HELLO") def test_writes_aligned_8_bit_unsigned_integers(self): for bit_offset in range(0, (len(self.data) - 1) * 8, 8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") byte_index = int(bit_offset / 8) BinaryAccessor.write( self.baseline_data[byte_index], @@ -541,25 +744,19 @@ def test_writes_aligned_8_bit_unsigned_integers(self): def test_writes_aligned_8_bit_signed_integers(self): for bit_offset in range(0, (len(self.data) - 1) * 8, 8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") byte_index = int(bit_offset / 8) value = self.baseline_data[byte_index] if value >= 128: value = value - 256 - BinaryAccessor.write( - value, bit_offset, 8, "INT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(value, bit_offset, 8, "INT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data[byte_index : byte_index + 1], self.baseline_data[byte_index : byte_index + 1], ) def test_converts_floats_when_writing_integers(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(1.0, 0, 8, "UINT", self.data, "BIG_ENDIAN", "ERROR") BinaryAccessor.write(2.5, 8, 8, "INT", self.data, "BIG_ENDIAN", "ERROR") BinaryAccessor.write(4.99, 16, 8, "UINT", self.data, "BIG_ENDIAN", "ERROR") @@ -569,9 +766,7 @@ def test_converts_floats_when_writing_integers(self): ) def test_converts_integer_strings_when_writing_integers(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write("1", 0, 8, "UINT", self.data, "BIG_ENDIAN", "ERROR") BinaryAccessor.write("2", 8, 8, "INT", self.data, "BIG_ENDIAN", "ERROR") BinaryAccessor.write("4", 16, 8, "UINT", self.data, "BIG_ENDIAN", "ERROR") @@ -607,12 +802,8 @@ def test_complains_about_non_integer_strings_when_writing_integers(self): class TestBinaryAccessorWriteBigEndian(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) - self.baseline_data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + self.baseline_data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") def test_writes_1_bit_unsigned_integers(self): BinaryAccessor.write(0x1, 8, 1, "UINT", self.data, "BIG_ENDIAN", "ERROR") @@ -640,9 +831,7 @@ def test_writes_7_bit_unsigned_integers(self): self.data, b"\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(0x20, 3, 7, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -655,9 +844,7 @@ def test_writes_7_bit_signed_integers(self): self.data, b"\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(32, 3, 7, "INT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -670,9 +857,7 @@ def test_writes_13_bit_unsigned_integers(self): self.data, b"\x00\x00\x00\x03\x84\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(0x0020, 1, 13, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -685,9 +870,7 @@ def test_writes_13_bit_signed_integers(self): self.data, b"\x00\x00\x00\x03\x84\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(32, 1, 13, "INT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -735,9 +918,7 @@ def test_writes_aligned_16_bit_signed_integers(self): expected = expected_array[index] if expected >= 2**15: expected = expected - 2**16 - BinaryAccessor.write( - expected, bit_offset, 16, "INT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected, bit_offset, 16, "INT", self.data, "BIG_ENDIAN", "ERROR") index += 1 self.assertEqual(self.data, self.baseline_data) @@ -777,9 +958,7 @@ def test_writes_aligned_32_bit_signed_integers(self): self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_negative_integers(self): - BinaryAccessor.write( - -2147483648, 0, 32, "INT", self.data, "BIG_ENDIAN", "ERROR_ALLOW_HEX" - ) + BinaryAccessor.write(-2147483648, 0, 32, "INT", self.data, "BIG_ENDIAN", "ERROR_ALLOW_HEX") self.assertEqual( self.data, b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @@ -787,18 +966,10 @@ def test_writes_aligned_32_bit_negative_integers(self): def test_writes_aligned_32_bit_floats(self): expected_array = [-1.189360e-038, -3.139169e-036, 8.301067e-040, 1.086646e-031] - BinaryAccessor.write( - expected_array[0], 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[1], 32, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[2], 64, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[3], 96, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected_array[0], 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[1], 32, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[2], 64, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[3], 96, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") self.assertAlmostEqual( BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN"), expected_array[0], @@ -823,30 +994,20 @@ def test_writes_aligned_32_bit_floats(self): def test_writes_simple_floats(self): data_len = len(self.data) BinaryAccessor.write(5.5, 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") - self.assertEqual( - 5.5, BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN") - ) + self.assertEqual(5.5, BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN")) self.assertEqual(data_len, len(self.data)) # buffer shouldn't grow - BinaryAccessor.write( - float("nan"), 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(float("nan"), 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") nan = BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN") self.assertTrue(math.isnan(nan)) self.assertEqual(data_len, len(self.data)) # buffer shouldn't grow - BinaryAccessor.write( - float("inf"), 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(float("inf"), 0, 32, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") inf = BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN") self.assertTrue(math.isinf(inf)) self.assertEqual(data_len, len(self.data)) # buffer shouldn't grow def test_writes_37_bit_unsigned_integers(self): - BinaryAccessor.write( - 0x8182838485 >> 3, 8, 37, "UINT", self.data, "BIG_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - 0x00090A0B0C, 67, 37, "UINT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x8182838485 >> 3, 8, 37, "UINT", self.data, "BIG_ENDIAN", "ERROR") + BinaryAccessor.write(0x00090A0B0C, 67, 37, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x81\x82\x83\x84\x80\x00\x00\x00\x09\x0A\x0B\x0C\x00\x00\x00", @@ -862,9 +1023,7 @@ def test_writes_37_bit_signed_integers(self): "BIG_ENDIAN", "ERROR", ) - BinaryAccessor.write( - 0x00090A0B0C, 67, 37, "INT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x00090A0B0C, 67, 37, "INT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x81\x82\x83\x84\x80\x00\x00\x00\x09\x0A\x0B\x0C\x00\x00\x00", @@ -872,17 +1031,13 @@ def test_writes_37_bit_signed_integers(self): def test_writes_63_bit_unsigned_integers(self): self.data[-1:] = b"\xFF" - BinaryAccessor.write( - 0x8081828384858687 >> 1, 0, 63, "UINT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x8081828384858687 >> 1, 0, 63, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x80\x81\x82\x83\x84\x85\x86\x86\x00\x00\x00\x00\x00\x00\x00\xFF", ) self.data[0:1] = b"\xFF" - BinaryAccessor.write( - 0x08090A0B0C0D0E0F, 65, 63, "UINT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x08090A0B0C0D0E0F, 65, 63, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\xFF\x81\x82\x83\x84\x85\x86\x86\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", @@ -898,18 +1053,14 @@ def test_writes_63_bit_signed_integers(self): "BIG_ENDIAN", "ERROR", ) - BinaryAccessor.write( - 0x00090A0B0C0D0E0F, 65, 63, "INT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x00090A0B0C0D0E0F, 65, 63, "INT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x80\x81\x82\x83\x84\x85\x86\x86\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F", ) def test_writes_67_bit_unsigned_integers(self): - BinaryAccessor.write( - 0x8081828384858687FF >> 5, 0, 67, "UINT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x8081828384858687FF >> 5, 0, 67, "UINT", self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x80\x81\x82\x83\x84\x85\x86\x87\xE0\x00\x00\x00\x00\x00\x00\x00", @@ -967,12 +1118,8 @@ def test_writes_aligned_64_bit_signed_integers(self): def test_writes_aligned_64_bit_floats(self): expected_array = [-3.116851e-306, 1.257060e-308] - BinaryAccessor.write( - expected_array[0], 0, 64, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[1], 64, 64, "FLOAT", self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected_array[0], 0, 64, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[1], 64, 64, "FLOAT", self.data, "BIG_ENDIAN", "ERROR") self.assertAlmostEqual( BinaryAccessor.read(0, 64, "FLOAT", self.data, "BIG_ENDIAN"), expected_array[0], @@ -1044,12 +1191,8 @@ def test_complains_about_mis_sized_floats(self): class TestBinaryAccessorWriteLittleEndian(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) - self.baseline_data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + self.baseline_data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") def test_complains_about_ill_defined_little_endian_bitfields(self): self.assertRaisesRegex( @@ -1091,9 +1234,7 @@ def test_writes_7_bit_unsigned_integers(self): self.data, b"\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(0x7F, 11, 7, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -1106,9 +1247,7 @@ def test_writes_7_bit_signed_integers(self): self.data, b"\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(32, 11, 7, "INT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -1116,16 +1255,12 @@ def test_writes_7_bit_signed_integers(self): ) def test_writes_13_bit_unsigned_integers(self): - BinaryAccessor.write( - 0x1C24, 30, 13, "UINT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x1C24, 30, 13, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x80\x84\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(0x0020, 9, 13, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -1138,9 +1273,7 @@ def test_writes_13_bit_signed_integers(self): self.data, b"\x00\x80\x84\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") BinaryAccessor.write(32, 9, 13, "INT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, @@ -1188,9 +1321,7 @@ def test_writes_aligned_16_bit_signed_integers(self): expected = expected_array[index] if expected >= 2**15: expected = expected - 2**16 - BinaryAccessor.write( - expected, bit_offset, 16, "INT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected, bit_offset, 16, "INT", self.data, "LITTLE_ENDIAN", "ERROR") index += 1 self.assertEqual(self.data, self.baseline_data) @@ -1231,18 +1362,10 @@ def test_writes_aligned_32_bit_signed_integers(self): def test_writes_aligned_32_bit_floats(self): expected_array = [-7.670445e-037, -2.024055e-034, 2.658460e-032, 7.003653e-030] - BinaryAccessor.write( - expected_array[0], 0, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[1], 32, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[2], 64, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[3], 96, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected_array[0], 0, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[1], 32, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[2], 64, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[3], 96, 32, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertAlmostEqual( BinaryAccessor.read(0, 32, "FLOAT", self.data, "LITTLE_ENDIAN"), expected_array[0], @@ -1265,33 +1388,23 @@ def test_writes_aligned_32_bit_floats(self): ) def test_writes_37_bit_unsigned_integers(self): - BinaryAccessor.write( - 0x1584838281, 43, 37, "UINT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - 0x0C0B0A0900 >> 3, 96, 37, "UINT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x1584838281, 43, 37, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(0x0C0B0A0900 >> 3, 96, 37, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x81\x82\x83\x84\x15\x00\x00\x00\x09\x0A\x0B\x0C\x00\x00\x00", ) def test_writes_37_bit_signed_integers(self): - BinaryAccessor.write( - 0x1584838281 - 2**37, 43, 37, "INT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - 0x0C0B0A0900 >> 3, 96, 37, "INT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x1584838281 - 2**37, 43, 37, "INT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(0x0C0B0A0900 >> 3, 96, 37, "INT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x81\x82\x83\x84\x15\x00\x00\x00\x09\x0A\x0B\x0C\x00\x00\x00", ) def test_writes_63_bit_unsigned_integers(self): - BinaryAccessor.write( - 0x4786858483828180, 57, 63, "UINT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x4786858483828180, 57, 63, "UINT", self.data, "LITTLE_ENDIAN", "ERROR") BinaryAccessor.write( 0x0F0E0D0C0B0A0900 >> 1, 120, @@ -1316,9 +1429,7 @@ def test_writes_63_bit_signed_integers(self): "LITTLE_ENDIAN", "ERROR", ) - BinaryAccessor.write( - 0x0F0E0D0C0B0A0900 >> 1, 120, 63, "INT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(0x0F0E0D0C0B0A0900 >> 1, 120, 63, "INT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x80\x81\x82\x83\x84\x85\x86\x47\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F", @@ -1391,12 +1502,8 @@ def test_writes_aligned_64_bit_signed_integers(self): def test_writes_aligned_64_bit_floats(self): expected_array = [-2.081577e-272, 3.691916e-236] - BinaryAccessor.write( - expected_array[0], 0, 64, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) - BinaryAccessor.write( - expected_array[1], 64, 64, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write(expected_array[0], 0, 64, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") + BinaryAccessor.write(expected_array[1], 64, 64, "FLOAT", self.data, "LITTLE_ENDIAN", "ERROR") self.assertAlmostEqual( BinaryAccessor.read(0, 64, "FLOAT", self.data, "LITTLE_ENDIAN"), expected_array[0], @@ -1439,9 +1546,7 @@ def test_complains_about_mis_sized_floats(self): class TestBinaryAccessorOverflow(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") def test_handles_invalid_overflow_types(self): self.assertRaisesRegex( @@ -1506,15 +1611,11 @@ def test_prevents_overflow_of_ints(self): ) def test_truncates_string(self): - BinaryAccessor.write( - b"abcde", 0, 32, "STRING", self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write(b"abcde", 0, 32, "STRING", self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual(self.data[0:5], b"abcd\x00") def test_truncates_block(self): - BinaryAccessor.write( - b"abcde", 0, 32, "BLOCK", self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write(b"abcde", 0, 32, "BLOCK", self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual(self.data[0:5], b"abcd\x00") def test_truncates_ints(self): @@ -1522,9 +1623,7 @@ def test_truncates_ints(self): data_type = "INT" value = 2 ** (bit_size - 1) truncated_value = -value - BinaryAccessor.write( - value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write(value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual( BinaryAccessor.read(0, bit_size, data_type, self.data, "BIG_ENDIAN"), truncated_value, @@ -1535,9 +1634,7 @@ def test_truncates_uints(self): data_type = "UINT" value = 2**bit_size + 1 truncated_value = 1 - BinaryAccessor.write( - value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write(value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual( BinaryAccessor.read(0, bit_size, data_type, self.data, "BIG_ENDIAN"), truncated_value, @@ -1551,13 +1648,9 @@ def test_saturates_ints(self): else: value = 2 ** (bit_size - 1) saturated_value = value - 1 - BinaryAccessor.write( - value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "SATURATE" - ) + BinaryAccessor.write(value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "SATURATE") self.assertEqual( - BinaryAccessor.read( - 0, bit_size, data_type, self.data, "BIG_ENDIAN" - ), + BinaryAccessor.read(0, bit_size, data_type, self.data, "BIG_ENDIAN"), saturated_value, ) if data_type == "UINT": @@ -1566,13 +1659,9 @@ def test_saturates_ints(self): else: value = -(value + 1) saturated_value = value + 1 - BinaryAccessor.write( - value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "SATURATE" - ) + BinaryAccessor.write(value, 0, bit_size, data_type, self.data, "BIG_ENDIAN", "SATURATE") self.assertEqual( - BinaryAccessor.read( - 0, bit_size, data_type, self.data, "BIG_ENDIAN" - ), + BinaryAccessor.read(0, bit_size, data_type, self.data, "BIG_ENDIAN"), saturated_value, ) @@ -1599,30 +1688,22 @@ def test_saturates_ints(self): class TestBinaryAccessorWriteArray(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") self.data_array = [] for i in range(len(self.data)): self.data_array.append(self.data[i]) - self.baseline_data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.baseline_data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") self.baseline_data_array = [] for i in range(len(self.baseline_data)): self.baseline_data_array.append(self.baseline_data[i]) def test_complains_about_value_other_than_array(self): with self.assertRaisesRegex(AttributeError, "values must be a list but is str"): - BinaryAccessor.write_array( - "", 0, 32, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array("", 0, 32, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR") def test_complains_about_unknown_data_types(self): with self.assertRaisesRegex(AttributeError, "data_type BLOB is not recognized"): - BinaryAccessor.write_array( - [0], 0, 32, "BLOB", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([0], 0, 32, "BLOB", 0, self.data, "BIG_ENDIAN", "ERROR") def test_complains_about_bit_offsets_before_the_beginning_of_the_buffer(self): with self.assertRaisesRegex( @@ -1654,18 +1735,10 @@ def test_writes_if_a_negative_bit_offset_is_equal_to_length_of_buffer(self): self.assertEqual(self.data, self.baseline_data) def test_complains_about_a_negative_or_zero_bit_size(self): - with self.assertRaisesRegex( - AttributeError, "bit_size 0 must be positive for arrays" - ): - BinaryAccessor.write_array( - [""], 0, 0, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR" - ) - with self.assertRaisesRegex( - AttributeError, "bit_size -8 must be positive for arrays" - ): - BinaryAccessor.write_array( - [""], 0, -8, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_size 0 must be positive for arrays"): + BinaryAccessor.write_array([""], 0, 0, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR") + with self.assertRaisesRegex(AttributeError, "bit_size -8 must be positive for arrays"): + BinaryAccessor.write_array([""], 0, -8, "STRING", 0, self.data, "BIG_ENDIAN", "ERROR") def test_writes_aligned_strings_with_fixed_array_size(self): data = self.data[:] @@ -1708,12 +1781,8 @@ def test_writes_strings_with_negative_bit_offsets(self): self.assertEqual(self.data, (b"\x00" * 14) + self.baseline_data[14:16]) def test_complains_about_unaligned_strings(self): - with self.assertRaisesRegex( - AttributeError, "bit_offset 1 is not byte aligned for data_type STRING" - ): - BinaryAccessor.write_array( - [], 1, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_offset 1 is not byte aligned for data_type STRING"): + BinaryAccessor.write_array([], 1, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR") def test_complains_if_pass_more_values_than_the_given_array_size_can_hold(self): with self.assertRaisesRegex( @@ -1774,9 +1843,7 @@ def test_writes_blocks_with_fixed_array_size(self): # self.assertEqual(self.data, (b"\x00" * 4) + self.baseline_data[0:-6]) def test_writes_blocks_with_zero_array_size(self): - BinaryAccessor.write_array( - self.baseline_data_array, 0, 8, "BLOCK", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(self.baseline_data_array, 0, 8, "BLOCK", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_blocks_with_negative_bit_offsets(self): @@ -1793,20 +1860,12 @@ def test_writes_blocks_with_negative_bit_offsets(self): self.assertEqual(self.data, (b"\x00" * 12) + self.baseline_data[0:4]) def test_complains_with_a_pos_array_size_not_a_multiple_of_bit_size(self): - with self.assertRaisesRegex( - AttributeError, "array_size 10 not a multiple of bit_size 8" - ): - BinaryAccessor.write_array( - [1, 2], 0, 8, "UINT", 10, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "array_size 10 not a multiple of bit_size 8"): + BinaryAccessor.write_array([1, 2], 0, 8, "UINT", 10, self.data, "BIG_ENDIAN", "ERROR") def test_complains_with_a_neg_array_size_not_a_multiple_of_bit_size(self): - with self.assertRaisesRegex( - AttributeError, "array_size -10 not a multiple of bit_size 8" - ): - BinaryAccessor.write_array( - [1, 2], 0, 8, "UINT", -10, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "array_size -10 not a multiple of bit_size 8"): + BinaryAccessor.write_array([1, 2], 0, 8, "UINT", -10, self.data, "BIG_ENDIAN", "ERROR") def test_excludes_the_remaining_bits_if_array_size_is_negative(self): data = self.data[:] @@ -1838,9 +1897,7 @@ def test_does_not_write_if_the_offset_equals_the_negative_array_size(self): def test_expands_the_buffer_to_handle_negative_array_size(self): self.data = bytearray(b"\x00\x01\x02\x00\x03") - BinaryAccessor.write_array( - [1, 2, 3, 4], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2, 3, 4], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x03", @@ -1850,43 +1907,31 @@ def test_shrinks_the_buffer_when_handling_negative_array_size(self): # Start with one array item self.data = bytearray(b"\x00\x01\x02\x00\x03") # Goto 4 array items array item - BinaryAccessor.write_array( - [1, 2, 3, 4], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2, 3, 4], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x03", ) # Goto 2 array items - BinaryAccessor.write_array( - [1, 2], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x00\x00\x00\x01\x00\x00\x00\x02\x03") # Goto 0 array items - BinaryAccessor.write_array( - [], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x03") # Go back to 1 array items - BinaryAccessor.write_array( - [1], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1], 0, 32, "UINT", -8, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x00\x00\x00\x01\x03") def test_complain_when_passed_a_zero_length_buffer(self): with self.assertRaises(AttributeError): - BinaryAccessor.write_array( - [1, 2, 3], 0, 8, "UINT", 32, b"", "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2, 3], 0, 8, "UINT", 32, b"", "LITTLE_ENDIAN", "ERROR") def test_expands_the_buffer_if_the_offset_is_greater_than_the_negative_array_size( self, ): offset = len(self.data) * 8 - 16 data = self.data[:] - BinaryAccessor.write_array( - [1, 2], offset, 8, "UINT", -32, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2], offset, 8, "UINT", -32, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, data[0:-2] + b"\x01\x02" + data[-4:]) def test_complains_with_negative_bit_offset_and_zero_array_size(self): @@ -1894,23 +1939,17 @@ def test_complains_with_negative_bit_offset_and_zero_array_size(self): AttributeError, r"negative or zero array_size \(0\) cannot be given with negative bit_offset \(-32\)", ): - BinaryAccessor.write_array( - [1, 2], -32, 8, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2], -32, 8, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR") def test_complains_with_negative_array_size(self): with self.assertRaisesRegex( AttributeError, r"negative or zero array_size \(-8\) cannot be given with negative bit_offset \(-32\)", ): - BinaryAccessor.write_array( - [1, 2], -32, 8, "UINT", -8, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 2], -32, 8, "UINT", -8, self.data, "LITTLE_ENDIAN", "ERROR") def test_writes_a_shorter_string_and_zero_fill_to_the_given_bit_size(self): - self.data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") BinaryAccessor.write_array( [b"\x01\x02", b"\x01\x02", b"\x01\x02", b"\x01\x02"], 0, @@ -1927,9 +1966,7 @@ def test_writes_a_shorter_string_and_zero_fill_to_the_given_bit_size(self): ) def test_writes_a_shorter_block_and_zero_fill_to_the_given_bit_size(self): - self.data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") BinaryAccessor.write_array( [b"\x01\x02", b"\x01\x02", b"\x01\x02", b"\x01\x02"], 0, @@ -1946,9 +1983,7 @@ def test_writes_a_shorter_block_and_zero_fill_to_the_given_bit_size(self): ) def test_complains_about_unaligned_blocks(self): - with self.assertRaisesRegex( - AttributeError, "bit_offset 7 is not byte aligned for data_type BLOCK" - ): + with self.assertRaisesRegex(AttributeError, "bit_offset 7 is not byte aligned for data_type BLOCK"): BinaryAccessor.write_array( self.baseline_data_array[0:2], 7, @@ -1965,9 +2000,7 @@ def test_complains_if_write_exceeds_the_size_of_the_buffer(self): AttributeError, "16 byte buffer insufficient to write STRING at bit_offset 8 with bit_size 800", ): - BinaryAccessor.write_array( - [], 8, 800, "STRING", 800, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([], 8, 800, "STRING", 800, self.data, "BIG_ENDIAN", "ERROR") def test_writes_aligned_8_bit_unsigned_integers(self): BinaryAccessor.write_array( @@ -1996,17 +2029,11 @@ def test_writes_aligned_8_bit_signed_integers(self): self.assertEqual(self.data, b"\x00\x01\x02\x03\x04\x05\xFF\x7F") def test_complains_about_unaligned_strings_bin(self): - with self.assertRaisesRegex( - AttributeError, "bit_offset 1 is not byte aligned for data_type STRING" - ): - BinaryAccessor.write_array( - [b"X"], 1, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_offset 1 is not byte aligned for data_type STRING"): + BinaryAccessor.write_array([b"X"], 1, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR") def test_writes_string_items(self): - BinaryAccessor.write_array( - [b"a"], 0, 64, "STRING", 0, self.baseline_data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([b"a"], 0, 64, "STRING", 0, self.baseline_data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.baseline_data, b"a\x00\x00\x00\x00\x00\x00\x00") def test_writes_block_items(self): @@ -2030,12 +2057,8 @@ def test_writes_variable_length_arrays_with_a_zero_and_negative_array_size(self) for i in range(len(self.baseline_data)): baseline_data_array_uint8.append(self.baseline_data[i]) # .ord for array_size in range(0, -(len(self.baseline_data) * 8), -8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) - self.expected_data = self.baseline_data[:] + ( - b"\x00" * -int(array_size / 8) - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + self.expected_data = self.baseline_data[:] + (b"\x00" * -int(array_size / 8)) BinaryAccessor.write_array( baseline_data_array_uint8, 0, @@ -2051,14 +2074,10 @@ def test_writes_variable_length_arrays_with_a_zero_and_negative_array_size(self) def test_writes_variable_length_arrays_or_32_bit_uints_with_a_zero_and_negative_array_size( self, ): - baseline_data = bytearray( - b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04" - ) + baseline_data = bytearray(b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04") data_array_uint32 = [0x01010101, 0x02020202, 0x03030303, 0x04040404] for array_size in range(0, -(len(self.baseline_data) * 8), -8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") self.expected_data = baseline_data[:] + (b"\x00" * -int(array_size / 8)) BinaryAccessor.write_array( data_array_uint32, @@ -2075,9 +2094,7 @@ def test_writes_variable_length_arrays_or_32_bit_uints_with_a_zero_and_negative_ def test_writes_variable_length_arrays_of_32_bit_uints_with_a_zero_and_negative_array_size_and_non_zero_bit_offset( self, ): - baseline_data = bytearray( - b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04" - ) + baseline_data = bytearray(b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04") data_array_uint32 = [0x01010101, 0x02020202, 0x03030303, 0x04040404] for array_size in range(0, -(len(self.baseline_data) * 8), -8): self.data = bytearray( @@ -2103,14 +2120,10 @@ def test_writes_variable_length_arrays_of_32_bit_uints_with_a_zero_and_negative_ def test_writes_variable_length_arrays_of_32_bit_uints_with_a_zero_and_negative_array_size_and_non_zero_bit_offset_and_grow_the_buffer( self, ): - baseline_data = bytearray( - b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04" - ) + baseline_data = bytearray(b"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04") data_array_uint32 = [0x01010101, 0x02020202, 0x03030303, 0x04040404] for array_size in range(0, -(len(self.baseline_data) * 8), -8): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") self.expected_data = ( b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + baseline_data[:] @@ -2129,24 +2142,18 @@ def test_writes_variable_length_arrays_of_32_bit_uints_with_a_zero_and_negative_ self.assertEqual(self.data, self.expected_data) def test_writes_1_bit_unsigned_integers(self): - BinaryAccessor.write_array( - [1, 0, 1], 8, 1, "UINT", 3, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 0, 1], 8, 1, "UINT", 3, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\xA0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) def test_writes_1_bit_signed_integers(self): - BinaryAccessor.write_array( - [1, 0, 1], 8, 1, "INT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 0, 1], 8, 1, "INT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x00\xA0") def test_writes_7_bit_unsigned_integers(self): - BinaryAccessor.write_array( - [0x40, 0x60, 0x50], 8, 7, "UINT", 21, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([0x40, 0x60, 0x50], 8, 7, "UINT", 21, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\x81\x82\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @@ -2154,154 +2161,94 @@ def test_writes_7_bit_unsigned_integers(self): def test_writes_aligned_16_bit_unsigned_integers(self): data = [0x8081, 0x8283, 0x8485, 0x8687, 0x0009, 0x0A0B, 0x0C0D, 0x0E0F] - BinaryAccessor.write_array( - data, 0, 16, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 16, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_16_bit_signed_integers(self): data = [0x8081, 0x8283, 0x8485, 0x8687, 0x0009, 0x0A0B, 0x0C0D, 0x0E0F] data = [(x & ~(1 << 15)) - (x & (1 << 15)) for x in data] - BinaryAccessor.write_array( - data, 0, 16, "INT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 16, "INT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_unsigned_integers(self): data = [0x80818283, 0x84858687, 0x00090A0B, 0x0C0D0E0F] - BinaryAccessor.write_array( - data, 0, 32, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 32, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_signed_integers(self): data = [0x80818283, 0x84858687, 0x00090A0B, 0x0C0D0E0F] data = [(x & ~(1 << 31)) - (x & (1 << 31)) for x in data] - BinaryAccessor.write_array( - data, 0, 32, "INT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 32, "INT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_small_32_bit_floats(self): data = [-1.189360e-038, -3.139169e-036, 8.301067e-040, 1.086646e-031] - BinaryAccessor.write_array( - data, 0, 32, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) - self.assertAlmostEqual( - BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 38 - ) - self.assertAlmostEqual( - BinaryAccessor.read(32, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 36 - ) - self.assertAlmostEqual( - BinaryAccessor.read(64, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[2], 40 - ) - self.assertAlmostEqual( - BinaryAccessor.read(96, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[3], 31 - ) + BinaryAccessor.write_array(data, 0, 32, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR") + self.assertAlmostEqual(BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 38) + self.assertAlmostEqual(BinaryAccessor.read(32, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 36) + self.assertAlmostEqual(BinaryAccessor.read(64, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[2], 40) + self.assertAlmostEqual(BinaryAccessor.read(96, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[3], 31) def test_writes_aligned_normal_32_bit_floats(self): data = [5.5, 6.6, 7.7, 8.8] - BinaryAccessor.write_array( - data, 0, 32, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) - self.assertAlmostEqual( - BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 5 - ) - self.assertAlmostEqual( - BinaryAccessor.read(32, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 5 - ) - self.assertAlmostEqual( - BinaryAccessor.read(64, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[2], 5 - ) - self.assertAlmostEqual( - BinaryAccessor.read(96, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[3], 5 - ) + BinaryAccessor.write_array(data, 0, 32, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR") + self.assertAlmostEqual(BinaryAccessor.read(0, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 5) + self.assertAlmostEqual(BinaryAccessor.read(32, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 5) + self.assertAlmostEqual(BinaryAccessor.read(64, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[2], 5) + self.assertAlmostEqual(BinaryAccessor.read(96, 32, "FLOAT", self.data, "BIG_ENDIAN"), data[3], 5) def test_writes_aligned_64_bit_unsigned_integers(self): data = [0x8081828384858687, 0x00090A0B0C0D0E0F] - BinaryAccessor.write_array( - data, 0, 64, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 64, "UINT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_64_bit_signed_integers(self): data = [0x8081828384858687, 0x00090A0B0C0D0E0F] data = [(x & ~(1 << 63)) - (x & (1 << 63)) for x in data] - BinaryAccessor.write_array( - data, 0, 64, "INT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 64, "INT", 0, self.data, "BIG_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_64_bit_floats(self): data = [-3.116851e-306, 1.257060e-308] - BinaryAccessor.write_array( - data, 0, 64, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) - self.assertAlmostEqual( - BinaryAccessor.read(0, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 306 - ) - self.assertAlmostEqual( - BinaryAccessor.read(64, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 308 - ) + BinaryAccessor.write_array(data, 0, 64, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR") + self.assertAlmostEqual(BinaryAccessor.read(0, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[0], 306) + self.assertAlmostEqual(BinaryAccessor.read(64, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[1], 308) def test_writes_normal_aligned_64_bit_floats(self): data = [3.14159, 12.3456789] - BinaryAccessor.write_array( - data, 0, 64, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR" - ) - self.assertAlmostEqual( - BinaryAccessor.read(0, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[0] - ) - self.assertAlmostEqual( - BinaryAccessor.read(64, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[1] - ) + BinaryAccessor.write_array(data, 0, 64, "FLOAT", 0, self.data, "BIG_ENDIAN", "ERROR") + self.assertAlmostEqual(BinaryAccessor.read(0, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[0]) + self.assertAlmostEqual(BinaryAccessor.read(64, 64, "FLOAT", self.data, "BIG_ENDIAN"), data[1]) def test_complains_about_unaligned_floats(self): - with self.assertRaisesRegex( - AttributeError, "bit_offset 17 is not byte aligned for data_type FLOAT" - ): - BinaryAccessor.write_array( - [0.0], 17, 32, "FLOAT", 32, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_offset 17 is not byte aligned for data_type FLOAT"): + BinaryAccessor.write_array([0.0], 17, 32, "FLOAT", 32, self.data, "BIG_ENDIAN", "ERROR") def test_complains_about_mis_sized_floats(self): - with self.assertRaisesRegex( - AttributeError, "bit_size is 33 but must be 32 or 64 for data_type FLOAT" - ): - BinaryAccessor.write_array( - [0.0], 0, 33, "FLOAT", 33, self.data, "BIG_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_size is 33 but must be 32 or 64 for data_type FLOAT"): + BinaryAccessor.write_array([0.0], 0, 33, "FLOAT", 33, self.data, "BIG_ENDIAN", "ERROR") class TestBinaryAccessorWriteArrayLittleEndian(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") self.data_array = [] for i in range(len(self.data)): self.data_array.append(self.data[i]) - self.baseline_data = bytearray( - b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F" - ) + self.baseline_data = bytearray(b"\x80\x81\x82\x83\x84\x85\x86\x87\x00\x09\x0A\x0B\x0C\x0D\x0E\x0F") self.baseline_data_array = [] for i in range(len(self.baseline_data)): self.baseline_data_array.append(self.baseline_data[i]) def test_writes_1_bit_unsigned_integers(self): - BinaryAccessor.write_array( - [1, 0, 1], 8, 1, "UINT", 3, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 0, 1], 8, 1, "UINT", 3, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual( self.data, b"\x00\xA0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ) def test_writes_1_bit_signed_integers(self): - BinaryAccessor.write_array( - [1, 0, 1], 8, 1, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array([1, 0, 1], 8, 1, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, b"\x00\xA0") def test_complains_about_little_endian_bit_fields_greater_than_1_bit(self): @@ -2322,42 +2269,30 @@ def test_complains_about_little_endian_bit_fields_greater_than_1_bit(self): def test_writes_aligned_16_bit_unsigned_integers(self): data = [0x8180, 0x8382, 0x8584, 0x8786, 0x0900, 0x0B0A, 0x0D0C, 0x0F0E] - BinaryAccessor.write_array( - data, 0, 16, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 16, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_16_bit_signed_integers(self): data = [0x8180, 0x8382, 0x8584, 0x8786, 0x0900, 0x0B0A, 0x0D0C, 0x0F0E] data = [(x & ~(1 << 15)) - (x & (1 << 15)) for x in data] - BinaryAccessor.write_array( - data, 0, 16, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 16, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_unsigned_integers(self): data = [0x83828180, 0x87868584, 0x0B0A0900, 0x0F0E0D0C] - BinaryAccessor.write_array( - data, 0, 32, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 32, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_signed_integers(self): data = [0x83828180, 0x87868584, 0x0B0A0900, 0x0F0E0D0C] data = [(x & ~(1 << 31)) - (x & (1 << 31)) for x in data] - BinaryAccessor.write_array( - data, 0, 32, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 32, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_32_bit_floats(self): data = [-7.670445e-037, -2.024055e-034, 2.658460e-032, 7.003653e-030] - BinaryAccessor.write_array( - data, 0, 32, "FLOAT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) - self.assertAlmostEqual( - BinaryAccessor.read(0, 32, "FLOAT", self.data, "LITTLE_ENDIAN"), data[0], 37 - ) + BinaryAccessor.write_array(data, 0, 32, "FLOAT", 0, self.data, "LITTLE_ENDIAN", "ERROR") + self.assertAlmostEqual(BinaryAccessor.read(0, 32, "FLOAT", self.data, "LITTLE_ENDIAN"), data[0], 37) self.assertAlmostEqual( BinaryAccessor.read(32, 32, "FLOAT", self.data, "LITTLE_ENDIAN"), data[1], @@ -2376,24 +2311,18 @@ def test_writes_aligned_32_bit_floats(self): def test_writes_aligned_64_bit_unsigned_integers(self): data = [0x8786858483828180, 0x0F0E0D0C0B0A0900] - BinaryAccessor.write_array( - data, 0, 64, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 64, "UINT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_64_bit_signed_integers(self): data = [0x8786858483828180, 0x0F0E0D0C0B0A0900] data = [(x & ~(1 << 63)) - (x & (1 << 63)) for x in data] - BinaryAccessor.write_array( - data, 0, 64, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 64, "INT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertEqual(self.data, self.baseline_data) def test_writes_aligned_64_bit_floats(self): data = [-2.081577e-272, 3.691916e-236] - BinaryAccessor.write_array( - data, 0, 64, "FLOAT", 0, self.data, "LITTLE_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(data, 0, 64, "FLOAT", 0, self.data, "LITTLE_ENDIAN", "ERROR") self.assertAlmostEqual( BinaryAccessor.read(0, 64, "FLOAT", self.data, "LITTLE_ENDIAN"), data[0], @@ -2406,53 +2335,37 @@ def test_writes_aligned_64_bit_floats(self): ) def test_complains_about_unaligned_floats(self): - with self.assertRaisesRegex( - AttributeError, "bit_offset 1 is not byte aligned for data_type FLOAT" - ): - BinaryAccessor.write_array( - [0.0], 1, 32, "FLOAT", 32, self.data, "LITTLE_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_offset 1 is not byte aligned for data_type FLOAT"): + BinaryAccessor.write_array([0.0], 1, 32, "FLOAT", 32, self.data, "LITTLE_ENDIAN", "ERROR") def test_complains_about_mis_sized_floats(self): - with self.assertRaisesRegex( - AttributeError, "bit_size is 65 but must be 32 or 64 for data_type FLOAT" - ): - BinaryAccessor.write_array( - [0.0], 0, 65, "FLOAT", 65, self.data, "LITTLE_ENDIAN", "ERROR" - ) + with self.assertRaisesRegex(AttributeError, "bit_size is 65 but must be 32 or 64 for data_type FLOAT"): + BinaryAccessor.write_array([0.0], 0, 65, "FLOAT", 65, self.data, "LITTLE_ENDIAN", "ERROR") class TestBinaryAccessorWriteOverflow(unittest.TestCase): def setUp(self): - self.data = bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - ) + self.data = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") def test_prevents_overflow_of_string(self): with self.assertRaisesRegex( AttributeError, "value of 5 bytes does not fit into 4 bytes for data_type STRING", ): - BinaryAccessor.write_array( - ["abcde"], 0, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(["abcde"], 0, 32, "STRING", 32, self.data, "BIG_ENDIAN", "ERROR") def test_prevents_overflow_of_block(self): with self.assertRaisesRegex( AttributeError, "value of 5 bytes does not fit into 4 bytes for data_type BLOCK", ): - BinaryAccessor.write_array( - ["abcde"], 0, 32, "BLOCK", 32, self.data, "BIG_ENDIAN", "ERROR" - ) + BinaryAccessor.write_array(["abcde"], 0, 32, "BLOCK", 32, self.data, "BIG_ENDIAN", "ERROR") def test_prevents_overflow_of_8_bit_int(self): bit_size = 8 data_type = "INT" value = 2 ** (bit_size - 1) - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2468,9 +2381,7 @@ def test_prevents_overflow_of_16_bit_int(self): bit_size = 16 data_type = "INT" value = 2 ** (bit_size - 1) - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2486,9 +2397,7 @@ def test_prevents_overflow_of_32_bit_int(self): bit_size = 32 data_type = "INT" value = 2 ** (bit_size - 1) - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2504,9 +2413,7 @@ def test_prevents_overflow_of_64_bit_int(self): bit_size = 64 data_type = "INT" value = 2 ** (bit_size - 1) - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2522,9 +2429,7 @@ def test_prevents_overflow_of_3_bit_int(self): bit_size = 3 data_type = "INT" value = 2 ** (bit_size - 1) - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2540,9 +2445,7 @@ def test_prevents_overflow_of_8_bit_uint(self): bit_size = 8 data_type = "UINT" value = 2**bit_size - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2558,9 +2461,7 @@ def test_prevents_overflow_of_16_bit_uint(self): bit_size = 16 data_type = "UINT" value = 2**bit_size - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2576,9 +2477,7 @@ def test_prevents_overflow_of_32_bit_uint(self): bit_size = 32 data_type = "UINT" value = 2**bit_size - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2594,9 +2493,7 @@ def test_prevents_overflow_of_64_bit_uint(self): bit_size = 64 data_type = "UINT" value = 2**bit_size - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2612,9 +2509,7 @@ def test_prevents_overflow_of_3_bit_uint(self): bit_size = 3 data_type = "UINT" value = 2**bit_size - with self.assertRaisesRegex( - AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}" - ): + with self.assertRaisesRegex(AttributeError, f"value of {value} invalid for {bit_size}-bit {data_type}"): BinaryAccessor.write_array( [value], 0, @@ -2627,15 +2522,11 @@ def test_prevents_overflow_of_3_bit_uint(self): ) def test_truncates_string(self): - BinaryAccessor.write_array( - [b"abcde"], 0, 32, "STRING", 32, self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write_array([b"abcde"], 0, 32, "STRING", 32, self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual(self.data[0:5], b"abcd\x00") def test_truncates_block(self): - BinaryAccessor.write_array( - [b"abcde"], 0, 32, "BLOCK", 32, self.data, "BIG_ENDIAN", "TRUNCATE" - ) + BinaryAccessor.write_array([b"abcde"], 0, 32, "BLOCK", 32, self.data, "BIG_ENDIAN", "TRUNCATE") self.assertEqual(self.data[0:5], b"abcd\x00") def test_truncates_ints(self): diff --git a/openc3/python/test/packets/test_packet_config.py b/openc3/python/test/packets/test_packet_config.py index 937d6d0924..49ea0be6cb 100644 --- a/openc3/python/test/packets/test_packet_config.py +++ b/openc3/python/test/packets/test_packet_config.py @@ -111,12 +111,19 @@ def test_creates_unknown_cmd_tlm_packets(self): "SEG_POLY_WRITE_CONVERSION", "GENERIC_READ_CONVERSION_START", "GENERIC_WRITE_CONVERSION_START", + "REQUIRED", "LIMITS", "LIMITS_RESPONSE", "UNITS", "FORMAT_STRING", "DESCRIPTION", - "META", + "MINIMUM_VALUE", + "MAXIMUM_VALUE", + "DEFAULT_VALUE", + "OVERFLOW", + "OVERLAP", + "KEY", + "VARIABLE_BIT_SIZE", ] def test_complains_if_a_current_packet_is_not_defined(self): @@ -132,9 +139,6 @@ def test_complains_if_a_current_packet_is_not_defined(self): def test_complains_if_a_current_item_is_not_defined(self): # Check for missing ITEM definitions for keyword in TestPacketConfig.item_keywords: - if keyword == "META": - continue - tf = tempfile.NamedTemporaryFile(mode="w") tf.write('TELEMETRY tgt1 pkt1 LITTLE_ENDIAN "Packet"\n') tf.write(keyword) @@ -162,7 +166,12 @@ def test_complains_if_there_are_not_enough_parameters(self): tf.close() for keyword in TestPacketConfig.item_keywords: - if keyword == "GENERIC_READ_CONVERSION_START" or keyword == "GENERIC_WRITE_CONVERSION_START": + ignore = ["GENERIC_READ_CONVERSION_START", "GENERIC_WRITE_CONVERSION_START"] + # The following have 0 parameters + ignore.append("OVERLAP") + # The following are command only + ignore.extend(["REQUIRED", "MINIMUM_VALUE", "MAXIMUM_VALUE", "DEFAULT_VALUE"]) + if keyword in ignore: continue tf = tempfile.NamedTemporaryFile(mode="w") @@ -253,6 +262,11 @@ def test_complains_if_there_are_too_many_parameters(self): "SEG_POLY_WRITE_CONVERSION", "LIMITS_RESPONSE", "META", + # The following are command only + "REQUIRED", + "MINIMUM_VALUE", + "MAXIMUM_VALUE", + "DEFAULT_VALUE", ]: continue @@ -263,14 +277,19 @@ def test_complains_if_there_are_too_many_parameters(self): case "STATE": tf.write("STATE mystate 0 RED extra\n") case "GENERIC_READ_CONVERSION_START" | "GENERIC_WRITE_CONVERSION_START": - tf.write(f"{keyword} FLOAT 64 extra") + tf.write(f"{keyword} FLOAT 64 extra\n") case "LIMITS": tf.write("LIMITS mylimits 1 ENABLED 0 10 20 30 12 18 20\n") case "UNITS": tf.write("UNITS degrees deg extra\n") - case "FORMAT_STRING" | "DESCRIPTION": - tf.write(f"{keyword} 'string' extra") + case "FORMAT_STRING" | "DESCRIPTION" | "OVERFLOW" | "KEY": + tf.write(f"{keyword} 'string' extra\n") + case "VARIABLE_BIT_SIZE": + tf.write(f"{keyword} LEN 8 0 extra\n") + case _: + tf.write(f"{keyword} extra\n") tf.seek(0) + print(keyword) with self.assertRaisesRegex(ConfigParser.Error, f"Too many parameters for {keyword}"): self.pc.process_file(tf.name, "TGT1") tf.close() @@ -510,6 +529,17 @@ def test_marks_the_packet_as_disabled(self): self.assertFalse(self.pc.commands["TGT1"]["PKT2"].disabled) tf.close() + def test_marks_the_packet_as_a_virtual_packet(self): + tf = tempfile.NamedTemporaryFile(mode="w") + tf.write('TELEMETRY tgt1 pkt1 LITTLE_ENDIAN "Description"\n') + tf.write("VIRTUAL\n") + tf.seek(0) + self.pc.process_file(tf.name, "TGT1") + self.assertTrue(self.pc.telemetry["TGT1"]["PKT1"].hidden) + self.assertTrue(self.pc.telemetry["TGT1"]["PKT1"].disabled) + self.assertTrue(self.pc.telemetry["TGT1"]["PKT1"].virtual) + tf.close() + def test_sets_the_accessor_for_the_packet(self): tf = tempfile.NamedTemporaryFile(mode="w") tf.write('TELEMETRY tgt1 pkt1 LITTLE_ENDIAN "Description"\n') @@ -1096,6 +1126,17 @@ def test_sets_the_overflow_type_for_items(self): ) tf.close() + def test_sets_the_variable_bit_size_variables(self): + tf = tempfile.NamedTemporaryFile(mode="w") + tf.write('TELEMETRY tgt1 pkt1 LITTLE_ENDIAN "Packet"\n') + tf.write(" ITEM item1 0 8 UINT\n") + tf.write(" VARIABLE_BIT_SIZE LEN 16 8\n") + tf.seek(0) + self.pc.process_file(tf.name, "TGT1") + vbs = {"length_item_name": "LEN", "length_bits_per_count": 16, "length_value_bit_offset": 8} + self.assertEqual(self.pc.telemetry["TGT1"]["PKT1"].get_item("item1").variable_bit_size, vbs) + tf.close() + def test_allows_item_overlap(self): tf = tempfile.NamedTemporaryFile(mode="w") tf.write('TELEMETRY tgt1 pkt2 LITTLE_ENDIAN "Packet"\n') diff --git a/openc3/python/test/packets/test_structure.py b/openc3/python/test/packets/test_structure.py index 14bd45a00f..dc2d0d8407 100644 --- a/openc3/python/test/packets/test_structure.py +++ b/openc3/python/test/packets/test_structure.py @@ -268,28 +268,6 @@ def test_appends_an_item_after_an_array_item(self): self.assertEqual(self.s.sorted_items[1].name, "TEST2") self.assertEqual(self.s.defined_length, 4) - def test_complains_if_appending_after_a_variably_sized_item(self): - self.s.define_item("test1", 0, 0, "BLOCK") - self.assertRaisesRegex( - AttributeError, - "Can't append an item after a variably sized item", - self.s.append_item, - "test2", - 8, - "UINT", - ) - - def test_complains_if_appending_after_a_variably_sized_array(self): - self.s.define_item("test1", 0, 8, "UINT", -8) - self.assertRaisesRegex( - AttributeError, - "Can't append an item after a variably sized item", - self.s.append_item, - "test2", - 8, - "UINT", - ) - class TestStructureAppend(unittest.TestCase): def setUp(self): @@ -305,16 +283,6 @@ def test_appends_an_item_to_the_structure(self): self.assertEqual(self.s.sorted_items[1].name, "TEST2") self.assertEqual(self.s.defined_length, 3) - def test_complains_if_appending_after_a_variably_sized_define_item(self): - self.s.define_item("test1", 0, 0, "BLOCK") - item = StructureItem("test2", 0, 16, "UINT", "BIG_ENDIAN") - self.assertRaisesRegex( - AttributeError, - "Can't append an item after a variably sized item", - self.s.append, - item, - ) - class TestStructureGetItem(unittest.TestCase): def setUp(self): @@ -325,9 +293,7 @@ def test_returns_a_defined_item(self): self.assertIsNotNone(self.s.get_item("test1")) def test_complains_if_an_item_doesnt_exist(self): - self.assertRaisesRegex( - AttributeError, "Unknown item: test2", self.s.get_item, "test2" - ) + self.assertRaisesRegex(AttributeError, "Unknown item: test2", self.s.get_item, "test2") class TestStructureSetItem(unittest.TestCase): @@ -362,9 +328,7 @@ def test_removes_the_item_and_leaves_a_hole(self): self.s.append_item("test2", 16, "UINT") self.assertEqual(self.s.defined_length, 3) self.s.delete_item("test1") - self.assertRaisesRegex( - AttributeError, "Unknown item: test1", self.s.get_item, "test1" - ) + self.assertRaisesRegex(AttributeError, "Unknown item: test1", self.s.get_item, "test1") self.assertEqual(self.s.defined_length, 3) self.assertIsNone(self.s.items.get("TEST1")) self.assertIsNotNone(self.s.items["TEST2"]) @@ -447,9 +411,7 @@ def test_writes_array_data_to_the_buffer(self): class TestStructureRead(unittest.TestCase): def test_complains_if_item_doesnt_exist(self): - self.assertRaisesRegex( - AttributeError, "Unknown item: BLAH", Structure().read, "BLAH" - ) + self.assertRaisesRegex(AttributeError, "Unknown item: BLAH", Structure().read, "BLAH") def test_reads_data_from_the_buffer(self): s = Structure() @@ -600,17 +562,13 @@ def test_returns_the_buffer(self): def test_complains_if_the_given_buffer_is_too_small(self): s = Structure("BIG_ENDIAN") s.append_item("test1", 16, "UINT") - with self.assertRaisesRegex( - AttributeError, "Buffer length less than defined length" - ): + with self.assertRaisesRegex(AttributeError, "Buffer length less than defined length"): s.buffer = b"\x00" def test_complains_if_the_given_buffer_is_too_big(self): s = Structure("BIG_ENDIAN") s.append_item("test1", 16, "UINT") - with self.assertRaisesRegex( - AttributeError, "Buffer length greater than defined length" - ): + with self.assertRaisesRegex(AttributeError, "Buffer length greater than defined length"): s.buffer = b"\x00\x00\x00" def test_does_not_complain_if_the_given_buffer_is_too_big_and_were_not_fixed_length( diff --git a/openc3/python/test/packets/test_structure_item.py b/openc3/python/test/packets/test_structure_item.py index 454e4d91c8..5638a5dd27 100644 --- a/openc3/python/test/packets/test_structure_item.py +++ b/openc3/python/test/packets/test_structure_item.py @@ -1,4 +1,4 @@ -# Copyright 2023 OpenC3, Inc. +# Copyright 2024 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -22,9 +22,7 @@ class TestStructureItem(unittest.TestCase): def test_name_creates_new_structure_items(self): - self.assertEqual( - StructureItem("test", 0, 8, "UINT", "BIG_ENDIAN", None).name, "TEST" - ) + self.assertEqual(StructureItem("test", 0, 8, "UINT", "BIG_ENDIAN", None).name, "TEST") def test_name_complains_about_non_string_names(self): self.assertRaisesRegex( @@ -88,9 +86,7 @@ def test_complains_about_bad_endianness(self): def test_accepts_data_types(self): for type in ["INT", "UINT", "FLOAT", "STRING", "BLOCK"]: - self.assertEqual( - StructureItem("test", 0, 32, type, "BIG_ENDIAN", None).data_type, type - ) + self.assertEqual(StructureItem("test", 0, 32, type, "BIG_ENDIAN", None).data_type, type) self.assertEqual( StructureItem("test", 0, 0, "DERIVED", "BIG_ENDIAN", None).data_type, @@ -184,19 +180,18 @@ def test_complains_about_bad_bit_sizes_types(self): None, ) - def test_complains_about_0_size_int_uint_and_float(self): - for type in ["INT", "UINT", "FLOAT"]: - self.assertRaisesRegex( - AttributeError, - "TEST: bit_size cannot be negative or zero for 'INT', 'UINT', and 'FLOAT' items: 0", - StructureItem, - "TEST", - 0, - 0, - type, - "BIG_ENDIAN", - None, - ) + def test_complains_about_0_size_floats(self): + self.assertRaisesRegex( + AttributeError, + "TEST: bit_size cannot be negative or zero for 'FLOAT' items: 0", + StructureItem, + "TEST", + 0, + 0, + "FLOAT", + "BIG_ENDIAN", + None, + ) def test_complains_about_bad_float_bit_sizes(self): self.assertRaisesRegex( @@ -212,12 +207,8 @@ def test_complains_about_bad_float_bit_sizes(self): ) def test_creates_32_and_64_bit_floats(self): - self.assertEqual( - StructureItem("test", 0, 32, "FLOAT", "BIG_ENDIAN", None).bit_size, 32 - ) - self.assertEqual( - StructureItem("test", 0, 64, "FLOAT", "BIG_ENDIAN", None).bit_size, 64 - ) + self.assertEqual(StructureItem("test", 0, 32, "FLOAT", "BIG_ENDIAN", None).bit_size, 32) + self.assertEqual(StructureItem("test", 0, 64, "FLOAT", "BIG_ENDIAN", None).bit_size, 64) def test_complains_about_non_zero_derived_bit_sizes(self): self.assertRaisesRegex( diff --git a/openc3/spec/packets/packet_config_spec.rb b/openc3/spec/packets/packet_config_spec.rb index d9c2b1ff3b..bac46d8565 100644 --- a/openc3/spec/packets/packet_config_spec.rb +++ b/openc3/spec/packets/packet_config_spec.rb @@ -789,7 +789,7 @@ module OpenC3 end end - context "with READ_CONVERSION and WRITE_CONVERSION" do + context "with READ_CONVERSION and WRITE_CONVERSION", no_ext: true do it "complains about missing conversion file" do filename = File.join(File.dirname(__FILE__), "../test_only.rb") File.delete(filename) if File.exist?(filename) @@ -800,7 +800,7 @@ module OpenC3 tf.puts ' ITEM item1 0 16 INT "Integer Item"' tf.puts ' READ_CONVERSION test_only.rb' tf.close - expect { @pc.process_file(tf.path, "TGT1") }.to raise_error(ConfigParser::Error, /Unable to require test_only.rb due to cannot load such file -- test_only.rb. Ensure test_only.rb is in the OpenC3 lib directory./) + expect { @pc.process_file(tf.path, "TGT1") }.to raise_error(/Unable to require test_only.rb due to cannot load such file -- test_only.rb. Ensure test_only.rb is in the OpenC3 lib directory./) tf.unlink tf = Tempfile.new('unittest') @@ -808,7 +808,7 @@ module OpenC3 tf.puts ' PARAMETER item1 0 16 INT 0 0 0' tf.puts ' WRITE_CONVERSION test_only.rb' tf.close - expect { @pc.process_file(tf.path, "TGT1") }.to raise_error(ConfigParser::Error, /Unable to require test_only.rb due to cannot load such file -- test_only.rb. Ensure test_only.rb is in the OpenC3 lib directory./) + expect { @pc.process_file(tf.path, "TGT1") }.to raise_error(/Unable to require test_only.rb due to cannot load such file -- test_only.rb. Ensure test_only.rb is in the OpenC3 lib directory./) tf.unlink end diff --git a/openc3/spec/packets/packet_spec.rb b/openc3/spec/packets/packet_spec.rb index 0dcf785d0a..13bd31b289 100644 --- a/openc3/spec/packets/packet_spec.rb +++ b/openc3/spec/packets/packet_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2024, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -27,6 +27,17 @@ module OpenC3 describe Packet do + describe "initialize", no_ext: true do + it "initializes the instance variables" do + pkt = Packet.new("TGT", "PKT", :LITTLE_ENDIAN, "Description", "\x00\x01") + expect(pkt.target_name).to eql "TGT" + expect(pkt.packet_name).to eql "PKT" + expect(pkt.default_endianness).to eql :LITTLE_ENDIAN + expect(pkt.description).to eql "Description" + expect(pkt.buffer).to eql "\x00\x01" + end + end + describe "template=" do it "sets the template" do p = Packet.new("tgt", "pkt") @@ -374,7 +385,7 @@ module OpenC3 end describe "read and read_item" do - before (:each) do + before(:each) do @p = Packet.new("tgt", "pkt") end @@ -605,7 +616,7 @@ module OpenC3 end describe "write and write_item" do - before (:each) do + before(:each) do @p = Packet.new("tgt", "pkt") @buffer = "\x00\x00\x00\x00" end @@ -824,26 +835,26 @@ module OpenC3 p.write("test4", "Test") expect(p.formatted).to include("TEST1: [1, 2]") expect(p.formatted).to include("TEST2: TRUE") - expect(p.formatted).to include("TEST3: #{0x02030405}") + expect(p.formatted).to include("TEST3: 33752069") expect(p.formatted).to include("TEST4: Test") # Test the data_type parameter expect(p.formatted(:RAW)).to include("TEST1: [1, 2]") - expect(p.formatted(:RAW)).to include("TEST2: #{0x0304}") - expect(p.formatted(:RAW)).to include("TEST3: #{0x0406080A}") + expect(p.formatted(:RAW)).to include("TEST2: 772") + expect(p.formatted(:RAW)).to include("TEST3: 67504138") expect(p.formatted(:RAW)).to include("00000000: 54 65 73 74") # Raw TEST4 block # Test the indent parameter expect(p.formatted(:CONVERTED, 4)).to include(" TEST1: [1, 2]") # Test the buffer parameter buffer = "\x02\x03\x04\x05\x00\x00\x00\x02\x44\x45\x41\x44" expect(p.formatted(:CONVERTED, 0, buffer)).to include("TEST1: [2, 3]") - expect(p.formatted(:CONVERTED, 0, buffer)).to include("TEST2: #{0x0405}") + expect(p.formatted(:CONVERTED, 0, buffer)).to include("TEST2: 1029") expect(p.formatted(:CONVERTED, 0, buffer)).to include("TEST3: 1") expect(p.formatted(:CONVERTED, 0, buffer)).to include("TEST4: DEAD") # Test the ignored parameter string = p.formatted(:CONVERTED, 0, p.buffer, %w(TEST1 TEST4)) expect(string).not_to include("TEST1") expect(string).to include("TEST2: TRUE") - expect(string).to include("TEST3: #{0x02030405}") + expect(string).to include("TEST3: 33752069") expect(string).not_to include("TEST4") end end diff --git a/openc3/spec/spec_helper.rb b/openc3/spec/spec_helper.rb index 6967caa0ca..36b198edae 100644 --- a/openc3/spec/spec_helper.rb +++ b/openc3/spec/spec_helper.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2024, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -29,6 +29,8 @@ # Kernel.load(file, wrap) # end +require 'rspec' + # NOTE: You MUST require simplecov before anything else! if !ENV['OPENC3_NO_SIMPLECOV'] require 'simplecov' @@ -38,6 +40,13 @@ else SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter end + # Explicitly set the command_name so we don't clobber the results + # of the regular run with the '--tag no_ext' run + name = "test:unit" + if RSpec.configuration.filter_manager.inclusions.rules[:no_ext] + name += ':no_ext' + end + SimpleCov.command_name name SimpleCov.start do merge_timeout 60 * 60 # merge the last hour of results add_filter '/spec/' # no coverage on spec files @@ -52,7 +61,6 @@ SimpleCov.result.format! end end -require 'rspec' # Disable Redis and Fluentd in the Logger ENV['OPENC3_NO_STORE'] = 'true' @@ -99,11 +107,11 @@ def initialize(update_interval) end alias old_method_missing method_missing - def method_missing(message, *args, **kwargs, &block) + def method_missing(message, *args, **kwargs, &) if $store_queued - old_method_missing(message, *args, **kwargs, &block) + old_method_missing(message, *args, **kwargs, &) else - @store.public_send(message, *args, **kwargs, &block) + @store.public_send(message, *args, **kwargs, &) end end end @@ -140,10 +148,10 @@ class MockRedis module StreamMethods private - def with_stream_at(key, &blk) + def with_stream_at(key, &) @mutex ||= Mutex.new @mutex.synchronize do - return with_thing_at(key, :assert_streamy, proc { Stream.new }, &blk) + return with_thing_at(key, :assert_streamy, proc { Stream.new }, &) end end From e6f6bd6862c7b15703e22483494d9a5b12d89f3f Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Wed, 14 Aug 2024 12:22:37 -0600 Subject: [PATCH 2/7] Fix ruff --- openc3/python/openc3/accessors/binary_accessor.py | 1 - openc3/python/test/accessors/test_binary_accessor_write.py | 1 - openc3/python/test/packets/test_packet_config.py | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openc3/python/openc3/accessors/binary_accessor.py b/openc3/python/openc3/accessors/binary_accessor.py index c8defb0666..93b90a1bd3 100644 --- a/openc3/python/openc3/accessors/binary_accessor.py +++ b/openc3/python/openc3/accessors/binary_accessor.py @@ -18,7 +18,6 @@ import math import struct from .accessor import Accessor -from openc3.utilities.string import simple_formatted class BinaryAccessor(Accessor): diff --git a/openc3/python/test/accessors/test_binary_accessor_write.py b/openc3/python/test/accessors/test_binary_accessor_write.py index c63bdd7099..50a490767a 100644 --- a/openc3/python/test/accessors/test_binary_accessor_write.py +++ b/openc3/python/test/accessors/test_binary_accessor_write.py @@ -21,7 +21,6 @@ from test.test_helper import * from openc3.accessors.binary_accessor import BinaryAccessor from openc3.packets.packet import Packet -from openc3.utilities.string import simple_formatted class TestBinaryAccessorWrite(unittest.TestCase): diff --git a/openc3/python/test/packets/test_packet_config.py b/openc3/python/test/packets/test_packet_config.py index 49ea0be6cb..abe2d4267d 100644 --- a/openc3/python/test/packets/test_packet_config.py +++ b/openc3/python/test/packets/test_packet_config.py @@ -20,7 +20,7 @@ from test.test_helper import * from openc3.config.config_parser import ConfigParser from openc3.packets.packet_config import PacketConfig -from cbor2 import dump, dumps, loads +from cbor2 import dump, loads class TestPacketConfig(unittest.TestCase): @@ -823,7 +823,7 @@ def test_sets_the_template_via_file(self): def test_handles_bad_tesmplate_files(self): tf = tempfile.NamedTemporaryFile(mode="w") tf.write('TELEMETRY tgt1 pkt1 LITTLE_ENDIAN "Description"\n') - tf.write(f"TEMPLATE_FILE nope.txt\n") + tf.write("TEMPLATE_FILE nope.txt\n") tf.seek(0) with self.assertRaisesRegex(ConfigParser.Error, "No such file or directory"): self.pc.process_file(tf.name, "TGT1") From 98505cd34f4c50466c9bf366ab360eaa3af809d7 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Wed, 14 Aug 2024 13:44:41 -0600 Subject: [PATCH 3/7] Change type to isinstance throughout --- .../targets/INST2/procedures/collect.py | 2 +- .../scripts/run_script.py | 4 +- .../scripts/running_script.py | 8 +- .../openc3/accessors/binary_accessor.py | 219 +++++++++--------- .../python/openc3/accessors/cbor_accessor.py | 6 +- .../python/openc3/accessors/json_accessor.py | 12 +- openc3/python/openc3/api/cmd_api.py | 4 +- openc3/python/openc3/api/interface_api.py | 2 +- openc3/python/openc3/api/limits_api.py | 2 +- openc3/python/openc3/api/tlm_api.py | 4 +- openc3/python/openc3/config/config_parser.py | 8 +- .../interfaces/protocols/burst_protocol.py | 2 +- .../interfaces/protocols/cobs_protocol.py | 2 +- .../interfaces/protocols/crc_protocol.py | 4 +- .../interfaces/protocols/slip_protocol.py | 2 +- .../interfaces/protocols/template_protocol.py | 2 +- openc3/python/openc3/io/json_api_object.py | 28 +-- openc3/python/openc3/io/json_drb_object.py | 2 +- openc3/python/openc3/io/json_rpc.py | 2 +- .../microservices/interface_microservice.py | 2 +- openc3/python/openc3/models/cvt_model.py | 2 +- openc3/python/openc3/models/model.py | 2 +- openc3/python/openc3/packets/commands.py | 4 +- openc3/python/openc3/packets/packet.py | 28 +-- openc3/python/openc3/packets/packet_config.py | 2 +- openc3/python/openc3/packets/packet_item.py | 71 ++---- .../openc3/packets/packet_item_limits.py | 2 +- .../packets/parsers/processor_parser.py | 5 +- openc3/python/openc3/packets/structure.py | 2 +- .../python/openc3/packets/structure_item.py | 24 +- openc3/python/openc3/script/api_shared.py | 10 +- openc3/python/openc3/script/commands.py | 4 +- openc3/python/openc3/script/metadata.py | 4 +- openc3/python/openc3/script/suite.py | 8 +- openc3/python/openc3/script/suite_results.py | 2 +- .../openc3/topics/limits_event_topic.py | 2 +- openc3/python/openc3/utilities/crc.py | 4 +- openc3/python/openc3/utilities/local_mode.py | 2 +- openc3/python/openc3/utilities/store.py | 4 +- 39 files changed, 232 insertions(+), 267 deletions(-) diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/collect.py b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/collect.py index bb3260a607..756002f575 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/collect.py +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/collect.py @@ -11,7 +11,7 @@ if not isinstance(number, (int, float)): raise RuntimeError("Bad return") number = ask_string("Enter a number.") -if not type(number) == str: +if not isinstance(number, str): raise RuntimeError("Bad return") result = message_box("Click something.", "CHOICE1", "CHOICE2") diff --git a/openc3-cosmos-script-runner-api/scripts/run_script.py b/openc3-cosmos-script-runner-api/scripts/run_script.py index d9bfec1524..b62e4885a8 100644 --- a/openc3-cosmos-script-runner-api/scripts/run_script.py +++ b/openc3-cosmos-script-runner-api/scripts/run_script.py @@ -130,7 +130,7 @@ def run_script_log(id, message, color="BLACK", message_log=True): for msg in p.listen(): parsed_cmd = json.loads(msg["data"]) if not parsed_cmd == "shutdown" or ( - type(parsed_cmd) is dict and parsed_cmd["method"] + isinstance(parsed_cmd, dict) and parsed_cmd["method"] ): run_script_log(id, f"Script {path} received command: {msg['data']}") match parsed_cmd: @@ -148,7 +148,7 @@ def run_script_log(id, message, color="BLACK", message_log=True): case "shutdown": p.unsubscribe() case _: - if type(parsed_cmd) is dict and "method" in parsed_cmd: + if isinstance(parsed_cmd, dict) and "method" in parsed_cmd: match parsed_cmd["method"]: # This list matches the list in running_script.py:113 case ( diff --git a/openc3-cosmos-script-runner-api/scripts/running_script.py b/openc3-cosmos-script-runner-api/scripts/running_script.py index 5bed2671cd..ac7c1a67f5 100644 --- a/openc3-cosmos-script-runner-api/scripts/running_script.py +++ b/openc3-cosmos-script-runner-api/scripts/running_script.py @@ -527,8 +527,8 @@ def post_line_instrumentation(self, filename, line_number): def exception_instrumentation(self, filename, line_number): _, error, _ = sys.exc_info() if ( - issubclass(error.__class__, StopScript) - or issubclass(error.__class__, SkipScript) + isinstance(error, StopScript) + or isinstance(error, SkipScript) or not self.use_instrumentation ): raise error @@ -994,9 +994,7 @@ def run_thread_body( ) except Exception as error: - if issubclass(error.__class__, StopScript) or issubclass( - error.__class__, SkipScript - ): + if isinstance(error, StopScript) or isinstance(error, SkipScript): self.handle_output_io() self.scriptrunner_puts( f"Script stopped: {os.path.basename(self.filename)}" diff --git a/openc3/python/openc3/accessors/binary_accessor.py b/openc3/python/openc3/accessors/binary_accessor.py index 93b90a1bd3..fe1fa849de 100644 --- a/openc3/python/openc3/accessors/binary_accessor.py +++ b/openc3/python/openc3/accessors/binary_accessor.py @@ -151,114 +151,17 @@ def class_read_item(cls, item, buffer): return cls.read(item.bit_offset, item.bit_size, item.data_type, buffer, item.endianness) def handle_write_variable_bit_size(self, item, value, buffer): + adjustment = 0 # Update length field to new size if (item.data_type == "INT" or item.data_type == "UINT") and item.original_array_size is None: - # QUIC encoding is currently assumed for individual variable sized integers - # see https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc - - # Calculate current bit size so we can preserve bytes after the item - length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") - match length_item_value: - case 0: - current_bit_size = 6 - case 1: - current_bit_size = 14 - case 2: - current_bit_size = 30 - case 3: - current_bit_size = 62 - case _: - raise RuntimeError( - f"Value {item.variable_bit_size['length_item_name']} has unknown QUIC bit size encoding: {length_item_value}" - ) - - if item.data_type == "UINT": - if value <= 63: - # Length = 0, value up to 6-bits - new_bit_size = 6 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 0) - elif value <= 16383: - # Length = 1, value up to 14-bits - new_bit_size = 14 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 1) - elif value <= 1073741823: - # Length = 2, value up to 30-bits - new_bit_size = 30 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 2) - else: - # Length = 3, value up to 62-bits - new_bit_size = 62 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 3) - else: - if value <= 31 and value >= -32: - # Length = 0, value up to 6-bits - new_bit_size = 6 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 0) - elif value <= 8191 and value >= -8192: - # Length = 1, value up to 14-bits - new_bit_size = 14 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 1) - elif value <= 536870911 and value >= -536870912: - # Length = 2, value up to 30-bits - new_bit_size = 30 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 2) - else: - # Length = 3, value up to 62-bits - new_bit_size = 62 - item.bit_size = new_bit_size - self.packet.write(item.variable_bit_size["length_item_name"], 3) - - # Later items need their bit_offset adjusted by the change in this item - adjustment = new_bit_size - current_bit_size - bytes = int(adjustment / 8) - item_offset = int(item.bit_offset / 8) - if bytes > 0: - original_length = len(buffer) - # Add extra bytes because we're adjusting larger - buffer += BinaryAccessor.ZERO_STRING * bytes - # We added bytes to the end so now we have to shift the buffer over - # We copy from the original offset with the original length - # to the new shifted offset all the way to the end of the buffer - buffer[(item_offset + bytes) :] = buffer[item_offset:original_length] - elif bytes < 0: - # Remove extra bytes because we're adjusting smaller - del buffer[item_offset + 1 : item_offset + 1 - bytes] + adjustment = self._write_variable_int(item, value, buffer) # Probably not possible to get this condition because we don't allow 0 sized floats # but check for it just to cover all the possible data_types elif item.data_type == "FLOAT": - raise "Variable bit size not currently supported for FLOAT data type" + raise AttributeError("Variable bit size not currently supported for FLOAT data type") else: # STRING, BLOCK, or array types - - # Calculate current bit size so we can preserve bytes after the item - length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") - current_bit_size = ( - length_item_value * item.variable_bit_size["length_bits_per_count"] - ) + item.variable_bit_size["length_value_bit_offset"] - - # Calculate bits after this item - bits_with_item = item.bit_offset + current_bit_size - bits_after_item = (len(buffer) * 8) - bits_with_item - if item.original_array_size is not None: - item.array_size = -bits_after_item - else: - item.bit_size = -bits_after_item - - new_bit_size = len(value) * 8 - length_value = (new_bit_size - item.variable_bit_size["length_value_bit_offset"]) / item.variable_bit_size[ - "length_bits_per_count" - ] - self.packet.write(item.variable_bit_size["length_item_name"], length_value) - - # Later items need their bit_offset adjusted by the change in this item - adjustment = new_bit_size - current_bit_size + adjustment = self._write_variable_other(item, value, buffer) # Recalculate bit offsets after this item if adjustment != 0 and item.bit_offset >= 0: @@ -269,6 +172,112 @@ def handle_write_variable_bit_size(self, item, value, buffer): if sitem != item: sitem.bit_offset += adjustment + def _write_variable_int(self, item, value, buffer): + # QUIC encoding is currently assumed for individual variable sized integers + # see https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc + + # Calculate current bit size so we can preserve bytes after the item + length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") + match length_item_value: + case 0: + current_bit_size = 6 + case 1: + current_bit_size = 14 + case 2: + current_bit_size = 30 + case 3: + current_bit_size = 62 + case _: + raise AttributeError( + f"Value {item.variable_bit_size['length_item_name']} has unknown QUIC bit size encoding: {length_item_value}" + ) + + if item.data_type == "UINT": + if value <= 63: + # Length = 0, value up to 6-bits + new_bit_size = 6 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 0) + elif value <= 16383: + # Length = 1, value up to 14-bits + new_bit_size = 14 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 1) + elif value <= 1073741823: + # Length = 2, value up to 30-bits + new_bit_size = 30 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 2) + else: + # Length = 3, value up to 62-bits + new_bit_size = 62 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 3) + else: + if value <= 31 and value >= -32: + # Length = 0, value up to 6-bits + new_bit_size = 6 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 0) + elif value <= 8191 and value >= -8192: + # Length = 1, value up to 14-bits + new_bit_size = 14 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 1) + elif value <= 536870911 and value >= -536870912: + # Length = 2, value up to 30-bits + new_bit_size = 30 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 2) + else: + # Length = 3, value up to 62-bits + new_bit_size = 62 + item.bit_size = new_bit_size + self.packet.write(item.variable_bit_size["length_item_name"], 3) + + # Later items need their bit_offset adjusted by the change in this item + adjustment = new_bit_size - current_bit_size + adjust_bytes = int(adjustment / 8) + item_offset = int(item.bit_offset / 8) + if adjust_bytes > 0: + original_length = len(buffer) + # Add extra bytes because we're adjusting larger + buffer += BinaryAccessor.ZERO_STRING * adjust_bytes + # We added bytes to the end so now we have to shift the buffer over + # We copy from the original offset with the original length + # to the new shifted offset all the way to the end of the buffer + buffer[(item_offset + adjust_bytes) :] = buffer[item_offset:original_length] + elif adjust_bytes < 0: + # Remove extra bytes because we're adjusting smaller + del buffer[item_offset + 1 : item_offset + 1 - adjust_bytes] + + return adjustment + + def _write_variable_other(self, item, value, buffer): + # Calculate current bit size so we can preserve bytes after the item + length_item_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") + current_bit_size = ( + length_item_value * item.variable_bit_size["length_bits_per_count"] + ) + item.variable_bit_size["length_value_bit_offset"] + + # Calculate bits after this item + bits_with_item = item.bit_offset + current_bit_size + bits_after_item = (len(buffer) * 8) - bits_with_item + if item.original_array_size is not None: + item.array_size = -bits_after_item + else: + item.bit_size = -bits_after_item + + new_bit_size = len(value) * 8 + length_value = (new_bit_size - item.variable_bit_size["length_value_bit_offset"]) / item.variable_bit_size[ + "length_bits_per_count" + ] + self.packet.write(item.variable_bit_size["length_item_name"], length_value) + + # Later items need their bit_offset adjusted by the change in this item + # so return the adjustment value + return new_bit_size - current_bit_size + def write_item(self, item, value, buffer): if item.data_type == "DERIVED": return None @@ -491,7 +500,7 @@ def write(cls, value, bit_offset, bit_size, data_type, buffer, endianness, overf ####################################### if cls.byte_aligned(bit_offset): - if data_type == "STRING" and type(value) == str: + if data_type == "STRING" and isinstance(value, str): temp = value.encode(encoding="utf-8") else: temp = value @@ -529,7 +538,7 @@ def write(cls, value, bit_offset, bit_size, data_type, buffer, endianness, overf else: # given_bit_size > 0 byte_size = math.floor(bit_size / 8) if len(value) < byte_size: - if type(value) is str: + if isinstance(value, str): ba = bytearray() ba.extend(value.encode(encoding="utf-8")) value = ba @@ -959,7 +968,7 @@ def write_array( given_array_size = array_size # Verify a list was given - if type(values) != list: + if not isinstance(values, list): raise AttributeError(f"values must be a list but is {values.__class__.__name__}") # Handle negative and zero bit sizes @@ -1041,7 +1050,7 @@ def write_array( if byte_aligned: for index in range(num_writes): value = values[index] - if type(value) == int: + if isinstance(value, int): value = value.to_bytes(1, byteorder="big") cls.write( value, diff --git a/openc3/python/openc3/accessors/cbor_accessor.py b/openc3/python/openc3/accessors/cbor_accessor.py index 3c30eea05c..24b9bc5000 100644 --- a/openc3/python/openc3/accessors/cbor_accessor.py +++ b/openc3/python/openc3/accessors/cbor_accessor.py @@ -23,7 +23,7 @@ class CborAccessor(JsonAccessor): def class_read_item(cls, item, buffer): if item.data_type == "DERIVED": return None - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): parsed = loads(buffer) else: parsed = buffer @@ -33,14 +33,14 @@ def class_read_item(cls, item, buffer): def class_write_item(cls, item, value, buffer): if item.data_type == "DERIVED": return None - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): decoded = loads(buffer) else: decoded = buffer super().class_write_item(item, value, decoded) - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): # buffer[0:] syntax so we copy into the buffer buffer[0:] = dumps(decoded) return value diff --git a/openc3/python/openc3/accessors/json_accessor.py b/openc3/python/openc3/accessors/json_accessor.py index e31e71aaad..f538c97c3e 100644 --- a/openc3/python/openc3/accessors/json_accessor.py +++ b/openc3/python/openc3/accessors/json_accessor.py @@ -24,7 +24,7 @@ class JsonAccessor(Accessor): def class_read_item(cls, item, buffer): if item.data_type == "DERIVED": return None - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): buffer = json.loads(buffer.decode()) result = parse(item.key).find(buffer) if len(result) == 0: @@ -35,7 +35,7 @@ def class_read_item(cls, item, buffer): def class_write_item(cls, item, value, buffer): if item.data_type == "DERIVED": return None - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): decoded = json.loads(buffer.decode()) else: decoded = buffer @@ -43,25 +43,25 @@ def class_write_item(cls, item, value, buffer): value = cls.convert_to_type(value, item) result = parse(item.key).update(decoded, value) - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): # buffer[0:] syntax so we copy into the buffer buffer[0:] = bytearray(json.dumps(result), encoding="utf-8") @classmethod def class_read_items(cls, items, buffer): - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): buffer = json.loads(buffer.decode()) return super().class_read_items(items, buffer) @classmethod def class_write_items(cls, items, values, buffer): - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): decoded = json.loads(buffer.decode()) else: decoded = buffer super().class_write_items(items, values, decoded) - if type(buffer) is bytearray: + if isinstance(buffer, bytearray): # buffer[0:] syntax so we copy into the buffer buffer[0:] = bytearray(json.dumps(decoded), encoding="utf-8") diff --git a/openc3/python/openc3/api/cmd_api.py b/openc3/python/openc3/api/cmd_api.py index e79bb89666..405ed7299a 100644 --- a/openc3/python/openc3/api/cmd_api.py +++ b/openc3/python/openc3/api/cmd_api.py @@ -352,7 +352,7 @@ def get_cmd_hazardous(*args, scope=OPENC3_SCOPE): for name, hash in item["states"].items(): parameter_name = parameters[item["name"]] # Remove quotes from string parameters - if type(parameter_name) == str: + if isinstance(parameter_name, str): parameter_name = parameter_name.replace('"', "").replace("'", "") # To be hazardous the state must be marked hazardous # Check if either the state name or value matches the param passed @@ -492,7 +492,7 @@ def get_cmd_cnt(*args, scope=OPENC3_SCOPE): # @return [Numeric] Transmit count for the command def get_cmd_cnts(target_commands, scope=OPENC3_SCOPE): authorize(permission="system", scope=scope) - if type(target_commands) is list and type(target_commands[0] is list): + if isinstance(target_commands, list) and isinstance(target_commands[0], list): counts = [] for target_name, command_name in target_commands: target_name = target_name.upper() diff --git a/openc3/python/openc3/api/interface_api.py b/openc3/python/openc3/api/interface_api.py index 324aa52bb8..3f84e54559 100644 --- a/openc3/python/openc3/api/interface_api.py +++ b/openc3/python/openc3/api/interface_api.py @@ -141,7 +141,7 @@ def map_target_to_interface( ): authorize(permission="system_set", interface_name=interface_name, scope=scope) new_interface = InterfaceModel.get_model(name=interface_name, scope=scope) - if type(target_name) is list: + if isinstance(target_name, list): target_names = target_name else: target_names = [target_name] diff --git a/openc3/python/openc3/api/limits_api.py b/openc3/python/openc3/api/limits_api.py index 312ff85f36..d26c39a4f6 100644 --- a/openc3/python/openc3/api/limits_api.py +++ b/openc3/python/openc3/api/limits_api.py @@ -227,7 +227,7 @@ def get_limits(target_name, packet_name, item_name, scope=OPENC3_SCOPE): limits = {} item = _get_item(target_name, packet_name, item_name, scope=scope) for key, vals in item["limits"].items(): - if type(vals) != dict: + if not isinstance(vals, dict): continue limits[key] = [ diff --git a/openc3/python/openc3/api/tlm_api.py b/openc3/python/openc3/api/tlm_api.py index d224a1c99f..e0878cd621 100644 --- a/openc3/python/openc3/api/tlm_api.py +++ b/openc3/python/openc3/api/tlm_api.py @@ -260,7 +260,7 @@ def get_tlm_packet(*args, stale_time: int = 30, type: str = "CONVERTED", scope: # Array consisting of the item value and limits state # given as symbols such as :RED, :YELLOW, :STALE def get_tlm_values(items, stale_time=30, cache_timeout=0.1, scope=OPENC3_SCOPE): - if type(items) is not list or len(items) == 0 or type(items[0]) is not str: + if not isinstance(items, list) or len(items) == 0 or not isinstance(items[0], str): raise AttributeError("items must be array of strings: ['TGT__PKT__ITEM__TYPE', ...]") packets = [] cvt_items = [] @@ -373,7 +373,7 @@ def subscribe_packets(packets, scope=OPENC3_SCOPE): Return: (str) ID which should be passed to get_packets """ - if type(packets) is not list or type(packets[0]) is not list: + if not isinstance(packets, list) or not isinstance(packets[0], list): raise RuntimeError("packets must be nested array: [['TGT','PKT'],...]") result = {} diff --git a/openc3/python/openc3/config/config_parser.py b/openc3/python/openc3/config/config_parser.py index 93cddf5643..15aa4e86b5 100644 --- a/openc3/python/openc3/config/config_parser.py +++ b/openc3/python/openc3/config/config_parser.py @@ -185,7 +185,7 @@ def verify_parameter_naming(self, index, usage=""): # self.return [None|Object] @classmethod def handle_none(cls, value): - if type(value) == str: + if isinstance(value, str): match value.upper(): case "" | "NONE" | "NULL": return None @@ -199,7 +199,7 @@ def handle_none(cls, value): # self.return [True|False|Object] @classmethod def handle_true_false(cls, value): - if type(value) == str: + if isinstance(value, str): match value.upper(): case "TRUE": return True @@ -214,7 +214,7 @@ def handle_true_false(cls, value): # self.return [True|False|None|Object] @classmethod def handle_true_false_none(cls, value): - if type(value) == str: + if isinstance(value, str): match value.upper(): case "TRUE": return True @@ -236,7 +236,7 @@ def handle_true_false_none(cls, value): # self.return [Numeric] The converted value. Either a Fixnum or Float. @classmethod def handle_defined_constants(cls, value, data_type=None, bit_size=None): - if type(value) == str: + if isinstance(value, str): match value.upper(): case "MIN" | "MAX": return ConfigParser.calculate_range_value(value.upper(), data_type, bit_size) diff --git a/openc3/python/openc3/interfaces/protocols/burst_protocol.py b/openc3/python/openc3/interfaces/protocols/burst_protocol.py index 9a25e1611b..1f65af9ae7 100644 --- a/openc3/python/openc3/interfaces/protocols/burst_protocol.py +++ b/openc3/python/openc3/interfaces/protocols/burst_protocol.py @@ -79,7 +79,7 @@ def read_data(self, data, extra=None): continue # Potentially allow blank string to be sent to other protocols if no packet is ready in this one - if type(packet_data) is str: + if isinstance(packet_data, str): if (len(data) <= 0) and packet_data != "DISCONNECT": # On blank string test, return blank string (if not we had a packet or need disconnect) # The base class handles the special match of returning STOP if on the last protocol in the diff --git a/openc3/python/openc3/interfaces/protocols/cobs_protocol.py b/openc3/python/openc3/interfaces/protocols/cobs_protocol.py index 89d5b0a062..4227766719 100644 --- a/openc3/python/openc3/interfaces/protocols/cobs_protocol.py +++ b/openc3/python/openc3/interfaces/protocols/cobs_protocol.py @@ -48,7 +48,7 @@ def __init__(self, allow_empty_data=None): def read_data(self, data, extra=None): data, extra = super().read_data(data, extra) - if len(data) <= 0 or type(data) is str: + if len(data) <= 0 or isinstance(data, str): return (data, extra) result_data = b"" diff --git a/openc3/python/openc3/interfaces/protocols/crc_protocol.py b/openc3/python/openc3/interfaces/protocols/crc_protocol.py index a3d172d27c..b471d480c2 100644 --- a/openc3/python/openc3/interfaces/protocols/crc_protocol.py +++ b/openc3/python/openc3/interfaces/protocols/crc_protocol.py @@ -80,14 +80,14 @@ def __init__( poly = ConfigParser.handle_none(poly) try: - if type(poly) is str: + if isinstance(poly, str): poly = int(poly, 0) except (ValueError, TypeError): raise ValueError(f"Invalid polynomial of {poly}. Must be a number.") seed = ConfigParser.handle_none(seed) try: - if type(seed) is str: + if isinstance(seed, str): seed = int(seed, 0) except ValueError: raise ValueError(f"Invalid seed of {seed}. Must be a number.") diff --git a/openc3/python/openc3/interfaces/protocols/slip_protocol.py b/openc3/python/openc3/interfaces/protocols/slip_protocol.py index 8bca0a1397..fe26b0d702 100644 --- a/openc3/python/openc3/interfaces/protocols/slip_protocol.py +++ b/openc3/python/openc3/interfaces/protocols/slip_protocol.py @@ -87,7 +87,7 @@ def __init__( def read_data(self, data, extra=None): data, extra = super().read_data(data, extra) - if len(data) <= 0 or type(data) is str: + if len(data) <= 0 or isinstance(data, str): return (data, extra) if self.read_strip_characters: diff --git a/openc3/python/openc3/interfaces/protocols/template_protocol.py b/openc3/python/openc3/interfaces/protocols/template_protocol.py index 0b7c001328..1911b80595 100644 --- a/openc3/python/openc3/interfaces/protocols/template_protocol.py +++ b/openc3/python/openc3/interfaces/protocols/template_protocol.py @@ -219,7 +219,7 @@ def write_packet(self, packet): raw_packet = Packet(None, None) raw_packet.buffer = bytes(self.template, "ascii") raw_packet = super().write_packet(raw_packet) - if type(raw_packet) is str: + if isinstance(raw_packet, str): return raw_packet data = raw_packet.buffer diff --git a/openc3/python/openc3/io/json_api_object.py b/openc3/python/openc3/io/json_api_object.py index 369622bd8d..ed07d98aa2 100644 --- a/openc3/python/openc3/io/json_api_object.py +++ b/openc3/python/openc3/io/json_api_object.py @@ -131,10 +131,8 @@ def _generate_scope(kwargs): scope = kwargs.get("scope", None) if not scope: raise JsonApiError(f"no scope keyword found: {kwargs}") - elif type(scope) is not str: - raise JsonApiError( - f"incorrect type for keyword 'scope' MUST be String: {scope}" - ) + elif not isinstance(scope, str): + raise JsonApiError(f"incorrect type for keyword 'scope' MUST be String: {scope}") return scope def _generate_headers(self, kwargs): @@ -142,10 +140,8 @@ def _generate_headers(self, kwargs): headers = kwargs.get("headers", None) if not headers: headers = kwargs["headers"] = {} - elif type(headers) is not dict: - raise JsonApiError( - f"incorrect type for keyword 'headers' MUST be Dictionary: {headers}" - ) + elif not isinstance(headers, dict): + raise JsonApiError(f"incorrect type for keyword 'headers' MUST be Dictionary: {headers}") if "json" in kwargs and kwargs["json"]: headers["Content-Type"] = "application/json" @@ -165,10 +161,8 @@ def _generate_data(kwargs): data = kwargs.get("data", None) if not data: data = kwargs["data"] = {} - elif type(data) is not dict and type(data) is not str: - raise JsonApiError( - f"incorrect type for keyword 'data' MUST be Dictionary or String: {data}" - ) + elif not isinstance(data, dict) and not isinstance(data, str): + raise JsonApiError(f"incorrect type for keyword 'data' MUST be Dictionary or String: {data}") if "json" in kwargs and kwargs["json"]: return json.dumps(kwargs["data"]) else: @@ -180,10 +174,8 @@ def _generate_query(kwargs): query = kwargs.get("query", None) if query is None: query = kwargs["query"] = {} - elif type(query) is not dict: - raise JsonApiError( - f"incorrect type for keyword 'query' MUST be Dictionary: {query}" - ) + elif not isinstance(query, dict): + raise JsonApiError(f"incorrect type for keyword 'query' MUST be Dictionary: {query}") if "scope" in kwargs and kwargs["scope"]: kwargs["query"]["scope"] = kwargs["scope"] return kwargs["query"] @@ -194,9 +186,7 @@ def _send_request(self, method, endpoint, kwargs): kwargs["url"] = f"{self.url}{endpoint}" self.log[0] = f"{method} Request: {kwargs}" resp = getattr(self.http, method)(**kwargs) - self.log[1] = ( - f"{method} Response: {resp.status_code} {resp.headers} {resp.text}" - ) + self.log[1] = f"{method} Response: {resp.status_code} {resp.headers} {resp.text}" self.response_data = resp.text return resp except Exception as error: diff --git a/openc3/python/openc3/io/json_drb_object.py b/openc3/python/openc3/io/json_drb_object.py index dddf04f946..810bf18ad8 100644 --- a/openc3/python/openc3/io/json_drb_object.py +++ b/openc3/python/openc3/io/json_drb_object.py @@ -141,7 +141,7 @@ def make_request(self, request, token=None): def handle_response(self, response: JsonRpcSuccessResponse | JsonRpcErrorResponse): # The code below will always either raise or return breaking out of the loop - if type(response) == JsonRpcErrorResponse: + if isinstance(response, JsonRpcErrorResponse): if response.error.data: error = JsonDRbError.from_hash(response.error.data) raise error diff --git a/openc3/python/openc3/io/json_rpc.py b/openc3/python/openc3/io/json_rpc.py index 734701c0e9..3609092518 100644 --- a/openc3/python/openc3/io/json_rpc.py +++ b/openc3/python/openc3/io/json_rpc.py @@ -168,7 +168,7 @@ def from_json(cls, response_data): """ msg = f"invalid json-rpc 2.0 response{response_data}\n" - if type(response_data) == str: + if isinstance(response_data, str): try: return json.loads(response_data) # .decode("latin-1")) except Exception as e: diff --git a/openc3/python/openc3/microservices/interface_microservice.py b/openc3/python/openc3/microservices/interface_microservice.py index d80d95daef..aee2b884cc 100644 --- a/openc3/python/openc3/microservices/interface_microservice.py +++ b/openc3/python/openc3/microservices/interface_microservice.py @@ -532,7 +532,7 @@ def run(self): if not self.interface.connected(): self.handle_connection_lost() except RuntimeError as error: - if type(error) != SystemExit: # or signal exception + if not isinstance(error, SystemExit): # or signal exception self.logger.error(f"{self.interface.name}: Packet reading thread died: {repr(error)}") # handle_fatal_exception(error) # Try to do clean disconnect because we're going down diff --git a/openc3/python/openc3/models/cvt_model.py b/openc3/python/openc3/models/cvt_model.py index 7835d9ca71..02f46e3200 100644 --- a/openc3/python/openc3/models/cvt_model.py +++ b/openc3/python/openc3/models/cvt_model.py @@ -160,7 +160,7 @@ def get_tlm_values(cls, items: list, stale_time: int = 30, cache_timeout: float ) hash = packet_lookup[target_packet_key] item_result = [] - if type(value_keys) is dict: # Set in _parse_item to indicate override + if isinstance(value_keys, dict): # Set in _parse_item to indicate override item_result.insert(0, value_keys["value"]) else: for key in value_keys: diff --git a/openc3/python/openc3/models/model.py b/openc3/python/openc3/models/model.py index 780c4d6680..2138409dad 100644 --- a/openc3/python/openc3/models/model.py +++ b/openc3/python/openc3/models/model.py @@ -80,7 +80,7 @@ def from_json(cls, json_data: str | dict, scope: str): Return: [Model] Model generated from the passed JSON """ - if type(json_data) is str: + if isinstance(json_data, str): json_data = json.loads(json_data) if json_data is None: raise RuntimeError("json data is nil") diff --git a/openc3/python/openc3/packets/commands.py b/openc3/python/openc3/packets/commands.py index 329bb92b50..24ca4ede4c 100644 --- a/openc3/python/openc3/packets/commands.py +++ b/openc3/python/openc3/packets/commands.py @@ -295,8 +295,8 @@ def _set_parameters(self, command, params, range_checking): maximum = item.maximum if minimum is not None and maximum is not None: # Perform Range Check on command parameter - if type(range_check_value) is str or range_check_value < minimum or range_check_value > maximum: - if type(range_check_value) is str: + if isinstance(range_check_value, str) or range_check_value < minimum or range_check_value > maximum: + if isinstance(range_check_value, str): range_check_value = f"'{range_check_value}'" raise RuntimeError( f"Command parameter '{command.target_name} {command.packet_name} {item_upcase}' = {range_check_value} not in valid range of {minimum} to {maximum}" diff --git a/openc3/python/openc3/packets/packet.py b/openc3/python/openc3/packets/packet.py index a607bcb995..a11bb9c1cb 100644 --- a/openc3/python/openc3/packets/packet.py +++ b/openc3/python/openc3/packets/packet.py @@ -107,7 +107,7 @@ def target_name(self, target_name): """Sets the target name this packet is associated with. Unidentified packets will have target name set to None.""" if target_name is not None: - if type(target_name) != str: + if not isinstance(target_name, str): raise AttributeError(f"target_name must be a str but is a {target_name.__class__.__name__}") self.__target_name = target_name.upper() @@ -122,7 +122,7 @@ def packet_name(self): def packet_name(self, packet_name): """Sets the packet name. Unidentified packets will have packet name set to None""" if packet_name is not None: - if type(packet_name) != str: + if not isinstance(packet_name, str): raise AttributeError(f"packet_name must be a str but is a {packet_name.__class__.__name__}") self.__packet_name = packet_name.upper() @@ -137,7 +137,7 @@ def description(self): def description(self, description): """Sets the packet description""" if description is not None: - if type(description) != str: + if not isinstance(description, str): raise AttributeError(f"description must be a str but is a {description.__class__.__name__}") self.__description = description @@ -169,7 +169,7 @@ def received_time(self): def received_time(self, received_time): """Sets the received time of the packet""" if received_time is not None: - if type(received_time) is not datetime.datetime: + if not isinstance(received_time, datetime.datetime): raise AttributeError(f"received_time must be a datetime but is a {received_time.__class__.__name__}") self.__received_time = received_time self.read_conversion_cache = {} @@ -183,7 +183,7 @@ def received_count(self): @received_count.setter def received_count(self, received_count): """Sets the packet name. Unidentified packets will have packet name set to None""" - if type(received_count) != int: + if not isinstance(received_count, int): raise AttributeError(f"received_count must be an int but is a {received_count.__class__.__name__}") self.__received_count = received_count @@ -295,7 +295,7 @@ def hazardous_description(self): def hazardous_description(self, hazardous_description): """Sets the packet hazardous_description""" if hazardous_description is not None: - if type(hazardous_description) != str: + if not isinstance(hazardous_description, str): raise AttributeError( f"hazardous_description must be a str but is a {hazardous_description.__class__.__name__}" ) @@ -312,7 +312,7 @@ def given_values(self): def given_values(self, given_values): """Sets the packet given_values""" if given_values is not None: - if type(given_values) != dict: + if not isinstance(given_values, dict): raise AttributeError(f"given_values must be a dict but is a {given_values.__class__.__name__}") self.__given_values = given_values @@ -536,9 +536,9 @@ def read_item(self, item, value_type="CONVERTED", buffer=None, given_raw=None): if self.read_conversion_cache.get(item.name): value = self.read_conversion_cache[item.name] # Make sure cached value is not modified by anyone by creating a deep copy - if type(value) is str: + if isinstance(value, str): value = copy.copy(value) - elif type(value) is list: + elif isinstance(value, list): value = value.copy() using_cached_value = True @@ -554,9 +554,9 @@ def read_item(self, item, value_type="CONVERTED", buffer=None, given_raw=None): self.read_conversion_cache[item.name] = value # Make sure cached value is not modified by anyone by creating a deep copy - if type(value) is str: + if isinstance(value, str): value = copy.copy(value) - elif type(value) is list: + elif isinstance(value, list): value = value.copy() # Derived raw values perform read_conversions but nothing else: @@ -565,7 +565,7 @@ def read_item(self, item, value_type="CONVERTED", buffer=None, given_raw=None): # Convert from value to state if possible: if item.states: - if type(value) is list: + if isinstance(value, list): for index, val in enumerate(value): key = item.states_by_value().get(value[index]) if key is not None: @@ -583,7 +583,7 @@ def read_item(self, item, value_type="CONVERTED", buffer=None, given_raw=None): else: value = self.apply_format_string_and_units(item, value, value_type) else: - if type(value) is list: + if isinstance(value, list): for index, val in enumerate(value): value[index] = self.apply_format_string_and_units(item, val, value_type) else: @@ -650,7 +650,7 @@ def write_item(self, item, value, value_type="CONVERTED", buffer=None): try: super().write_item(item, value, "RAW", buffer) except ValueError as error: - if item.states and type(value) is str and "invalid literal for" in repr(error): + if item.states and isinstance(value, str) and "invalid literal for" in repr(error): raise ValueError(f"Unknown state {value} for {item.name}") from error else: raise error diff --git a/openc3/python/openc3/packets/packet_config.py b/openc3/python/openc3/packets/packet_config.py index 647dffc63c..ad22027146 100644 --- a/openc3/python/openc3/packets/packet_config.py +++ b/openc3/python/openc3/packets/packet_config.py @@ -427,7 +427,7 @@ def process_current_packet(self, parser, keyword, params): else: meta_values = [] for index, value in enumerate(meta_values): - if type(value) is str: + if isinstance(value, str): meta_values[index] = value if self.current_item: # Item META diff --git a/openc3/python/openc3/packets/packet_item.py b/openc3/python/openc3/packets/packet_item.py index a9c4ba8220..71a3110fa6 100644 --- a/openc3/python/openc3/packets/packet_item.py +++ b/openc3/python/openc3/packets/packet_item.py @@ -19,6 +19,7 @@ from openc3.packets.structure_item import StructureItem from openc3.packets.packet_item_limits import PacketItemLimits from openc3.utilities.string import quote_if_necessary, simple_formatted +from openc3.conversions.conversion import Conversion class PacketItem(StructureItem): @@ -64,7 +65,7 @@ def format_string(self): @format_string.setter def format_string(self, format_string): if format_string: - if type(format_string) is not str: + if not isinstance(format_string, str): raise AttributeError( f"{self.name}: format_string must be a str but is a {format_string.__class__.__name__}" ) @@ -81,10 +82,7 @@ def read_conversion(self): @read_conversion.setter def read_conversion(self, read_conversion): if read_conversion: - # NOTE: issubclass is not reliable ... - # if not issubclass(read_conversion.__class__, Conversion): - # TODO: This is weak comparison to the name rather than the type - if "Conversion" not in read_conversion.__class__.__name__: + if not isinstance(read_conversion, Conversion): raise AttributeError( f"{self.name}: read_conversion must be a Conversion but is a {read_conversion.__class__.__name__}" ) @@ -99,9 +97,7 @@ def write_conversion(self): @write_conversion.setter def write_conversion(self, write_conversion): if write_conversion: - # NOTE: issubclass is not reliable ... - # if not issubclass(write_conversion.__class__, Conversion): - if "Conversion" not in write_conversion.__class__.__name__: + if not isinstance(write_conversion, Conversion): raise AttributeError( f"{self.name}: write_conversion must be a Conversion but is a {write_conversion.__class__.__name__}" ) @@ -128,7 +124,7 @@ def states(self): @states.setter def states(self, states): if states is not None: - if type(states) is not dict: + if not isinstance(states, dict): raise AttributeError(f"{self.name}: states must be a dict but is a {states.__class__.__name__}") # Make sure all states are in upper case @@ -154,7 +150,7 @@ def description(self): @description.setter def description(self, description): if description: - if type(description) is not str: + if not isinstance(description, str): raise AttributeError( f"{self.name}: description must be a str but is a {description.__class__.__name__}" ) @@ -169,7 +165,7 @@ def units_full(self): @units_full.setter def units_full(self, units_full): if units_full: - if type(units_full) is not str: + if not isinstance(units_full, str): raise AttributeError(f"{self.name}: units_full must be a str but is a {units_full.__class__.__name__}") self.__units_full = units_full else: @@ -182,7 +178,7 @@ def units(self): @units.setter def units(self, units): if units: - if type(units) is not str: + if not isinstance(units, str): raise AttributeError(f"{self.name}: units must be a str but is a {units.__class__.__name__}") self.__units = units else: @@ -191,73 +187,48 @@ def units(self, units): def check_default_and_range_data_types(self): if self.default and not self.write_conversion: if self.array_size is not None: - if type(self.default) is not list: + if not isinstance(self.default, list): raise AttributeError( f"{self.name}: default must be a list but is a {self.default.__class__.__name__}" ) else: match self.data_type: case "INT" | "UINT": - if type(self.default) is not int: + if not isinstance(self.default, int): raise AttributeError( f"{self.name}: default must be a int but is a {self.default.__class__.__name__}" ) - if type(self.minimum) is not int: + if not isinstance(self.minimum, int): raise AttributeError( f"{self.name}: minimum must be a int but is a {self.minimum.__class__.__name__}" ) - if type(self.maximum) is not int: + if not isinstance(self.maximum, int): raise AttributeError( f"{self.name}: maximum must be a int but is a {self.maximum.__class__.__name__}" ) case "FLOAT": - if type(self.default) not in [float, int]: + if not isinstance(self.default, (float, int)): raise AttributeError( f"{self.name}: default must be a float but is a {self.default.__class__.__name__}" ) self.default = float(self.default) - if type(self.minimum) not in [int, float]: + if not isinstance(self.minimum, (int, float)): raise AttributeError( f"{self.name}: minimum must be a float but is a {self.minimum.__class__.__name__}" ) - if type(self.maximum) not in [int, float]: + if not isinstance(self.maximum, (int, float)): raise AttributeError( f"{self.name}: maximum must be a float but is a {self.maximum.__class__.__name__}" ) - # if self.range: - # if type(self.range.start) not in [float, int]: - # raise AttributeError( - # f"{self.name}: minimum must be a float or int but is a {self.range.start.__class__.__name__}" - # ) - # if type(self.range.stop) not in [float, int]: - # raise AttributeError( - # f"{self.name}: maximum must be a float or int but is a {self.range.stop.__class__.__name__}" - # ) - # self.range = frange(self.range.start, self.range.stop) case "BLOCK" | "STRING": - if type(self.default) not in [str, bytes, bytearray]: + if not isinstance(self.default, (str, bytes, bytearray)): raise AttributeError( f"{self.name}: default must be a str but is a {self.default.__class__.__name__}" ) self.default = str(self.default) - # @property - # def range(self): - # return self.__range - - # @range.setter - # def range(self, range): - # if range: - # if type(range).__name__ not in ["range", "frange"]: - # raise AttributeError( - # f"{self.name}: range must be a range but is a {range.__class__.__name__}" - # ) - # self.__range = range - # else: - # self.__range = None - @property def hazardous(self): return self.__hazardous @@ -265,7 +236,7 @@ def hazardous(self): @hazardous.setter def hazardous(self, hazardous): if hazardous is not None: - if type(hazardous) is not dict: + if not isinstance(hazardous, dict): raise AttributeError(f"{self.name}: hazardous must be a dict but is a {hazardous.__class__.__name__}") self.__hazardous = hazardous else: @@ -278,7 +249,7 @@ def messages_disabled(self): @messages_disabled.setter def messages_disabled(self, messages_disabled): if messages_disabled is not None: - if type(messages_disabled) is not dict: + if not isinstance(messages_disabled, dict): raise AttributeError( f"{self.name}: messages_disabled must be a dict but is a {messages_disabled.__class__.__name__}" ) @@ -294,7 +265,7 @@ def state_colors(self): @state_colors.setter def state_colors(self, state_colors): if state_colors is not None: - if type(state_colors) is not dict: + if not isinstance(state_colors, dict): raise AttributeError( f"{self.name}: state_colors must be a dict but is a {state_colors.__class__.__name__}" ) @@ -310,7 +281,7 @@ def limits(self): @limits.setter def limits(self, limits): if limits is not None: - if type(limits) is not PacketItemLimits: + if not isinstance(limits, PacketItemLimits): raise AttributeError( f"{self.name}: limits must be a PacketItemLimits but is a {limits.__class__.__name__}" ) @@ -326,7 +297,7 @@ def meta(self): @meta.setter def meta(self, meta): if meta is not None: - if type(meta) is not dict: + if not isinstance(meta, dict): raise AttributeError(f"{self.name}: meta must be a dict but is a {meta.__class__.__name__}") self.__meta = meta diff --git a/openc3/python/openc3/packets/packet_item_limits.py b/openc3/python/openc3/packets/packet_item_limits.py index 52b47482b1..c89ead244a 100644 --- a/openc3/python/openc3/packets/packet_item_limits.py +++ b/openc3/python/openc3/packets/packet_item_limits.py @@ -58,7 +58,7 @@ def values(self): @values.setter def values(self, values): if values is not None: - if type(values) is not dict: + if not isinstance(values, dict): raise AttributeError(f"values must be a Hash but is a {values.__class__.__name__}") if "DEFAULT" not in values: raise AttributeError("values must be a Hash with a 'DEFAULT' key") diff --git a/openc3/python/openc3/packets/parsers/processor_parser.py b/openc3/python/openc3/packets/parsers/processor_parser.py index 616df24fe7..90b00d85c3 100644 --- a/openc3/python/openc3/packets/parsers/processor_parser.py +++ b/openc3/python/openc3/packets/parsers/processor_parser.py @@ -16,6 +16,7 @@ from openc3.top_level import get_class_from_module from openc3.utilities.string import filename_to_module, filename_to_class_name +from openc3.processors.processor import Processor class ProcessorParser: @@ -52,9 +53,7 @@ def create_processor(self, packet): processor = klass(*self.parser.parameters[2 : (len(self.parser.parameters))]) else: processor = klass() - # NOTE: issubclass is not reliable ... - # if not issubclass(type(processor), Processor): - if "Processor" not in processor.__class__.__name__: + if not isinstance(processor, Processor): raise AttributeError(f"processor must be a Processor but is a {processor.__class__.__name__}") processor.name = self._get_processor_name() diff --git a/openc3/python/openc3/packets/structure.py b/openc3/python/openc3/packets/structure.py index 52165d445d..f049ef5737 100644 --- a/openc3/python/openc3/packets/structure.py +++ b/openc3/python/openc3/packets/structure.py @@ -38,7 +38,7 @@ def __init__( ): if (default_endianness == "BIG_ENDIAN") or (default_endianness == "LITTLE_ENDIAN"): self.default_endianness = default_endianness - if buffer is not None and not isinstance(buffer, (bytes, bytearray)): # type(buffer) != str: + if buffer is not None and not isinstance(buffer, (bytes, bytearray)): raise TypeError(f"wrong argument type {buffer.__class__.__name__} (expected bytes)") if buffer is None: self._buffer = None diff --git a/openc3/python/openc3/packets/structure_item.py b/openc3/python/openc3/packets/structure_item.py index 4cb250fdf7..a94df89da0 100644 --- a/openc3/python/openc3/packets/structure_item.py +++ b/openc3/python/openc3/packets/structure_item.py @@ -74,7 +74,7 @@ def name(self): @name.setter def name(self, name): - if type(name) != str: + if not isinstance(name, str): raise AttributeError(f"name must be a String but is a {name.__class__.__name__}") if len(name) == 0: raise AttributeError("name must contain at least one character") @@ -89,7 +89,7 @@ def key(self): @key.setter def key(self, key): - if type(key) != str: + if not isinstance(key, str): raise AttributeError(f"key must be a String but is a {key.__class__.__name__}") if len(key) == 0: raise AttributeError("key must contain at least one character") @@ -101,7 +101,7 @@ def endianness(self): @endianness.setter def endianness(self, endianness): - if type(endianness) != str: + if not isinstance(endianness, str): raise AttributeError(f"{self.name}: endianness must be a String but is a {endianness.__class__.__name__}") if endianness not in BinaryAccessor.ENDIANNESS: raise AttributeError( @@ -117,7 +117,7 @@ def bit_offset(self): @bit_offset.setter def bit_offset(self, bit_offset): - if type(bit_offset) != int: + if not isinstance(bit_offset, int): raise AttributeError(f"{self.name}: bit_offset must be an Integer") byte_aligned = (bit_offset % 8) == 0 @@ -139,7 +139,7 @@ def bit_size(self): @bit_size.setter def bit_size(self, bit_size): - if type(bit_size) != int: + if not isinstance(bit_size, int): raise AttributeError(f"{self.name}: bit_size must be an Integer") byte_multiple = (bit_size % 8) == 0 @@ -162,7 +162,7 @@ def data_type(self): @data_type.setter def data_type(self, data_type): - if type(data_type) != str: + if not isinstance(data_type, str): raise AttributeError( f"{self.name}: data_type must be a str but {data_type} is a {type(data_type).__name__}" ) @@ -182,7 +182,7 @@ def array_size(self): @array_size.setter def array_size(self, array_size): if array_size is not None: - if type(array_size) != int: + if not isinstance(array_size, int): raise AttributeError(f"{self.name}: array_size must be an Integer") if not (self.bit_size == 0 or (array_size % self.bit_size == 0) or array_size < 0): raise AttributeError(f"{self.name}: array_size must be a multiple of bit_size") @@ -199,7 +199,7 @@ def overflow(self): @overflow.setter def overflow(self, overflow): - if type(overflow) != str: + if not isinstance(overflow, str): raise AttributeError(f"{self.name}: overflow type must be a String") if overflow not in BinaryAccessor.OVERFLOW_TYPES: @@ -218,13 +218,13 @@ def variable_bit_size(self): @variable_bit_size.setter def variable_bit_size(self, variable_bit_size): if variable_bit_size: - if type(variable_bit_size) != dict: + if not isinstance(variable_bit_size, dict): raise AttributeError(f"{self.name}: variable_bit_size must be a dict") - if type(variable_bit_size["length_item_name"]) != str: + if not isinstance(variable_bit_size["length_item_name"], str): raise AttributeError(f"{self.name}: variable_bit_size['length_item_name'] must be a String") - if type(variable_bit_size["length_value_bit_offset"]) != int: + if not isinstance(variable_bit_size["length_value_bit_offset"], int): raise AttributeError(f"{self.name}: variable_bit_size['length_value_bit_offset'] must be an Integer") - if type(variable_bit_size["length_bits_per_count"]) != int: + if not isinstance(variable_bit_size["length_bits_per_count"], int): raise AttributeError(f"{self.name}: variable_bit_size['length_bits_per_count'] must be an Integer") self.__variable_bit_size = variable_bit_size if self.structure_item_constructed: diff --git a/openc3/python/openc3/script/api_shared.py b/openc3/python/openc3/script/api_shared.py index 46a42ee91c..10938ef907 100644 --- a/openc3/python/openc3/script/api_shared.py +++ b/openc3/python/openc3/script/api_shared.py @@ -647,7 +647,7 @@ def _check_tolerance_process_args(args): if length == 3: target_name, packet_name, item_name = extract_fields_from_tlm_text(args[0]) expected_value = args[1] - if type(args[2]) == list: + if isinstance(args[2], list): tolerance = [abs(x) for x in args[2]] else: tolerance = abs(args[2]) @@ -656,7 +656,7 @@ def _check_tolerance_process_args(args): packet_name = args[1] item_name = args[2] expected_value = args[3] - if type(args[4]) == list: + if isinstance(args[4], list): tolerance = [abs(x) for x in args[4]] else: tolerance = abs(args[4]) @@ -739,7 +739,7 @@ def _execute_wait( polling_rate, scope, ) - if type(value) == str: + if isinstance(value, str): value = f"'{value}'" # Show user the check against a quoted string time_diff = time.time() - start_time wait_str = f"WAIT: {_upcase(target_name, packet_name, item_name)} {comparison_to_eval}" @@ -757,7 +757,7 @@ def _wait_tolerance_process_args(args, function_name): if length == 4 or length == 5: target_name, packet_name, item_name = extract_fields_from_tlm_text(args[0]) expected_value = args[1] - if type(args[2]) == list: + if isinstance(args[2], list): tolerance = [abs(x) for x in args[2]] else: tolerance = abs(args[2]) @@ -771,7 +771,7 @@ def _wait_tolerance_process_args(args, function_name): packet_name = args[1] item_name = args[2] expected_value = args[3] - if type(args[4]) == list: + if isinstance(args[4], list): tolerance = [abs(x) for x in args[4]] else: tolerance = abs(args[4]) diff --git a/openc3/python/openc3/script/commands.py b/openc3/python/openc3/script/commands.py index f560161843..2925e9d2ef 100644 --- a/openc3/python/openc3/script/commands.py +++ b/openc3/python/openc3/script/commands.py @@ -148,14 +148,14 @@ def _cmd_string(target_name, cmd_name, cmd_params, raw): for key, value in cmd_params.items(): if key in Packet.RESERVED_ITEM_NAMES: continue - if type(value) == str: + if isinstance(value, str): value = convert_to_value(value) if len(value) > 256: value = value[:255] + "...'" if not value.isascii(): value = "BINARY" value = value.replace('"', "'") - elif type(value) == list: + elif isinstance(value, list): value = str(value) params.append(f"{key} {value}") params = ", ".join(params) diff --git a/openc3/python/openc3/script/metadata.py b/openc3/python/openc3/script/metadata.py index bb5745ac51..f463e44711 100644 --- a/openc3/python/openc3/script/metadata.py +++ b/openc3/python/openc3/script/metadata.py @@ -73,7 +73,7 @@ def metadata_set( Return: The json result of the method call """ - if type(metadata) is not dict: + if not isinstance(metadata, dict): raise RuntimeError(f"metadata must be a dict: {metadata} is a {metadata.__class__.__name__}") data = {"color": color if color else "#003784", "metadata": metadata} @@ -103,7 +103,7 @@ def metadata_update( Return: The json result of the method call """ - if type(metadata) is not dict: + if not isinstance(metadata, dict): raise RuntimeError(f"metadata must be a Hash: {metadata} is a {metadata.__class__.__name__}") if start is None: # No start so grab latest diff --git a/openc3/python/openc3/script/suite.py b/openc3/python/openc3/script/suite.py index 9ebdeb9052..1f16089674 100644 --- a/openc3/python/openc3/script/suite.py +++ b/openc3/python/openc3/script/suite.py @@ -373,18 +373,16 @@ def run_method(self, object, method_name): openc3.script.RUNNING_SCRIPT.instance.exceptions = None except Exception as error: - # Check that the error belongs to the StopScript inheritance chain - if issubclass(error.__class__, StopScript): + if isinstance(error, StopScript): result.stopped = True result.result = "STOP" - # Check that the error belongs to the SkipScript inheritance chain - if issubclass(error.__class__, SkipScript): + if isinstance(error, SkipScript): result.result = "SKIP" if hasattr(error, "message"): result.message = result.message or "" result.message += error.message + "\n" else: - if not issubclass(error.__class__, StopScript) and ( + if not isinstance(error, StopScript) and ( not openc3.script.RUNNING_SCRIPT or not openc3.script.RUNNING_SCRIPT.instance or not openc3.script.RUNNING_SCRIPT.instance.exceptions diff --git a/openc3/python/openc3/script/suite_results.py b/openc3/python/openc3/script/suite_results.py index 9535a73a2d..6bb051b1fc 100644 --- a/openc3/python/openc3/script/suite_results.py +++ b/openc3/python/openc3/script/suite_results.py @@ -62,7 +62,7 @@ def start( # or a single OpenC3TestResult object def process_result(self, results): # If we were passed an array we concat it to the results global - if type(results) == list: + if isinstance(results, list): self.results.append(results) # A single result is appended and then turned into an array else: diff --git a/openc3/python/openc3/topics/limits_event_topic.py b/openc3/python/openc3/topics/limits_event_topic.py index 19ef15cb4b..c1a4ef03b1 100644 --- a/openc3/python/openc3/topics/limits_event_topic.py +++ b/openc3/python/openc3/topics/limits_event_topic.py @@ -203,7 +203,7 @@ def sync_system(cls, scope): enabled = limits_settings.get("enabled", None) persistence = limits_settings.get("persistence_setting", 1) for limits_set, settings in limits_settings.items(): - if type(settings) != dict: + if not isinstance(settings, dict): continue System.limits.set( target_name, diff --git a/openc3/python/openc3/utilities/crc.py b/openc3/python/openc3/utilities/crc.py index 923442ffb7..3503834e94 100644 --- a/openc3/python/openc3/utilities/crc.py +++ b/openc3/python/openc3/utilities/crc.py @@ -384,7 +384,7 @@ def calc(self, data, seed=None): if self.reflect: for byte in data: - if type(byte) is str: + if isinstance(byte, str): byte = ord(byte) crc = (crc << 8 & filter_mask) ^ self.table[(crc >> right_shift) ^ self.bit_reverse_8(byte)] @@ -395,7 +395,7 @@ def calc(self, data, seed=None): return final_bit_reverse(crc) else: for byte in data: - if type(byte) is str: + if isinstance(byte, str): byte = ord(byte) crc = ((crc << 8) & filter_mask) ^ self.table[(crc >> right_shift) ^ byte] diff --git a/openc3/python/openc3/utilities/local_mode.py b/openc3/python/openc3/utilities/local_mode.py index 45f0f231e3..4046d8b51f 100644 --- a/openc3/python/openc3/utilities/local_mode.py +++ b/openc3/python/openc3/utilities/local_mode.py @@ -333,7 +333,7 @@ def put_target_file(cls, path, io_or_string, scope): full_folder_path = f"{cls.LOCAL_MODE_PATH}/{path}" os.makedirs(os.path.dirname(full_folder_path), exist_ok=True) flags = "w" - if type(io_or_string) == bytes: + if isinstance(io_or_string, bytes): flags += "b" with open(full_folder_path, flags) as file: if hasattr(io_or_string, "read"): diff --git a/openc3/python/openc3/utilities/store.py b/openc3/python/openc3/utilities/store.py index fd612daf3d..a6a88c07b6 100644 --- a/openc3/python/openc3/utilities/store.py +++ b/openc3/python/openc3/utilities/store.py @@ -198,9 +198,9 @@ def read_topics(self, topics, offsets=None, timeout_ms=1000, count=None): if result and len(result) > 0: for topic, messages in result: for msg_id, msg_hash in messages: - if type(topic) is bytes: + if isinstance(topic, bytes): topic = topic.decode() - if type(msg_id) is bytes: + if isinstance(msg_id, bytes): msg_id = msg_id.decode() topic_offsets[topic] = msg_id yield topic, msg_id, msg_hash, redis From 1ca6ca866be45d5f24d9eaa371ecbf11ae93b761 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Wed, 14 Aug 2024 18:32:54 -0600 Subject: [PATCH 4/7] Add python conversions --- .rubocop.yml | 3 + .../conversions/bit_reverse_conversion.rb | 6 +- .../conversions/bit_reverse_conversion.py | 47 ++++++++++ .../openc3/conversions/ip_read_conversion.py | 51 +++++++++++ .../openc3/conversions/ip_write_conversion.py | 53 +++++++++++ .../conversions/object_read_conversion.py | 71 +++++++++++++++ .../conversions/object_write_conversion.py | 36 ++++++++ .../test_bit_reverse_conversion.py | 58 ++++++++++++ .../test_object_read_conversion.py | 80 +++++++++++++++++ .../test_object_write_conversion.py | 90 +++++++++++++++++++ .../config/targets/INST/cmd_tlm/inst_tlm.txt | 6 +- .../bit_reverse_conversion_spec.rb | 79 ++++++++++++++++ .../object_write_conversion_spec.rb | 32 +++---- 13 files changed, 590 insertions(+), 22 deletions(-) create mode 100644 openc3/python/openc3/conversions/bit_reverse_conversion.py create mode 100644 openc3/python/openc3/conversions/ip_read_conversion.py create mode 100644 openc3/python/openc3/conversions/ip_write_conversion.py create mode 100644 openc3/python/openc3/conversions/object_read_conversion.py create mode 100644 openc3/python/openc3/conversions/object_write_conversion.py create mode 100644 openc3/python/test/conversions/test_bit_reverse_conversion.py create mode 100644 openc3/python/test/conversions/test_object_read_conversion.py create mode 100644 openc3/python/test/conversions/test_object_write_conversion.py create mode 100644 openc3/spec/conversions/bit_reverse_conversion_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index a6d0e3757c..eea6d46f55 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -42,3 +42,6 @@ Lint/OrAssignmentToConstant: Enabled: false Lint/UnusedMethodArgument: Enabled: false +# This rule doesn't allow for value.to_s(16) +Lint/RedundantStringCoercion: + Enabled: false diff --git a/openc3/lib/openc3/conversions/bit_reverse_conversion.rb b/openc3/lib/openc3/conversions/bit_reverse_conversion.rb index 4557697350..ef9abc3414 100644 --- a/openc3/lib/openc3/conversions/bit_reverse_conversion.rb +++ b/openc3/lib/openc3/conversions/bit_reverse_conversion.rb @@ -21,10 +21,10 @@ module OpenC3 class BitReverseConversion < Conversion def initialize(converted_type, converted_bit_size) + super() @converted_type = converted_type.to_s.upcase.intern @converted_bit_size = converted_bit_size.to_i - @converted_array_size = nil - if @converted_data_type == :FLOAT + if @converted_type == :FLOAT raise "Float Bit Reverse Not Yet Supported" end end @@ -48,7 +48,7 @@ def call(value, _packet, _buffer) # @return [String] The conversion class def to_s - "#{self.class.to_s.split('::')[-1]}.new(#{@converted_type}, #{@converted_bit_size})" + "#{self.class.to_s.split('::')[-1]}.new(:#{@converted_type}, #{@converted_bit_size})" end # @param read_or_write [String] Either 'READ' or 'WRITE' diff --git a/openc3/python/openc3/conversions/bit_reverse_conversion.py b/openc3/python/openc3/conversions/bit_reverse_conversion.py new file mode 100644 index 0000000000..00cd34f7ef --- /dev/null +++ b/openc3/python/openc3/conversions/bit_reverse_conversion.py @@ -0,0 +1,47 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +from openc3.conversions.conversion import Conversion + + +class BitReverseConversion(Conversion): + def __init__(self, converted_type, converted_bit_size): + super().__init__() + self.converted_type = str(converted_type).upper() + self.converted_bit_size = int(converted_bit_size) + if self.converted_type == "FLOAT": + raise RuntimeError("Float Bit Reverse Not Yet Supported") + + # Perform the conversion on the value. + # + # @param value [Object] The value to convert + # @param packet [Packet] The packet which contains the value. This can + # be useful to reach into the packet and use other values in the + # conversion. + # @param buffer [String] The packet buffer + # @return The converted value + def call(self, value, _packet, _buffer): + b = "{:0{width}b}".format(value, width=self.converted_bit_size) + return int(b[::-1], 2) + + # @return [String] The conversion class + def __str__(self): + return f"BitReverseConversion {self.converted_type} {self.converted_bit_size}" + + # @param read_or_write [String] Either 'READ' or 'WRITE' + # @return [String] Config fragment for this conversion + def to_config(self, read_or_write): + return f"{read_or_write}_CONVERSION openc3/conversions/bit_reverse_conversion.py {self.converted_type} {self.converted_bit_size}\n" diff --git a/openc3/python/openc3/conversions/ip_read_conversion.py b/openc3/python/openc3/conversions/ip_read_conversion.py new file mode 100644 index 0000000000..e5f2d81bd1 --- /dev/null +++ b/openc3/python/openc3/conversions/ip_read_conversion.py @@ -0,0 +1,51 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +from openc3.conversions.conversion import Conversion + + +class IpReadConversion(Conversion): + def __init__(self): + super().__init__() + self.converted_type = "STRING" + self.converted_bit_size = 120 + + # Perform the conversion on the value. + # + # @param value [Object] The value to convert + # @param packet [Packet] The packet which contains the value. This can + # be useful to reach into the packet and use other values in the + # conversion. + # @param buffer [String] The packet buffer + # @return The converted value + def call(value, _packet, _buffer): + byte4 = value & 0xFF + value = value >> 8 + byte3 = value & 0xFF + value = value >> 8 + byte2 = value & 0xFF + value = value >> 8 + byte1 = value & 0xFF + return f"{byte1}.{byte2}.{byte3}.{byte4}" + + # @return [String] The conversion class + def __str__(self): + return "IpReadConversion" + + # @param read_or_write [String] Either 'READ' or 'WRITE' + # @return [String] Config fragment for this conversion + def to_config(self, read_or_write): + return f"{read_or_write}_CONVERSION openc3/conversions/ip_read_conversion.py\n" diff --git a/openc3/python/openc3/conversions/ip_write_conversion.py b/openc3/python/openc3/conversions/ip_write_conversion.py new file mode 100644 index 0000000000..389a55e2d3 --- /dev/null +++ b/openc3/python/openc3/conversions/ip_write_conversion.py @@ -0,0 +1,53 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +from openc3.conversions.conversion import Conversion + + +class IpWriteConversion(Conversion): + def __init__(self): + super().__init__() + self.converted_type = "UINT" + self.converted_bit_size = 32 + + # Perform the conversion on the value. + # + # @param value [Object] The value to convert + # @param packet [Packet] The packet which contains the value. This can + # be useful to reach into the packet and use other values in the + # conversion. + # @param buffer [String] The packet buffer + # @return The converted value + def call(value, _packet, _buffer): + bytes = value.split(".") + result = 0 + result += int(bytes[0]) + result = result.append(8) + result += int(bytes[1]) + result = result.append(8) + result += int(bytes[2]) + result = result.append(8) + result += int(bytes[3]) + return result + + # @return [String] The conversion class + def __str__(self): + return "IpWriteConversion" + + # @param read_or_write [String] Either 'READ' or 'WRITE' + # @return [String] Config fragment for this conversion + def to_config(read_or_write): + return f"{read_or_write}_CONVERSION openc3/conversions/ip_write_conversion.py\n" diff --git a/openc3/python/openc3/conversions/object_read_conversion.py b/openc3/python/openc3/conversions/object_read_conversion.py new file mode 100644 index 0000000000..a6eb482319 --- /dev/null +++ b/openc3/python/openc3/conversions/object_read_conversion.py @@ -0,0 +1,71 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +from openc3.config.config_parser import ConfigParser +from openc3.conversions.conversion import Conversion +from openc3.system.system import System + + +class ObjectReadConversion(Conversion): + def __init__(self, cmd_or_tlm, target_name, packet_name): + super().__init__() + cmd_or_tlm = ConfigParser.handle_none(cmd_or_tlm) + if cmd_or_tlm: + self.cmd_or_tlm = str(cmd_or_tlm).upper() + if self.cmd_or_tlm not in ["CMD", "TLM", "COMMAND", "TELEMETRY"]: + raise AttributeError(f"Unknown type:{cmd_or_tlm}") + else: + # Unknown - Will need to search + self.cmd_or_tlm = None + self.target_name = str(target_name).upper() + self.packet_name = str(packet_name).upper() + self.converted_type = "OBJECT" + self.converted_bit_size = 0 + + def lookup_packet(self): + if self.cmd_or_tlm: + if self.cmd_or_tlm == "CMD" or self.cmd_or_tlm == "COMMAND": + return System.commands.packet(self.target_name, self.packet_name) + else: + return System.telemetry.packet(self.target_name, self.packet_name) + else: + # Always searches commands first + try: + return System.commands.packet(self.target_name, self.packet_name) + except: + return System.telemetry.packet(self.target_name, self.packet_name) + + # Perform the conversion on the value. + def call(self, value, _packet, buffer): + fill_packet = self.lookup_packet() + fill_packet.buffer = value + all = fill_packet.read_all("CONVERTED", buffer, True) + return {item[0]: item[1] for item in all} + + # @return [String] The conversion class + def __str__(self): + return f"{self.__class__.__name__} {self.cmd_or_tlm if self.cmd_or_tlm else 'None'} {self.target_name} {self.packet_name}" + + # @return [String] Config fragment for this conversion + def to_config(self, read_or_write): + return f"{read_or_write}_CONVERSION openc3/conversions/object_read_conversion.py {self.cmd_or_tlm if self.cmd_or_tlm else 'None'} {self.target_name} {self.packet_name}\n" + + def as_json(self, *a): + result = super().as_json(*a) + result["cmd_or_tlm"] = self.cmd_or_tlm + result["target_name"] = self.target_name + result["packet_name"] = self.packet_name + return result diff --git a/openc3/python/openc3/conversions/object_write_conversion.py b/openc3/python/openc3/conversions/object_write_conversion.py new file mode 100644 index 0000000000..1a8c14ad07 --- /dev/null +++ b/openc3/python/openc3/conversions/object_write_conversion.py @@ -0,0 +1,36 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +from openc3.conversions.object_read_conversion import ObjectReadConversion + + +class ObjectWriteConversion(ObjectReadConversion): + # Perform the conversion on the value. + # + # @param value [Object] Hash of packet key/value pairs + # @param packet [Packet] Unused + # @param buffer [String] The packet buffer + # @return Raw BLOCK data + def call(self, value, _packet, _buffer): + fill_packet = self.lookup_packet() + fill_packet.restore_defaults() + for key, write_value in value.items(): + fill_packet.write(key, write_value) + return fill_packet.buffer + + # @return [String] Config fragment for this conversion + def to_config(self, read_or_write): + return f"{read_or_write}_CONVERSION openc3/conversions/object_write_conversion.py {self.cmd_or_tlm if self.cmd_or_tlm else 'None'} {self.target_name} {self.packet_name}\n" diff --git a/openc3/python/test/conversions/test_bit_reverse_conversion.py b/openc3/python/test/conversions/test_bit_reverse_conversion.py new file mode 100644 index 0000000000..5845af7f5c --- /dev/null +++ b/openc3/python/test/conversions/test_bit_reverse_conversion.py @@ -0,0 +1,58 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +import unittest +from unittest.mock import * +from test.test_helper import * +from openc3.conversions.bit_reverse_conversion import BitReverseConversion + + +class TestBitReverseConversion(unittest.TestCase): + def test_takes_converted_type_and_converted_bit_size(self): + brc = BitReverseConversion("UINT", 8) + self.assertEqual(brc.converted_type, "UINT") + self.assertEqual(brc.converted_bit_size, 8) + + def test_complains_about_invalid_converted_type(self): + with self.assertRaisesRegex(RuntimeError, "Float Bit Reverse Not Yet Supported"): + BitReverseConversion("FLOAT", 8) + + def test_reverses_the_bits(self): + brc = BitReverseConversion("UINT", 8) + self.assertEqual(brc.call(0x11, None, None), 0x88) + brc = BitReverseConversion("UINT", 16) + self.assertEqual(brc.call(0x1234, None, None), 0x2C48) + brc = BitReverseConversion("UINT", 32) + self.assertEqual(brc.call(0x87654321, None, None), 0x84C2A6E1) + + def test_returns_the_conversion_string(self): + self.assertEqual(str(BitReverseConversion("UINT", 8)), "BitReverseConversion UINT 8") + + def test_returns_a_read_config_snippit(self): + brc = BitReverseConversion("UINT", 8).to_config("READ").strip() + self.assertEqual(brc, "READ_CONVERSION openc3/conversions/bit_reverse_conversion.py UINT 8") + + def test_creates_a_reproducable_format(self): + brc = BitReverseConversion("UINT", "8") + json = brc.as_json() + self.assertEqual(json["class"], "BitReverseConversion") + self.assertEqual(json["converted_type"], "UINT") + self.assertEqual(json["converted_bit_size"], 8) + new_brc = BitReverseConversion(json["converted_type"], json["converted_bit_size"]) + self.assertEqual(brc.converted_type, (new_brc.converted_type)) + self.assertEqual(brc.converted_bit_size, (new_brc.converted_bit_size)) + self.assertEqual(brc.call(0x11, None, None), 0x88) + self.assertEqual(new_brc.call(0x11, None, None), 0x88) diff --git a/openc3/python/test/conversions/test_object_read_conversion.py b/openc3/python/test/conversions/test_object_read_conversion.py new file mode 100644 index 0000000000..7d2504a351 --- /dev/null +++ b/openc3/python/test/conversions/test_object_read_conversion.py @@ -0,0 +1,80 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +import unittest +from unittest.mock import * +from test.test_helper import * +from openc3.conversions.object_read_conversion import ObjectReadConversion + + +class TestObjectReadConversion(unittest.TestCase): + def setUp(self): + mock_redis(self) + setup_system() + + def test_takes_cmd_tlm_target_name_packet_name(self): + orc = ObjectReadConversion("TLM", "inst", "HEALTH_STATUS") + self.assertEqual(orc.cmd_or_tlm, "TLM") + self.assertEqual(orc.target_name, "INST") + self.assertEqual(orc.packet_name, "HEALTH_STATUS") + self.assertEqual(orc.converted_type, "OBJECT") + self.assertEqual(orc.converted_bit_size, 0) + + def test_complains_about_invalid_cmd_tlm(self): + with self.assertRaisesRegex(AttributeError, f"Unknown type:OTHER"): + ObjectReadConversion("OTHER", "TGT", "PKT") + + def test_fills_the_cmd_packet_and_returns_a_hash_of_the_converted_values(self): + orc = ObjectReadConversion("CMD", "INST", "ABORT") + pkt = System.commands.packet("INST", "ABORT") + pkt.write("PKTID", 5) + result = orc.call(pkt.buffer, pkt, pkt.buffer) + self.assertTrue(isinstance(result, dict)) + self.assertEqual(result["CCSDSVER"], 0) + self.assertEqual(result["PKTID"], 5) + + def test_fills_the_tlm_packet_and_returns_a_hash_of_the_converted_values(self): + orc = ObjectReadConversion("TLM", "INST", "PARAMS") + pkt = System.telemetry.packet("INST", "PARAMS") + pkt.write("VALUE0", 1) + pkt.write("VALUE2", 1) + pkt.write("VALUE4", 1) + result = orc.call(pkt.buffer, pkt, pkt.buffer) + self.assertTrue(isinstance(result, dict)) + self.assertEqual(result["CCSDSVER"], 0) + self.assertEqual(result["VALUE0"], "BAD") + self.assertEqual(result["VALUE1"], "GOOD") + self.assertEqual(result["VALUE2"], "BAD") + self.assertEqual(result["VALUE3"], "GOOD") + self.assertEqual(result["VALUE4"], "BAD") + + def test_returns_the_parameters(self): + orc = ObjectReadConversion("TLM", "INST", "PARAMS") + self.assertEqual(f"{orc}", "ObjectReadConversion TLM INST PARAMS") + + def test_returns_a_read_config_snippet(self): + orc = ObjectReadConversion("TLM", "INST", "PARAMS").to_config("READ").strip() + self.assertEqual(orc, "READ_CONVERSION openc3/conversions/object_read_conversion.py TLM INST PARAMS") + + def test_creates_a_reproducable_format(self): + orc = ObjectReadConversion("TLM", "INST", "PARAMS") + json = orc.as_json() + self.assertEqual(json["class"], "ObjectReadConversion") + self.assertEqual(json["converted_type"], "OBJECT") + self.assertEqual(json["converted_bit_size"], 0) + self.assertEqual(json["cmd_or_tlm"], "TLM") + self.assertEqual(json["target_name"], "INST") + self.assertEqual(json["packet_name"], "PARAMS") diff --git a/openc3/python/test/conversions/test_object_write_conversion.py b/openc3/python/test/conversions/test_object_write_conversion.py new file mode 100644 index 0000000000..aea0a2218d --- /dev/null +++ b/openc3/python/test/conversions/test_object_write_conversion.py @@ -0,0 +1,90 @@ +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +import unittest +from unittest.mock import * +from test.test_helper import * +from openc3.conversions.object_write_conversion import ObjectWriteConversion + + +class TestObjectWriteConversion(unittest.TestCase): + def setUp(self): + mock_redis(self) + setup_system() + + def test_takes_cmd_tlm_target_name_packet_name(self): + owc = ObjectWriteConversion("TLM", "inst", "HEALTH_STATUS") + self.assertEqual(owc.cmd_or_tlm, "TLM") + self.assertEqual(owc.target_name, "INST") + self.assertEqual(owc.packet_name, "HEALTH_STATUS") + self.assertEqual(owc.converted_type, "OBJECT") + self.assertEqual(owc.converted_bit_size, 0) + + def test_complains_about_invalid_cmd_tlm(self): + with self.assertRaisesRegex(AttributeError, f"Unknown type:OTHER"): + ObjectWriteConversion("OTHER", "TGT", "PKT") + + def test_writes_the_cmd_packet_and_returns_a_raw_block(self): + values = {} + values["PKTID"] = 5 + values["CCSDSSEQCNT"] = 10 + + # Make the same writes to a stand alone packet + pkt = System.commands.packet("INST", "ABORT") + pkt.write("PKTID", 5) + pkt.write("CCSDSSEQCNT", 10) + + owc = ObjectWriteConversion("CMD", "INST", "ABORT") + result = owc.call(values, pkt, pkt.buffer) + self.assertTrue(isinstance(result, bytearray)) + self.assertEqual(result, pkt.buffer) + + def test_fills_the_tlm_packet_and_returns_a_hash_of_the_converted_values(self): + values = {} + values["CCSDSSEQCNT"] = 11 + values["VALUE0"] = 1 + values["VALUE2"] = 1 + values["VALUE4"] = 1 + + # Make the same writes to a stand alone packet + pkt = System.telemetry.packet("INST", "PARAMS") + pkt.write("CCSDSSEQCNT", 11) + pkt.write("VALUE0", 1) + pkt.write("VALUE2", 1) + pkt.write("VALUE4", 1) + + owc = ObjectWriteConversion("TLM", "INST", "PARAMS") + result = owc.call(values, pkt, pkt.buffer) + self.assertTrue(isinstance(result, bytearray)) + self.assertEqual(result, pkt.buffer) + + def test_returns_the_parameters(self): + owc = ObjectWriteConversion("TLM", "INST", "PARAMS") + self.assertEqual(f"{owc}", "ObjectWriteConversion TLM INST PARAMS") + + def test_returns_a_read_config_snippet(self): + owc = ObjectWriteConversion("TLM", "INST", "PARAMS").to_config("WRITE").strip() + self.assertEqual(owc, "WRITE_CONVERSION openc3/conversions/object_write_conversion.py TLM INST PARAMS") + + def test_creates_a_reproducable_format(self): + owc = ObjectWriteConversion("TLM", "INST", "PARAMS") + json = owc.as_json() + self.assertEqual(json["class"], "ObjectWriteConversion") + self.assertEqual(json["converted_type"], "OBJECT") + self.assertEqual(json["converted_bit_size"], 0) + self.assertEqual(json["cmd_or_tlm"], "TLM") + self.assertEqual(json["target_name"], "INST") + self.assertEqual(json["packet_name"], "PARAMS") diff --git a/openc3/python/test/install/config/targets/INST/cmd_tlm/inst_tlm.txt b/openc3/python/test/install/config/targets/INST/cmd_tlm/inst_tlm.txt index 11f19a42c2..c8980b16e7 100644 --- a/openc3/python/test/install/config/targets/INST/cmd_tlm/inst_tlm.txt +++ b/openc3/python/test/install/config/targets/INST/cmd_tlm/inst_tlm.txt @@ -153,6 +153,9 @@ TELEMETRY INST PARAMS BIG_ENDIAN "Params set by SETPARAMS command" ITEM TIMESEC 48 32 UINT "Seconds since epoch (January 1st, 1970, midnight)" ITEM TIMEUS 80 32 UINT "Microseconds of second" ID_ITEM PKTID 112 16 UINT 1 "Packet id (The combination of CCSDS_APID and PACKET_ID identify the packet)" + APPEND_ITEM VALUE0 16 UINT "Value setting" + STATE GOOD 0 GREEN + STATE BAD 1 RED APPEND_ITEM VALUE1 16 UINT "Value setting" STATE GOOD 0 GREEN STATE BAD 1 RED @@ -165,9 +168,6 @@ TELEMETRY INST PARAMS BIG_ENDIAN "Params set by SETPARAMS command" APPEND_ITEM VALUE4 16 UINT "Value setting" STATE GOOD 0 GREEN STATE BAD 1 RED - APPEND_ITEM VALUE5 16 UINT "Value setting" - STATE GOOD 0 GREEN - STATE BAD 1 RED TELEMETRY INST IMAGE BIG_ENDIAN "Packet with image data" ITEM CCSDSVER 0 3 UINT "CCSDS packet version number (See CCSDS 133.0-B-1)" diff --git a/openc3/spec/conversions/bit_reverse_conversion_spec.rb b/openc3/spec/conversions/bit_reverse_conversion_spec.rb new file mode 100644 index 0000000000..cb37c2698c --- /dev/null +++ b/openc3/spec/conversions/bit_reverse_conversion_spec.rb @@ -0,0 +1,79 @@ +# encoding: ascii-8bit + +# Copyright 2024 OpenC3, Inc. +# All Rights Reserved. +# +# This program is free software; you can modify and/or redistribute it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation; version 3 with +# attribution addendums as found in the LICENSE.txt +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# This file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +require 'spec_helper' +require 'openc3/conversions/bit_reverse_conversion' + +module OpenC3 + describe BitReverseConversion do + describe "initialize" do + it "takes converted_type and converted_bit_size" do + brc = BitReverseConversion.new(:UINT, 8) + expect(brc.converted_type).to eql :UINT + expect(brc.converted_bit_size).to eql 8 + end + + it "complains about invalid converted_type" do + expect { BitReverseConversion.new(:FLOAT, 8) }.to raise_error("Float Bit Reverse Not Yet Supported") + end + end + + describe "call" do + it "reverses the bits" do + brc = BitReverseConversion.new(:UINT, 8) + expect(brc.call(0x11, nil, nil)).to eql 0x88 + + brc = BitReverseConversion.new(:UINT, 16) + expect(brc.call(0x1234, nil, nil)).to eql 0x2C48 + + brc = BitReverseConversion.new(:UINT, 32) + expect(brc.call(0x87654321, nil, nil)).to eql 0x84C2A6E1 + end + end + + describe "to_s" do + it "returns the conversion string" do + expect(BitReverseConversion.new(:UINT, 8).to_s).to eql "BitReverseConversion.new(:UINT, 8)" + end + end + + describe "to_config" do + it "returns a read config snippet" do + brc = BitReverseConversion.new(:UINT, 8).to_config("READ").strip() + expect(brc).to eql "READ_CONVERSION bit_reverse_conversion.rb UINT 8" + end + end + + describe "as_json" do + it "creates a reproducable format" do + brc = BitReverseConversion.new(:UINT, 8) + json = brc.as_json(:allow_nan => true) + expect(json['class']).to eql "OpenC3::BitReverseConversion" + expect(json['converted_type']).to eql :UINT + expect(json['converted_bit_size']).to eql 8 + expect(json['converted_array_size']).to eql nil + new_brc = OpenC3::const_get(json['class']).new(json['converted_type'], json['converted_bit_size']) + expect(brc.converted_type).to eql(new_brc.converted_type) + expect(brc.converted_bit_size).to eql(new_brc.converted_bit_size) + expect(brc.converted_array_size).to eql(new_brc.converted_array_size) + expect(brc.call(0x11, 0, 0)).to eql 0x88 + expect(new_brc.call(0x11, 0, 0)).to eql 0x88 + end + end + end +end diff --git a/openc3/spec/conversions/object_write_conversion_spec.rb b/openc3/spec/conversions/object_write_conversion_spec.rb index 9982e5aa77..c503eb5dee 100644 --- a/openc3/spec/conversions/object_write_conversion_spec.rb +++ b/openc3/spec/conversions/object_write_conversion_spec.rb @@ -28,12 +28,12 @@ module OpenC3 describe "initialize" do it "takes cmd/tlm, target name, packet name" do - orc = ObjectWriteConversion.new("TLM", "inst", "HEALTH_STATUS") - expect(orc.instance_variable_get("@cmd_or_tlm")).to eql :TLM - expect(orc.instance_variable_get("@target_name")).to eql "INST" - expect(orc.instance_variable_get("@packet_name")).to eql "HEALTH_STATUS" - expect(orc.converted_type).to eql :OBJECT - expect(orc.converted_bit_size).to eql 0 + owc = ObjectWriteConversion.new("TLM", "inst", "HEALTH_STATUS") + expect(owc.instance_variable_get("@cmd_or_tlm")).to eql :TLM + expect(owc.instance_variable_get("@target_name")).to eql "INST" + expect(owc.instance_variable_get("@packet_name")).to eql "HEALTH_STATUS" + expect(owc.converted_type).to eql :OBJECT + expect(owc.converted_bit_size).to eql 0 end it "complains about invalid cmd/tlm" do @@ -52,8 +52,8 @@ module OpenC3 pkt.write("PKTID", 5) pkt.write("CCSDSSEQCNT", 10) - orc = ObjectWriteConversion.new(:CMD, "INST", "ABORT") - result = orc.call(values, pkt, pkt.buffer) + owc = ObjectWriteConversion.new(:CMD, "INST", "ABORT") + result = owc.call(values, pkt, pkt.buffer) expect(result).to be_a String expect(result).to eql pkt.buffer end @@ -72,8 +72,8 @@ module OpenC3 pkt.write("VALUE2", 1) pkt.write("VALUE4", 1) - orc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS") - result = orc.call(values, pkt, pkt.buffer) + owc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS") + result = owc.call(values, pkt, pkt.buffer) expect(result).to be_a String expect(result).to eql pkt.buffer end @@ -81,22 +81,22 @@ module OpenC3 describe "to_s" do it "returns the parameters" do - orc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS").to_s - expect(orc).to eql "ObjectWriteConversion TLM INST PARAMS" + owc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS").to_s + expect(owc).to eql "ObjectWriteConversion TLM INST PARAMS" end end describe "to_config" do it "returns a read config snippet" do - orc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS").to_config("WRITE").strip() - expect(orc).to eql "WRITE_CONVERSION object_write_conversion.rb TLM INST PARAMS" + owc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS").to_config("WRITE").strip() + expect(owc).to eql "WRITE_CONVERSION object_write_conversion.rb TLM INST PARAMS" end end describe "as_json" do it "creates a reproducable format" do - orc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS") - json = orc.as_json(allow_nil: true) + owc = ObjectWriteConversion.new(:TLM, "INST", "PARAMS") + json = owc.as_json(allow_nil: true) expect(json['class']).to eql "OpenC3::ObjectWriteConversion" expect(json['converted_type']).to eql :OBJECT expect(json['converted_bit_size']).to eql 0 From c3f7f1ff980e753e38f9839afe7231b494fe8c68 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Wed, 14 Aug 2024 18:46:51 -0600 Subject: [PATCH 5/7] Fix recalculate_bit_offsets --- .../components/widgets/ImageviewerWidget.vue | 2 +- openc3/lib/openc3/packets/structure.rb | 5 +++- openc3/python/openc3/packets/structure.py | 4 ++- openc3/python/test/packets/test_structure.py | 28 +++++++++++++++++-- openc3/spec/packets/structure_spec.rb | 22 +++++++++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/openc3-cosmos-init/plugins/packages/openc3-tool-common/src/components/widgets/ImageviewerWidget.vue b/openc3-cosmos-init/plugins/packages/openc3-tool-common/src/components/widgets/ImageviewerWidget.vue index 978f3375c9..a7450bd6e2 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-tool-common/src/components/widgets/ImageviewerWidget.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-tool-common/src/components/widgets/ImageviewerWidget.vue @@ -43,7 +43,7 @@ export default { }, }, created: function () { - // TODO: We're hard coding COVERTED because the existing 4th parameter is the image format + // TODO: We're hard coding CONVERTED because the existing 4th parameter is the image format // Future breaking change would be to put the type as 4th and format as fifth this.valueId = `${this.parameters[0]}__${this.parameters[1]}__${this.parameters[2]}__CONVERTED` this.screen.addItem(this.valueId) diff --git a/openc3/lib/openc3/packets/structure.rb b/openc3/lib/openc3/packets/structure.rb index 42c6de4437..1a1edcb648 100644 --- a/openc3/lib/openc3/packets/structure.rb +++ b/openc3/lib/openc3/packets/structure.rb @@ -618,7 +618,10 @@ def recalculate_bit_offsets if item.original_bit_offset >= 0 item.bit_offset = item.original_bit_offset + adjustment if item.data_type != :DERIVED and (item.variable_bit_size or item.original_bit_size <= 0 or (item.original_array_size and item.original_array_size <= 0)) - adjustment += calculate_total_bit_size(item) + new_bit_size = calculate_total_bit_size(item) + if item.original_bit_size != new_bit_size + adjustment += (new_bit_size - item.original_bit_size) + end end end end diff --git a/openc3/python/openc3/packets/structure.py b/openc3/python/openc3/packets/structure.py index f049ef5737..003fbac935 100644 --- a/openc3/python/openc3/packets/structure.py +++ b/openc3/python/openc3/packets/structure.py @@ -560,7 +560,9 @@ def recalculate_bit_offsets(self): or item.original_bit_size <= 0 or (item.original_array_size and item.original_array_size <= 0) ): - adjustment += self.calculate_total_bit_size(item) + new_bit_size = self.calculate_total_bit_size(item) + if item.original_bit_size != new_bit_size: + adjustment += new_bit_size - item.original_bit_size def internal_buffer_equals(self, buffer): if not isinstance(buffer, (bytes, bytearray)): diff --git a/openc3/python/test/packets/test_structure.py b/openc3/python/test/packets/test_structure.py index dc2d0d8407..c02e0ba208 100644 --- a/openc3/python/test/packets/test_structure.py +++ b/openc3/python/test/packets/test_structure.py @@ -67,7 +67,7 @@ def test_renames_a_previously_defined_item(self): class TestStructureDefineItem(unittest.TestCase): def setUp(self): - self.s = Structure() + self.s = Structure("BIG_ENDIAN") def test_adds_item_to_items_and_sorted_items(self): self.assertIsNone(self.s.items.get("test1")) @@ -166,7 +166,7 @@ def test_adds_item_with_negative_offset(self): self.assertFalse(self.s.fixed_size) self.assertEqual(self.s.buffer, b"\x00\x00\x00") - def test_recalulates_sorted_items_when_adding_multiple_items(self): + def test_recalculates_sorted_items_when_adding_multiple_items(self): self.s.define_item("test1", 8, 32, "UINT") self.assertEqual(self.s.sorted_items[0].name, "TEST1") self.assertEqual(self.s.defined_length, 5) @@ -193,6 +193,30 @@ def test_overwrites_existing_items(self): self.assertTrue(self.s.fixed_size) self.assertEqual(self.s.buffer, b"\x00\x00") + def test_correctly_recalculates_bit_offsets(self): + self.s.append_item("item1", 8, "UINT") + self.s.append_item("item2", 2, "UINT") + item = self.s.append_item("item3", 6, "UINT") + item.variable_bit_size = {"length_item_name": "item2", "length_bits_per_count": 8, "length_value_bit_offset": 0} + self.s.append_item("item4", 32, "UINT") + self.s.append_item("item5", 32, "UINT") + self.s.append_item("item6", 8, "UINT") + item = self.s.append_item("item7", 0, "STRING") + item.variable_bit_size = {"length_item_name": "item6", "length_bits_per_count": 8, "length_value_bit_offset": 0} + self.s.append_item("item8", 16, "UINT") + + bit_offsets = [] + for item in self.s.sorted_items: + bit_offsets.append(item.bit_offset) + self.assertEqual(bit_offsets, [0, 8, 10, 16, 48, 80, 88, 88]) + + self.s.buffer = ("\x00" * self.s.defined_length).encode("ASCII") + + bit_offsets = [] + for item in self.s.sorted_items: + bit_offsets.append(item.bit_offset) + self.assertEqual(bit_offsets, [0, 8, 10, 16, 48, 80, 88, 88]) + class TestStructureDefine(unittest.TestCase): def setUp(self): diff --git a/openc3/spec/packets/structure_spec.rb b/openc3/spec/packets/structure_spec.rb index 36877e5787..cb15e049cf 100644 --- a/openc3/spec/packets/structure_spec.rb +++ b/openc3/spec/packets/structure_spec.rb @@ -171,6 +171,28 @@ module OpenC3 expect(s.read("test4")).to eql 0xBB66 end + it "correctly recalculates bit offsets" do + s = Structure.new(:BIG_ENDIAN) + s.append_item("item1", 8, :UINT) + s.append_item("item2", 2, :UINT) + item = s.append_item("item3", 6, :UINT) + item.variable_bit_size = {'length_item_name' => "item2", 'length_bits_per_count' => 8, 'length_value_bit_offset' => 0} + s.append_item("item4", 32, :UINT) + s.append_item("item5", 32, :UINT) + s.append_item("item6", 8, :UINT) + item = s.append_item("item7", 0, :STRING) + item.variable_bit_size = {'length_item_name' => "item6", 'length_bits_per_count' => 8, 'length_value_bit_offset' => 0} + s.append_item("item8", 16, :UINT) + + bit_offsets = s.sorted_items.map {|si| si.bit_offset} + expect(bit_offsets).to eql [0, 8, 10, 16, 48, 80, 88, 88] + + s.buffer = "\x00" * s.defined_length + + bit_offsets = s.sorted_items.map {|si| si.bit_offset} + expect(bit_offsets).to eql [0, 8, 10, 16, 48, 80, 88, 88] + end + it "handles blocks" do s = Structure.new(:BIG_ENDIAN) s.append_item("ccsdsheader", 32, :UINT) From a52157aa6d806678dd0b0ad7763e4dd628059c4c Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Wed, 14 Aug 2024 18:58:46 -0600 Subject: [PATCH 6/7] improve unit tests --- openc3/python/test/packets/test_structure.py | 16 +++++++++++++++- openc3/spec/packets/structure_spec.rb | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/openc3/python/test/packets/test_structure.py b/openc3/python/test/packets/test_structure.py index c02e0ba208..a77fed543d 100644 --- a/openc3/python/test/packets/test_structure.py +++ b/openc3/python/test/packets/test_structure.py @@ -210,7 +210,21 @@ def test_correctly_recalculates_bit_offsets(self): bit_offsets.append(item.bit_offset) self.assertEqual(bit_offsets, [0, 8, 10, 16, 48, 80, 88, 88]) - self.s.buffer = ("\x00" * self.s.defined_length).encode("ASCII") + self.s.buffer = ("\x00" * self.s.defined_length).encode("LATIN-1") + + bit_offsets = [] + for item in self.s.sorted_items: + bit_offsets.append(item.bit_offset) + self.assertEqual(bit_offsets, [0, 8, 10, 16, 48, 80, 88, 88]) + + self.s.buffer = "\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00".encode("LATIN-1") + + bit_offsets = [] + for item in self.s.sorted_items: + bit_offsets.append(item.bit_offset) + self.assertEqual(bit_offsets, [0, 8, 10, 40, 72, 104, 112, 128]) + + self.s.buffer = ("\x00" * 13).encode("LATIN-1") bit_offsets = [] for item in self.s.sorted_items: diff --git a/openc3/spec/packets/structure_spec.rb b/openc3/spec/packets/structure_spec.rb index cb15e049cf..37a6ac2af2 100644 --- a/openc3/spec/packets/structure_spec.rb +++ b/openc3/spec/packets/structure_spec.rb @@ -191,6 +191,16 @@ module OpenC3 bit_offsets = s.sorted_items.map {|si| si.bit_offset} expect(bit_offsets).to eql [0, 8, 10, 16, 48, 80, 88, 88] + + s.buffer = "\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00" + + bit_offsets = s.sorted_items.map {|si| si.bit_offset} + expect(bit_offsets).to eql [0, 8, 10, 40, 72, 104, 112, 128] + + s.buffer = "\x00" * 13 + + bit_offsets = s.sorted_items.map {|si| si.bit_offset} + expect(bit_offsets).to eql [0, 8, 10, 16, 48, 80, 88, 88] end it "handles blocks" do From e804c2bcd8bb7c0a26f7c36030178ce382ea8c9b Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Wed, 14 Aug 2024 19:03:01 -0600 Subject: [PATCH 7/7] fix ruff --- openc3/python/openc3/conversions/object_read_conversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openc3/python/openc3/conversions/object_read_conversion.py b/openc3/python/openc3/conversions/object_read_conversion.py index a6eb482319..4c92b0478c 100644 --- a/openc3/python/openc3/conversions/object_read_conversion.py +++ b/openc3/python/openc3/conversions/object_read_conversion.py @@ -45,7 +45,7 @@ def lookup_packet(self): # Always searches commands first try: return System.commands.packet(self.target_name, self.packet_name) - except: + except RuntimeError: return System.telemetry.packet(self.target_name, self.packet_name) # Perform the conversion on the value.