def test_subindexes(self): array = od.Array("Test Array", 0x1000) last_subindex = od.Variable("Last subindex", 0x1000, 0) last_subindex.data_type = od.UNSIGNED8 array.add_member(last_subindex) array.add_member(od.Variable("Test Variable", 0x1000, 1)) array.add_member(od.Variable("Test Variable 2", 0x1000, 2)) self.assertEqual(array[0].name, "Last subindex") self.assertEqual(array[1].name, "Test Variable") self.assertEqual(array[2].name, "Test Variable 2") self.assertEqual(array[3].name, "Test Variable_3")
def test_boolean(self): var = od.Variable("Test BOOLEAN", 0x1000) var.data_type = od.BOOLEAN self.assertEqual(var.decode_raw(b"\x01"), True) self.assertEqual(var.decode_raw(b"\x00"), False) self.assertEqual(var.encode_raw(True), b"\x01") self.assertEqual(var.encode_raw(False), b"\x00")
def test_phys(self): var = od.Variable("Test INTEGER16", 0x1000) var.data_type = od.INTEGER16 var.factor = 0.1 self.assertAlmostEqual(var.decode_phys(128), 12.8) self.assertEqual(var.encode_phys(-0.1), -1)
def build_variable(eds, section, node_id, index, subindex=0): """Creates a object dictionary entry. :param eds: String stream of the eds file :param section: :param node_id: Node ID :param index: Index of the CANOpen object :param subindex: Subindex of the CANOpen object (if presente, else 0) """ name = eds.get(section, "ParameterName") var = objectdictionary.Variable(name, index, subindex) try: var.storage_location = eds.get(section, "StorageLocation") except NoOptionError: var.storage_location = None var.data_type = int(eds.get(section, "DataType"), 0) var.access_type = eds.get(section, "AccessType").lower() if var.data_type > 0x1B: # The object dictionary editor from CANFestival creates an optional object if min max values are used # This optional object is then placed in the eds under the section [A0] (start point, iterates for more) # The eds.get function gives us 0x00A0 now convert to String without hex representation and upper case # The sub2 part is then the section where the type parameter stands try: var.data_type = int( eds.get("%Xsub1" % var.data_type, "DefaultValue"), 0) except NoSectionError: logger.warning("%s has an unknown or unsupported data type (%X)", name, var.data_type) # Assume DOMAIN to force application to interpret the byte data var.data_type = objectdictionary.DOMAIN var.pdo_mappable = bool( int(eds.get(section, "PDOMapping", fallback="0"), 0)) if eds.has_option(section, "LowLimit"): try: var.min = int(eds.get(section, "LowLimit"), 0) except ValueError: pass if eds.has_option(section, "HighLimit"): try: var.max = int(eds.get(section, "HighLimit"), 0) except ValueError: pass if eds.has_option(section, "DefaultValue"): try: var.default_raw = eds.get(section, "DefaultValue") if '$NODEID' in var.default_raw: var.relative = True var.default = _convert_variable(node_id, var.data_type, eds.get(section, "DefaultValue")) except ValueError: pass if eds.has_option(section, "ParameterValue"): try: var.value_raw = eds.get(section, "ParameterValue") var.value = _convert_variable(node_id, var.data_type, eds.get(section, "ParameterValue")) except ValueError: pass return var
def test_add_array(self): test_od = od.ObjectDictionary() array = od.Array("Test Array", 0x1002) array.add_member(od.Variable("Last subindex", 0x1002, 0)) test_od.add_object(array) self.assertEqual(test_od["Test Array"], array) self.assertEqual(test_od[0x1002], array)
def build_variable(eds, section, index, subindex=0): name = eds.get(section, "ParameterName") var = objectdictionary.Variable(name, index, subindex) var.data_type = int(eds.get(section, "DataType"), 0) var.access_type = eds.get(section, "AccessType").lower() if var.data_type > 0x1B: # The object dictionary editor from CANFestival creates an optional object if min max values are used # This optional object is then placed in the eds under the section [A0] (start point, iterates for more) # The eds.get function gives us 0x00A0 now convert to String without hex representation and upper case # The sub2 part is then the section where the type parameter stands var.data_type = int(eds.get("%Xsub1" % var.data_type, "DefaultValue"), 0) if eds.has_option(section, "LowLimit"): try: var.min = int(eds.get(section, "LowLimit"), 0) except ValueError: pass if eds.has_option(section, "HighLimit"): try: var.max = int(eds.get(section, "HighLimit"), 0) except ValueError: pass if eds.has_option(section, "DefaultValue"): try: var.default = int(eds.get(section, "DefaultValue"), 0) except ValueError: pass return var
def test_integer16(self): var = od.Variable("Test INTEGER16", 0x1000) var.data_type = od.INTEGER16 self.assertEqual(var.decode_raw(b"\xfe\xff"), -2) self.assertEqual(var.decode_raw(b"\x01\x00"), 1) self.assertEqual(var.encode_raw(-2), b"\xfe\xff") self.assertEqual(var.encode_raw(1), b"\x01\x00")
def test_integer8(self): var = od.Variable("Test INTEGER8", 0x1000) var.data_type = od.INTEGER8 self.assertEqual(var.decode_raw(b"\xff"), -1) self.assertEqual(var.decode_raw(b"\x7f"), 127) self.assertEqual(var.encode_raw(-2), b"\xfe") self.assertEqual(var.encode_raw(127), b"\x7f")
def import_eds(source, node_id): eds = ConfigParser() if hasattr(source, "read"): fp = source else: fp = open(source) try: # Python 3 eds.read_file(fp) except AttributeError: # Python 2 eds.readfp(fp) fp.close() od = objectdictionary.ObjectDictionary() if eds.has_section("DeviceComissioning"): od.bitrate = int(eds.get("DeviceComissioning", "Baudrate")) * 1000 od.node_id = int(eds.get("DeviceComissioning", "NodeID")) for section in eds.sections(): # Match indexes match = re.match(r"^[0-9A-Fa-f]{4}$", section) if match is not None: index = int(section, 16) name = eds.get(section, "ParameterName") object_type = int(eds.get(section, "ObjectType"), 0) if object_type == VAR: var = build_variable(eds, section, index) od.add_object(var) elif object_type == ARR and eds.has_option(section, "CompactSubObj"): arr = objectdictionary.Array(name, index) last_subindex = objectdictionary.Variable( "Number of entries", index, 0) last_subindex.data_type = objectdictionary.UNSIGNED8 arr.add_member(last_subindex) arr.add_member(build_variable(eds, section, index, 1)) od.add_object(arr) elif object_type == ARR: arr = objectdictionary.Array(name, index) od.add_object(arr) elif object_type == RECORD: record = objectdictionary.Record(name, index) od.add_object(record) continue # Match subindexes match = re.match(r"^([0-9A-Fa-f]{4})sub([0-9A-Fa-f]+)$", section) if match is not None: index = int(match.group(1), 16) subindex = int(match.group(2), 16) entry = od[index] if isinstance(entry, (objectdictionary.Record, objectdictionary.Array)): var = build_variable(eds, section, index, subindex) entry.add_member(var) return od
def test_add_record(self): test_od = od.ObjectDictionary() record = od.Record("Test Record", 0x1001) var = od.Variable("Test Subindex", 0x1001, 1) record.add_member(var) test_od.add_object(record) self.assertEqual(test_od["Test Record"], record) self.assertEqual(test_od[0x1001], record) self.assertEqual(test_od["Test Record"]["Test Subindex"], var)
def test_desc(self): var = od.Variable("Test UNSIGNED8", 0x1000) var.data_type = od.UNSIGNED8 var.add_value_description(0, "Value 0") var.add_value_description(1, "Value 1") var.add_value_description(3, "Value 3") self.assertEqual(var.decode_desc(0), "Value 0") self.assertEqual(var.decode_desc(3), "Value 3") self.assertEqual(var.encode_desc("Value 1"), 1)
def test_bits(self): var = od.Variable("Test UNSIGNED8", 0x1000) var.data_type = od.UNSIGNED8 var.add_bit_definition("BIT 0", [0]) var.add_bit_definition("BIT 2 and 3", [2, 3]) self.assertEqual(var.decode_bits(1, "BIT 0"), 1) self.assertEqual(var.decode_bits(1, [1]), 0) self.assertEqual(var.decode_bits(0xf, [0, 1, 2, 3]), 15) self.assertEqual(var.decode_bits(8, "BIT 2 and 3"), 2) self.assertEqual(var.encode_bits(0xf, [1], 0), 0xd) self.assertEqual(var.encode_bits(0, "BIT 0", 1), 1)
def build_variable(par_tree): index = int(par_tree.get("Index"), 0) try: subindex = int(par_tree.get("SubIndex")) except TypeError: subindex = 0 name = par_tree.get("SymbolName") data_type = par_tree.get("DataType") par = objectdictionary.Variable(name, index, subindex) factor = par_tree.get("Factor", "1") par.factor = int(factor) if factor.isdigit() else float(factor) unit = par_tree.get("Unit") if unit: par.unit = unit description = par_tree.find("Description") if description is not None: par.description = description.text if data_type in DATA_TYPES: par.data_type = DATA_TYPES[data_type] else: logger.warning("Don't know how to handle data type %s", data_type) par.access_type = par_tree.get("AccessType", "rw") try: par.min = int(par_tree.get("MinimumValue")) except (ValueError, TypeError): pass try: par.max = int(par_tree.get("MaximumValue")) except (ValueError, TypeError): pass try: par.default = int(par_tree.get("DefaultValue")) except (ValueError, TypeError): pass # Find value descriptions for value_field_def in par_tree.iterfind("ValueFieldDefs/ValueFieldDef"): value = int(value_field_def.get("Value"), 0) desc = value_field_def.get("Description") par.add_value_description(value, desc) # Find bit field descriptions for bits_tree in par_tree.iterfind("BitFieldDefs/BitFieldDef"): name = bits_tree.get("Name") bits = [int(bit) for bit in bits_tree.get("Bit").split(",")] par.add_bit_definition(name, bits) return par
def build_variable(eds, section, index, subindex=0): name = eds.get(section, "ParameterName") var = objectdictionary.Variable(name, index, subindex) var.data_type = int(eds.get(section, "DataType"), 0) var.access_type = eds.get(section, "AccessType").lower() if eds.has_option(section, "LowLimit"): try: var.min = int(eds.get(section, "LowLimit"), 0) except ValueError: pass if eds.has_option(section, "HighLimit"): try: var.max = int(eds.get(section, "HighLimit"), 0) except ValueError: pass return var
def _convert_eds(eds, node_id): od = objectdictionary.ObjectDictionary() if eds.has_section("DeviceComissioning"): od.bitrate = int(eds.get("DeviceComissioning", "Baudrate")) * 1000 od.node_id = int(eds.get("DeviceComissioning", "NodeID")) for section in eds.sections(): # Match indexes match = re.match(r"^[0-9A-Fa-f]{4}$", section) if match is not None: index = int(section, 16) name = eds.get(section, "ParameterName") object_type = int(eds.get(section, "ObjectType"), 0) if object_type == VAR: var = build_variable(eds, section, index) od.add_object(var) elif object_type == ARR and eds.has_option(section, "CompactSubObj"): arr = objectdictionary.Array(name, index) last_subindex = objectdictionary.Variable( "Number of entries", index, 0) last_subindex.data_type = objectdictionary.UNSIGNED8 arr.add_member(last_subindex) arr.add_member(build_variable(eds, section, index, 1)) od.add_object(arr) elif object_type == ARR: arr = objectdictionary.Array(name, index) od.add_object(arr) elif object_type == RECORD: record = objectdictionary.Record(name, index) od.add_object(record) continue # Match subindexes match = re.match(r"^([0-9A-Fa-f]{4})sub([0-9A-Fa-f]+)$", section) if match is not None: index = int(match.group(1), 16) subindex = int(match.group(2), 16) entry = od[index] if isinstance(entry, (objectdictionary.Record, objectdictionary.Array)): var = build_variable(eds, section, index, subindex) entry.add_member(var) return od
def build_variable(eds, section, node_id, index, subindex=0): """Creates a object dictionary entry. :param eds: String stream of the eds file :param section: :param node_id: Node ID :param index: Index of the CANOpen object :param subindex: Subindex of the CANOpen object (if presente, else 0) """ name = eds.get(section, "ParameterName") var = objectdictionary.Variable(name, index, subindex) var.data_type = int(eds.get(section, "DataType"), 0) var.access_type = eds.get(section, "AccessType").lower() if var.data_type > 0x1B: # The object dictionary editor from CANFestival creates an optional object if min max values are used # This optional object is then placed in the eds under the section [A0] (start point, iterates for more) # The eds.get function gives us 0x00A0 now convert to String without hex representation and upper case # The sub2 part is then the section where the type parameter stands var.data_type = int(eds.get("%Xsub1" % var.data_type, "DefaultValue"), 0) if eds.has_option(section, "LowLimit"): try: var.min = int(eds.get(section, "LowLimit"), 0) except ValueError: pass if eds.has_option(section, "HighLimit"): try: var.max = int(eds.get(section, "HighLimit"), 0) except ValueError: pass if eds.has_option(section, "DefaultValue"): try: var.default = _convert_variable(node_id, var.data_type, eds.get(section, "DefaultValue")) except ValueError: pass if eds.has_option(section, "ParameterValue"): try: var.value = _convert_variable(node_id, var.data_type, eds.get(section, "ParameterValue")) except ValueError: pass return var
def build_variable(eds, section, node_id, index, subindex=0): name = eds.get(section, "ParameterName") var = objectdictionary.Variable(name, index, subindex) var.data_type = int(eds.get(section, "DataType"), 0) var.access_type = eds.get(section, "AccessType").lower() if var.data_type > 0x1B: # The object dictionary editor from CANFestival creates an optional object if min max values are used # This optional object is then placed in the eds under the section [A0] (start point, iterates for more) # The eds.get function gives us 0x00A0 now convert to String without hex representation and upper case # The sub2 part is then the section where the type parameter stands var.data_type = int(eds.get("%Xsub1" % var.data_type, "DefaultValue"), 0) if eds.has_option(section, "LowLimit"): try: var.min = int(eds.get(section, "LowLimit"), 0) except ValueError: pass if eds.has_option(section, "HighLimit"): try: var.max = int(eds.get(section, "HighLimit"), 0) except ValueError: pass if eds.has_option(section, "DefaultValue"): try: default_value = eds.get(section, "DefaultValue") if var.data_type in objectdictionary.DATA_TYPES: var.default = default_value elif var.data_type in objectdictionary.FLOAT_TYPES: var.default = float(default_value) else: #COB-ID can have a suffix of '$NODEID+' so replace this with node_id before converting if '$NODEID+' in default_value and node_id is not None: var.default = int(default_value.replace('$NODEID+',''), 0) + node_id else: var.default = int(default_value, 0) except ValueError: pass return var
def build_variable(par_tree): index = int(par_tree.get("Index"), 0) subindex = int(par_tree.get("SubIndex")) name = par_tree.get("SymbolName") data_type = par_tree.get("DataType") par = objectdictionary.Variable(name, index, subindex) factor = par_tree.get("Factor", "1") par.factor = int(factor) if factor.isdigit() else float(factor) unit = par_tree.get("Unit") if unit and unit != "-": par.unit = unit par.data_type = DATA_TYPES[data_type] par.access_type = par_tree.get("AccessType", "rw") try: par.min = int(par_tree.get("MinimumValue")) except (ValueError, TypeError): pass try: par.max = int(par_tree.get("MaximumValue")) except (ValueError, TypeError): pass # Find value descriptions for value_field_def in par_tree.iterfind("ValueFieldDefs/ValueFieldDef"): value = int(value_field_def.get("Value"), 0) desc = value_field_def.get("Description") par.add_value_description(value, desc) # Find bit field descriptions for bits_tree in par_tree.iterfind("BitFieldDefs/BitFieldDef"): name = bits_tree.get("Name") bits = [int(bit) for bit in bits_tree.get("Bit").split(",")] par.add_bit_definition(name, bits) return par
def test_unsigned32(self): var = od.Variable("Test UNSIGNED32", 0x1000) var.data_type = od.UNSIGNED32 self.assertEqual(var.decode_raw(b"\xfc\xfd\xfe\xff"), 4294901244) self.assertEqual(var.encode_raw(4294901244), b"\xfc\xfd\xfe\xff")
def import_eds(source, node_id): eds = RawConfigParser() eds.optionxform = str if hasattr(source, "read"): fp = source else: fp = open(source) try: # Python 3 eds.read_file(fp) except AttributeError: # Python 2 eds.readfp(fp) fp.close() od = objectdictionary.ObjectDictionary() if eds.has_section("FileInfo"): od.__edsFileInfo = { opt: eds.get("FileInfo", opt) for opt in eds.options("FileInfo") } if eds.has_section("Comments"): linecount = int(eds.get("Comments", "Lines"), 0) od.comments = '\n'.join([ eds.get("Comments", "Line%i" % line) for line in range(1, linecount + 1) ]) if not eds.has_section("DeviceInfo"): logger.warn( "eds file does not have a DeviceInfo section. This section is mandatory" ) else: for rate in [10, 20, 50, 125, 250, 500, 800, 1000]: baudPossible = int( eds.get("DeviceInfo", "BaudRate_%i" % rate, fallback='0'), 0) if baudPossible != 0: od.device_information.allowed_baudrates.add(rate * 1000) for t, eprop, odprop in [ (str, "VendorName", "vendor_name"), (int, "VendorNumber", "vendor_number"), (str, "ProductName", "product_name"), (int, "ProductNumber", "product_number"), (int, "RevisionNumber", "revision_number"), (str, "OrderCode", "order_code"), (bool, "SimpleBootUpMaster", "simple_boot_up_master"), (bool, "SimpleBootUpSlave", "simple_boot_up_slave"), (bool, "Granularity", "granularity"), (bool, "DynamicChannelsSupported", "dynamic_channels_supported"), (bool, "GroupMessaging", "group_messaging"), (int, "NrOfRXPDO", "nr_of_RXPDO"), (int, "NrOfTXPDO", "nr_of_TXPDO"), (bool, "LSS_Supported", "LSS_supported"), ]: try: if t in (int, bool): setattr(od.device_information, odprop, t(int(eds.get("DeviceInfo", eprop), 0))) elif t is str: setattr(od.device_information, odprop, eds.get("DeviceInfo", eprop)) except NoOptionError: pass if eds.has_section("DeviceComissioning"): od.bitrate = int(eds.get("DeviceComissioning", "BaudRate")) * 1000 od.node_id = int(eds.get("DeviceComissioning", "NodeID"), 0) for section in eds.sections(): # Match dummy definitions match = re.match(r"^[Dd]ummy[Uu]sage$", section) if match is not None: for i in range(1, 8): key = "Dummy%04d" % i if eds.getint(section, key) == 1: var = objectdictionary.Variable(key, i, 0) var.data_type = i var.access_type = "const" od.add_object(var) # Match indexes match = re.match(r"^[0-9A-Fa-f]{4}$", section) if match is not None: index = int(section, 16) name = eds.get(section, "ParameterName") try: object_type = int(eds.get(section, "ObjectType"), 0) except NoOptionError: # DS306 4.6.3.2 object description # If the keyword ObjectType is missing, this is regarded as # "ObjectType=0x7" (=VAR). object_type = VAR try: storage_location = eds.get(section, "StorageLocation") except NoOptionError: storage_location = None if object_type in (VAR, DOMAIN): var = build_variable(eds, section, node_id, index) od.add_object(var) elif object_type == ARR and eds.has_option(section, "CompactSubObj"): arr = objectdictionary.Array(name, index) last_subindex = objectdictionary.Variable( "Number of entries", index, 0) last_subindex.data_type = objectdictionary.UNSIGNED8 arr.add_member(last_subindex) arr.add_member(build_variable(eds, section, node_id, index, 1)) arr.storage_location = storage_location od.add_object(arr) elif object_type == ARR: arr = objectdictionary.Array(name, index) arr.storage_location = storage_location od.add_object(arr) elif object_type == RECORD: record = objectdictionary.Record(name, index) record.storage_location = storage_location od.add_object(record) continue # Match subindexes match = re.match(r"^([0-9A-Fa-f]{4})[S|s]ub([0-9A-Fa-f]+)$", section) if match is not None: index = int(match.group(1), 16) subindex = int(match.group(2), 16) entry = od[index] if isinstance(entry, (objectdictionary.Record, objectdictionary.Array)): var = build_variable(eds, section, node_id, index, subindex) entry.add_member(var) # Match [index]Name match = re.match(r"^([0-9A-Fa-f]{4})Name", section) if match is not None: index = int(match.group(1), 16) num_of_entries = int(eds.get(section, "NrOfEntries")) entry = od[index] # For CompactSubObj index 1 is were we find the variable src_var = od[index][1] for subindex in range(1, num_of_entries + 1): var = copy_variable(eds, section, subindex, src_var) if var is not None: entry.add_member(var) return od
def test_add_variable(self): test_od = od.ObjectDictionary() var = od.Variable("Test Variable", 0x1000) test_od.add_object(var) self.assertEqual(test_od["Test Variable"], var) self.assertEqual(test_od[0x1000], var)
def test_unsigned8(self): var = od.Variable("Test UNSIGNED8", 0x1000) var.data_type = od.UNSIGNED8 self.assertEqual(var.decode_raw(b"\xff"), 255) self.assertEqual(var.encode_raw(254), b"\xfe")
def test_unsigned16(self): var = od.Variable("Test UNSIGNED16", 0x1000) var.data_type = od.UNSIGNED16 self.assertEqual(var.decode_raw(b"\xfe\xff"), 65534) self.assertEqual(var.encode_raw(65534), b"\xfe\xff")
def test_visible_string(self): var = od.Variable("Test VISIBLE_STRING", 0x1000) var.data_type = od.VISIBLE_STRING self.assertEqual(var.decode_raw(b"abcdefg"), "abcdefg") self.assertEqual(var.encode_raw("testing"), b"testing")
def test_integer32(self): var = od.Variable("Test INTEGER32", 0x1000) var.data_type = od.INTEGER32 self.assertEqual(var.decode_raw(b"\xfe\xff\xff\xff"), -2) self.assertEqual(var.encode_raw(-2), b"\xfe\xff\xff\xff")
def import_eds(source, node_id): eds = RawConfigParser() if hasattr(source, "read"): fp = source else: fp = open(source) try: # Python 3 eds.read_file(fp) except AttributeError: # Python 2 eds.readfp(fp) fp.close() od = objectdictionary.ObjectDictionary() if eds.has_section("DeviceComissioning"): od.bitrate = int(eds.get("DeviceComissioning", "Baudrate")) * 1000 od.node_id = int(eds.get("DeviceComissioning", "NodeID")) for section in eds.sections(): # Match dummy definitions match = re.match(r"^[Dd]ummy[Uu]sage$", section) if match is not None: for i in range(1, 8): key = "Dummy%04d" % i if eds.getint(section, key) == 1: var = objectdictionary.Variable(key, i, 0) var.data_type = i var.access_type = "const" od.add_object(var) # Match indexes match = re.match(r"^[0-9A-Fa-f]{4}$", section) if match is not None: index = int(section, 16) name = eds.get(section, "ParameterName") try: object_type = int(eds.get(section, "ObjectType"), 0) except NoOptionError: # DS306 4.6.3.2 object description # If the keyword ObjectType is missing, this is regarded as # "ObjectType=0x7" (=VAR). object_type = VAR if object_type in (VAR, DOMAIN): var = build_variable(eds, section, node_id, index) od.add_object(var) elif object_type == ARR and eds.has_option(section, "CompactSubObj"): arr = objectdictionary.Array(name, index) last_subindex = objectdictionary.Variable( "Number of entries", index, 0) last_subindex.data_type = objectdictionary.UNSIGNED8 arr.add_member(last_subindex) arr.add_member(build_variable(eds, section, node_id, index, 1)) od.add_object(arr) elif object_type == ARR: arr = objectdictionary.Array(name, index) od.add_object(arr) elif object_type == RECORD: record = objectdictionary.Record(name, index) od.add_object(record) continue # Match subindexes match = re.match(r"^([0-9A-Fa-f]{4})[S|s]ub([0-9A-Fa-f]+)$", section) if match is not None: index = int(match.group(1), 16) subindex = int(match.group(2), 16) entry = od[index] if isinstance(entry, (objectdictionary.Record, objectdictionary.Array)): var = build_variable(eds, section, node_id, index, subindex) entry.add_member(var) # Match [index]Name match = re.match(r"^([0-9A-Fa-f]{4})Name", section) if match is not None: index = int(match.group(1), 16) num_of_entries = int(eds.get(section, "NrOfEntries")) entry = od[index] # For CompactSubObj index 1 is were we find the variable src_var = od[index][1] for subindex in range(1, num_of_entries + 1): var = copy_variable(eds, section, subindex, src_var) if var is not None: entry.add_member(var) return od
def test_subindexes(self): array = od.Array("Test Array", 0x1000) array.template = od.Variable("Test Variable", 0x1000, 1) subindexes = [var.subindex for var in array] self.assertSequenceEqual(subindexes, range(0, 256))