quarchpy 2.1.10.dev5__py3-none-any.whl → 2.1.10.dev6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
quarchpy/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.1.10.dev5"
1
+ __version__ = "2.1.10.dev6"
@@ -241,6 +241,10 @@ def get_config_path_for_module (idn_string = None, module_connection = None):
241
241
  # Split the string into lines and run through them to locate the parts we need
242
242
  idn_lines = idn_string.upper().split("\n")
243
243
  for i in idn_lines:
244
+
245
+ if i.count(":") > 1:
246
+ i = i[i.index(":") + 1:]
247
+
244
248
  # Part number of the module
245
249
  if "PART#:" in i:
246
250
  device_number = i[6:].strip()
@@ -0,0 +1,766 @@
1
+ import logging
2
+ import os, os.path, sys
3
+ import inspect
4
+ import quarchpy.config_files
5
+ from enum import Enum
6
+
7
+ '''
8
+ Describes a unit for time measurement
9
+ '''
10
+ class TimeUnit(Enum):
11
+ "UNSPECIFIED",
12
+ "nS",
13
+ "uS",
14
+ "mS",
15
+ "S",
16
+
17
+ '''
18
+ Described a precise time duration for settings
19
+ '''
20
+ class TimeValue:
21
+ time_value = None
22
+ time_unit = None
23
+
24
+ def __init__ (self):
25
+ time_value = 0
26
+ time_unit = TimeUnit.UNSPECIFIED
27
+
28
+ '''
29
+ Describes a single range element if a module range parameter
30
+ '''
31
+ class ModuleRangeItem:
32
+ min_value = 0
33
+ max_value = 0
34
+ step_value = 0
35
+ unit = None
36
+
37
+ def __init__ (self):
38
+ min_value = 0
39
+ max_value = 0
40
+ step_value = 0
41
+ unit = None
42
+
43
+ '''
44
+ Describes a range of values which can be applied to the settings on a module
45
+ This is generally a time value, but can be anything
46
+ '''
47
+ class ModuleRangeParam:
48
+ ranges = None
49
+ range_unit = None
50
+
51
+ def __init__ (self):
52
+ self.ranges = list()
53
+ self.range_unit = None
54
+
55
+ def __repr__ (self):
56
+ str = ""
57
+ for item in self.ranges:
58
+ str = str + item.min_value + "," + item.max_value + "," + item.step_value + "," + item.unit + "|"
59
+ return str
60
+
61
+ '''
62
+ Adds a new range item, which is verified to match any existing items
63
+ '''
64
+ def add_range (self, new_range_item):
65
+ valid = True
66
+
67
+ if (self.range_unit is None):
68
+ self.range_unit = new_range_item.unit
69
+ # For additional ranges, verify the unit is the same
70
+ else:
71
+ if (new_range_item.unit != self.range_unit):
72
+ valid = False
73
+
74
+ # Add the new range if all is OK
75
+ if (valid):
76
+ self.ranges.append (new_range_item)
77
+ return True
78
+ else:
79
+ return False
80
+
81
+ '''
82
+ Internal method to find the closest value within a given range
83
+ '''
84
+ def _get_closest_value (self, range_item, value):
85
+
86
+ # Check for out of rang values
87
+ if (value < range_item.min_value):
88
+ result = range_item.min_value
89
+ elif (value > range_item.max_value):
90
+ result = range_item.max_value
91
+ # Else value is in range
92
+ else:
93
+ low_steps = int((float(value) / float(range_item.step_value)) + 0.5)
94
+ low_value = int(low_steps * range_item.step_value)
95
+ high_value = int(low_value + range_item.step_value)
96
+
97
+ # Get the closest step
98
+ if (abs(low_value - value) < abs(high_value - value)):
99
+ result - low_value
100
+ else:
101
+ result - high_value
102
+
103
+ return result
104
+
105
+ '''
106
+ Returns the closest allowable value to the given number
107
+ '''
108
+ def get_closest_value (self, value):
109
+ valid_value = -sys.maxsize -1
110
+ in_range = list()
111
+ running_error = sys.maxsize
112
+ curr_error = 0
113
+ possible_value = 0
114
+
115
+ if (self.ranges is None or len(self.ranges) == 0):
116
+ raise ValueError ("No ranges available to check against")
117
+ else:
118
+ # Loop through the ranges
119
+ for i in self.ranges:
120
+ # Find the closest value in this range
121
+ possible_value = self._get_closest_value (i, value)
122
+ curr_error = abs(possible_value - value)
123
+
124
+ # Store if it is closer than the previous tests
125
+ if (curr_error < running_error):
126
+ running_error = curr_error
127
+ valid_value = possible_value
128
+
129
+ return possible_value
130
+
131
+ '''
132
+ Returns the largest allowable value
133
+ '''
134
+ def get_max_value (self):
135
+ valid_value = -sys.maxsize -1
136
+
137
+ for i in self.ranges:
138
+ if (i.max_value > valid_value):
139
+ valid_value = i.max_value
140
+
141
+ return valid_value
142
+
143
+ '''
144
+ Returns the smallest allowable value
145
+ '''
146
+ def get_max_value (self):
147
+ valid_value = sys.maxsize
148
+
149
+ for i in self.ranges:
150
+ if (i.min_value < valid_value):
151
+ valid_value = i.min_value
152
+
153
+ return valid_value
154
+
155
+ '''
156
+ Describes a switched signal on a breaaker module
157
+ '''
158
+ class BreakerModuleSignal:
159
+ name = None
160
+ parameters = None
161
+
162
+ def __init__ (self):
163
+ self.name = None
164
+ self.parameters = dict ()
165
+
166
+ '''
167
+ Describes a signal group on a breaker module
168
+ '''
169
+ class BreakerSignalGroup:
170
+ name = None
171
+ signals = None
172
+
173
+ def __init__ (self):
174
+ self.name = None
175
+ self.signals = list ()
176
+
177
+ '''
178
+ Describes control sources on a breaker module
179
+ '''
180
+ class BreakerSource:
181
+ name = None
182
+ parameters = None
183
+
184
+ def __init__ (self):
185
+ self.name = None
186
+ self.parameters = dict ()
187
+
188
+ '''
189
+ Describes a Torridon hot-swap/breaker module and all its capabilities
190
+ '''
191
+ class TorridonBreakerModule:
192
+ config_data = None
193
+
194
+ def get_signals(self):
195
+ return self.config_data["SIGNALS"]
196
+
197
+ def get_signal_groups(self):
198
+ return self.config_data["SIGNAL_GROUPS"]
199
+
200
+ def get_sources(self):
201
+ return self.config_data["SOURCES"]
202
+
203
+ def get_general_capabilities(self):
204
+ return self.config_data["GENERAL"]
205
+
206
+ def __init__ (self):
207
+ self.config_data = dict ()
208
+
209
+
210
+ '''
211
+ Tries to locate configuration data for the given module and return a structure of device capabilities to help the user
212
+ control the module and understand what its features are
213
+ '''
214
+ def get_device_capabilities (idn_string = None, module_connection = None):
215
+ # Get config data and fail if none is found
216
+ file_path = get_config_path_for_module (idn_string = idn_string, module_connection = module_connection)
217
+ if (file_path is None):
218
+ raise ValueError ("No configuration data found for the given module")
219
+
220
+ return parse_config_file (file_path)
221
+
222
+ '''
223
+ Returns the path to the most recent config file that is compatible with the given module. Module information can be passed in as
224
+ an existing IDN string from the "*IDN?" command or via an open connection to the module
225
+ '''
226
+ def get_config_path_for_module (idn_string = None, module_connection = None):
227
+ device_number = None
228
+ device_fw = None
229
+ device_fpga = None
230
+ result = True
231
+
232
+ # Check for invalid parameters
233
+ if (idn_string is None and module_connection is None):
234
+ logging.error("Invalid parameters, no module information given")
235
+ result = False
236
+ else:
237
+ # Prefer IDN string, otherwise use the module connection to get it now
238
+ if (idn_string is None):
239
+ idn_string = module_connection.sendCommand ("*IDN?")
240
+
241
+ # Split the string into lines and run through them to locate the parts we need
242
+ idn_lines = idn_string.upper().split("\n")
243
+ for i in idn_lines:
244
+ # Part number of the module
245
+ if "PART#:" in i:
246
+ device_number = i[6:].strip()
247
+ # Firmware version
248
+ if "PROCESSOR:" in i:
249
+ device_fw = i[10:].strip()
250
+ pos = device_fw.find (",")
251
+ if (pos == -1):
252
+ device_fw = None
253
+ else:
254
+ device_fw = device_fw[pos+1:].strip()
255
+ # FPGA version
256
+ if "FPGA 1:" in i:
257
+ device_fpga = i[7:].strip()
258
+ pos = device_fpga.find (",")
259
+ if (pos == -1):
260
+ device_fpga = None
261
+ else:
262
+ device_fpga = device_fpga[pos+1:].strip()
263
+
264
+ # Log the failure if we did not get all the info needed
265
+ if (device_number is None):
266
+ result = False
267
+ logging.error("Unable to indentify module - no module number")
268
+ if (device_fw is None):
269
+ logging.error("Unable to indentify module - no firmware version")
270
+ result = False
271
+ if (device_fpga is None):
272
+ logging.error("Unable to indentify module - no FPGA version")
273
+ result = False
274
+
275
+ # If we got all the data, use it to find the config file
276
+ config_matches = list()
277
+ if (result == False):
278
+ logging.error(f"Error finding an element from *IDN, output was : {str(idn_string)}")
279
+ return None
280
+ else:
281
+ # Get all config files as a dictionary of their basic header information
282
+ config_file_header = get_config_file_headers ()
283
+
284
+ # Loop through each config file header
285
+ for i in config_file_header:
286
+ # If the part number can be used with this config file
287
+ if (check_part_number_matches(i, device_number)):
288
+ logging.debug("Matched part number")
289
+ # Check if the part number is not seperately excluded
290
+ if (check_part_exclude_matches(i, device_number) == False):
291
+ logging.debug("Config file does not exclude module number")
292
+ # Check Firmware can be used with this config file
293
+ if (check_fw_version_matches(i, device_fw)):
294
+ logging.debug("Matched fw versions")
295
+ # Check if FPGA version matches
296
+ if (check_fpga_version_matches(i, device_fpga)):
297
+ # Store this as a matching config file for the device
298
+ logging.debug("Located matching config file: " + i["Config_Path"])
299
+ config_matches.append (i)
300
+
301
+ # Sort the config files into preferred order
302
+ if (len(config_matches) > 0):
303
+ config_matches = sort_config_headers (config_matches)
304
+ return config_matches[0]["Config_Path"]
305
+ else:
306
+ logging.error("No matching config files were found for this module:"
307
+ f"device number: {device_number}\n"
308
+ f"device fw: {device_fw}"
309
+ f"device fpga {device_fpga}")
310
+ return None
311
+
312
+ # Attempts to parse every file on the system to check for errors in the config files or the parser
313
+ def test_config_parser (level=1):
314
+ # Get all config files as a dictionary of their basic header information
315
+ config_file_header = get_config_file_headers ()
316
+ for i in config_file_header:
317
+ print ("CONFIG:" + i["Config_Path"])
318
+ dev_caps = parse_config_file (i["Config_Path"])
319
+ if (dev_caps is None):
320
+ print ("Module not parsed!")
321
+ else:
322
+ if (type(dev_caps) is TorridonBreakerModule):
323
+ print (dev_caps.config_data["HEADER"]["DeviceDescription"])
324
+ print("")
325
+
326
+
327
+ '''
328
+ Processes all config files to get a list of dictionaries of basic header information
329
+ '''
330
+ def get_config_file_headers ():
331
+
332
+ # Get the path to the config files folder
333
+ # folder_path = os.path.dirname (os.path.abspath(quarchpy.config_files.__file__))
334
+ folder_path = os.path.dirname(os.path.realpath(__file__))
335
+ files_found = list()
336
+ config_headers = list()
337
+
338
+ # Iterate through all files, including and recursive folders
339
+ for search_path, search_subdirs, search_files in os.walk(folder_path):
340
+ for name in search_files:
341
+ if (".qfg" in name.lower()):
342
+ files_found.append (os.path.join(search_path, name))
343
+
344
+ # Now parse the header section of each file into the list of dictionaries
345
+ for i in files_found:
346
+ read_file = open (i, "r")
347
+ next_line, read_point = read_config_line (read_file)
348
+ if ("@CONFIG" in next_line):
349
+ # Read until we find the @HEADER section
350
+ while ("@HEADER" not in next_line):
351
+ next_line, read_point = read_config_line (read_file)
352
+ # Parse the header section data
353
+ new_config = parse_section_to_dictionary (read_file)
354
+ # Store the file path as an item
355
+ new_config["Config_Path"] = i
356
+ config_headers.append (new_config)
357
+ else:
358
+ logging.error("Config file parse failed, @CONFIG section not found")
359
+
360
+ return config_headers
361
+
362
+ '''
363
+ Reads the next line of the file which contains usable data, skips blank lines and comments
364
+ '''
365
+ def read_config_line (read_file):
366
+ while(True):
367
+ start_pos = read_file.tell()
368
+ line = read_file.readline ()
369
+
370
+ if (line == ''):
371
+ return None,0
372
+ else:
373
+ line = line.strip()
374
+ if (len(line) > 0):
375
+ if (line[0] != '#'):
376
+ return line, start_pos
377
+
378
+ '''
379
+ Reads the next section of the file (up to the next @ line) into a dictionary
380
+ '''
381
+ def parse_section_to_dictionary (read_file):
382
+ elements = dict()
383
+
384
+ # Read until we find the end
385
+ while(True):
386
+ # Read a line and the read point in the file
387
+ line, start_pos = read_config_line (read_file)
388
+ # If this is the start of a new section, set the file back one line and stop
389
+ if (line.find ('@') == 0):
390
+ read_file.seek (start_pos)
391
+ break
392
+ # Else we parse the line into a new dictionary item
393
+ else:
394
+ pos = line.find ('=')
395
+ if (pos == -1):
396
+ logging.error("Config line does not meet required format of x=y: " + line)
397
+ return None
398
+ else:
399
+ elements[line[:pos]] = line[pos+1:]
400
+
401
+ return elements
402
+
403
+
404
+ '''
405
+ Returns true of the config header is allowed for use on a module with the given part number
406
+ '''
407
+ def check_part_number_matches(config_header, device_number):
408
+
409
+ # Strip down to the main part number, removing the version
410
+ pos = device_number.find ("-")
411
+ if (pos != -1):
412
+ pos = len(device_number) - pos
413
+ short_device_number = device_number[:-pos]
414
+ # Fail on part number not including the version section
415
+ else:
416
+ logging.debug("Part number did not contain the version :" + device_number)
417
+ return False
418
+
419
+ # Loop through the allowed part numbers
420
+ allowed_device_numbers = config_header["DeviceNumbers"].split(",")
421
+ for dev in allowed_device_numbers:
422
+ pos = dev.find('-');
423
+ if (pos != -1):
424
+ pos = len(dev) - pos
425
+ short_config_number = dev[:-pos]
426
+ if ("xx" in dev):
427
+ any_version = True;
428
+ else:
429
+ any_version = False;
430
+ # Fail if config number is invalid
431
+ else:
432
+ logging.debug("Part number in config file is not in the right format: " + dev)
433
+ return False;
434
+
435
+ # Return true if we find a number that matches in full, or one which matches in part and the any_version flag was present in the config file
436
+ if (device_number == dev or (short_device_number == short_config_number and any_version)):
437
+ return True
438
+
439
+ # False as the fallback if no matching part numbers were found
440
+ return False
441
+
442
+ '''
443
+ Returns true of the config header does not contain an exclusion for the given device number
444
+ '''
445
+ def check_part_exclude_matches(config_header, device_number):
446
+
447
+ # Strip down to the main part number, removing the version
448
+ pos = device_number.find ("-")
449
+ if (pos != -1):
450
+ pos = len(device_number) - pos
451
+ short_device_number = device_number[:-pos]
452
+ # Fail on part number not including the version section
453
+ else:
454
+ logging.debug("Part number did not contain the version :" + device_number)
455
+ return False
456
+
457
+ # Check that the part number is fully qualified (will not be the case if the serial number is not set)
458
+ if ("?" in device_number):
459
+ logging.debug("Part number is not fully qualified :" + device_number)
460
+ return False
461
+
462
+ # Loop through the excluded part numbers
463
+ allowed_device_numbers = config_header["DeviceNumbersExclude"].split(",")
464
+ for dev in allowed_device_numbers:
465
+ # Skip blanks (normally due to no part numbers specified)
466
+ if (len(dev) == 0):
467
+ continue
468
+
469
+ pos = dev.find('-');
470
+ if (pos != -1):
471
+ pos = len(dev) - pos
472
+ short_config_number = dev[:-pos]
473
+ if ("xx" in dev):
474
+ any_version = True;
475
+ else:
476
+ any_version = False;
477
+ # Fail if config number is invalid
478
+ else:
479
+ logging.debug("Exclude part number in config file is not in the right format: " + dev)
480
+ return False;
481
+
482
+ # Return true if we find a number that matches in full, or one which matches in part and the any_version flag was present in the config file
483
+ if (device_number == dev or (short_device_number == short_config_number and any_version)):
484
+ return True
485
+
486
+ # False as the fallback if no matching part numbers were found
487
+ return False
488
+
489
+ '''
490
+ Checks that the firmware version on the config header allows the version on the device
491
+ '''
492
+ def check_fw_version_matches(config_header, device_fw):
493
+ if float(device_fw) >= float(config_header["MinFirmwareRequired"]):
494
+ return True
495
+ else:
496
+ return False
497
+
498
+ '''
499
+ Checks that the FPGA version on the config header allows the version on the device
500
+ '''
501
+ def check_fpga_version_matches(config_header, device_fpga):
502
+ if (float(device_fpga) >= float(config_header["MinFpgaRequired"])):
503
+ return True
504
+ else:
505
+ return False
506
+
507
+ '''
508
+ Sorts a list of config headers into order, where the highest firmware version file is at the top of the list
509
+ This is the one which would normally be chosen
510
+ '''
511
+ def sort_config_headers (config_matches):
512
+ return sorted (config_matches, key=lambda i: i["MinFirmwareRequired"], reverse=True)
513
+
514
+ def parse_config_file (file):
515
+
516
+ config_dict = dict ()
517
+ section_dict = dict ()
518
+ section_name = None
519
+ read_file = open (file, "r")
520
+
521
+ # Start with the first line, as this is not useful info anyway
522
+ line, read_pos = read_config_line (read_file)
523
+ # Loop through the file, reading all lines
524
+ while (True):
525
+ line, read_pos = read_config_line (read_file)
526
+ if (line is None):
527
+ break
528
+
529
+ # If this is the start of the first section, store its name
530
+ if ("@" in line and section_name is None):
531
+ section_name = line[1:]
532
+ # If a new section, store the old one first
533
+ elif ("@" in line):
534
+ config_dict[section_name] = section_dict
535
+ section_name = line[1:]
536
+ section_dict = dict ()
537
+ # Else this must be a standard data line
538
+ else:
539
+ # Special case for module signals, create as a BreakerSignal class type
540
+ if ("SIGNALS" in section_name):
541
+
542
+ # Change to a list for signals
543
+ if (len(section_dict) == 0):
544
+ section_dict = list()
545
+
546
+ signal = BreakerModuleSignal ()
547
+ line_value = line.split(',')
548
+ # Loop to add the optional parameters
549
+ for i in line_value:
550
+ pos = i.find('=')
551
+ line_param = i[pos+1:]
552
+ line_name = i[:pos]
553
+ if ("Name" in line_name):
554
+ signal.name = line_param
555
+ else:
556
+ signal.parameters[line_name] = line_param
557
+ # Add signal to the section
558
+ section_dict.append(signal)
559
+ # Special case for module signal groups
560
+ elif ("SIGNAL_GROUPS" in section_name):
561
+
562
+ # Change to a list for signals groups
563
+ if (len(section_dict) == 0):
564
+ section_dict = list()
565
+
566
+ group = BreakerSignalGroup ()
567
+ # Get the name of the group
568
+ pos = line.find(',')
569
+ line_group = line[pos+1:]
570
+ line_header = line[:pos]
571
+ pos = line_header.find('=')
572
+ line_param = line_header[pos+1:]
573
+ line_name = line_header[:pos]
574
+ group.name = line_param
575
+ # Get the list of signals
576
+ pos = line_group.find('=')
577
+ line_param = line_group[pos+1:].strip('\"')
578
+ group.signals = line_param.split (',')
579
+ # Add group to the section
580
+ section_dict.append(group)
581
+ # Special case for module sources
582
+ elif ("SOURCE_START" in section_name):
583
+ read_file.seek(read_pos)
584
+ sources = parse_breaker_sources_section(read_file)
585
+ config_dict["SOURCES"] = sources
586
+ else:
587
+ pos = line.find('=')
588
+ line_value = line[pos+1:]
589
+ line_name = line[:pos]
590
+ section_dict[line_name] = line_value
591
+
592
+ # Now build the appropriate module class
593
+ # TODO: Assuming breaker for testing
594
+ if (config_dict["HEADER"]["DeviceClass"] == "TorridonModule"):
595
+ dev_caps = TorridonBreakerModule ()
596
+ dev_caps.config_data = config_dict
597
+ return dev_caps
598
+ else:
599
+ logging.error("Only 'TorridonModule' class devices are currently supported")
600
+ return None
601
+
602
+ # Parses the sources section of a Torridon breaker module, returning a list of sources
603
+ def parse_breaker_sources_section(file_access):
604
+ new_source = None
605
+ sources = list()
606
+ first_source = True
607
+
608
+ while(True):
609
+ line, read_pos = read_config_line (file_access)
610
+
611
+ # If the start of a source
612
+ if ("@SOURCE_START" in line or first_source):
613
+ # If first source, step back on line as we have consumed the first param already
614
+ if (first_source):
615
+ file_access.seek (read_pos)
616
+ first_source = False
617
+ # Parse the basic sections of the source info
618
+ new_source = BreakerSource ()
619
+ parse_source_basic_section (file_access, new_source)
620
+ continue
621
+ # Bounce sections describe pin-bounce abilities
622
+ elif ("@SOURCE_BOUNCE" in line):
623
+ parse_source_bounce_section (file_access, new_source)
624
+ continue
625
+ elif ("@SOURCE_END" in line):
626
+ if (new_source is not None):
627
+ sources.append (new_source)
628
+ new_source = None
629
+ # If the start of a new section
630
+ elif ("@" in line):
631
+ # Return the file to the line as if we had not read it
632
+ file_access.seek(read_pos)
633
+ # Exit the source parsing loop
634
+ break
635
+
636
+ return sources
637
+
638
+ # Parses the 'general' section, present for all sources
639
+ def parse_source_basic_section(file_access, source):
640
+
641
+ while(True):
642
+ line, read_pos = read_config_line (file_access)
643
+
644
+ if ("@" not in line):
645
+ # Limits sections need parsed into a limits object
646
+ if ("_Limits" in line):
647
+ pos = line.find('=')
648
+ line_param = line[pos+1:]
649
+ line_name = line[:pos]
650
+ line_param = parse_limits_string (line_param)
651
+ # If we've seen a limit section for this already
652
+ if (line_name in source.parameters):
653
+ # Combine the limits
654
+ source.parameters[line_name].add_range (line_param)
655
+ # Else start a new range
656
+ else:
657
+ new_range = ModuleRangeParam ()
658
+ new_range.add_range (line_param)
659
+ source.parameters[line_name] = new_range
660
+ # Else add as dictionary item
661
+ else:
662
+ pos = line.find('=')
663
+ line_param = line[pos+1:]
664
+ line_name = line[:pos]
665
+
666
+ if ("Name" in line_name):
667
+ source.name = line_param
668
+ else:
669
+ source.parameters[line_name] = line_param
670
+ else:
671
+ break
672
+ # Jump file back a line
673
+ file_access.seek (read_pos)
674
+
675
+ # Parses a limit string into a ModuleRangeValue class
676
+ def parse_limits_string (limit_text):
677
+ new_item = ModuleRangeItem ()
678
+
679
+ parts = limit_text.split(',')
680
+ new_item.unit = parts[0]
681
+ new_item.min_value = parts[1]
682
+ new_item.max_value = parts[2]
683
+ new_item.step_value = parts[3]
684
+
685
+ return new_item
686
+
687
+ # Parse out the bounce parameter section, for sources with pin-bounce
688
+ def parse_source_bounce_section (file_access, source):
689
+ while(True):
690
+ line, read_pos = read_config_line (file_access)
691
+
692
+ if ("@" not in line):
693
+ # Limits sections need parsed into a limits object
694
+ if ("_Limits" in line):
695
+ pos = line.find('=')
696
+ line_param = line[pos+1:]
697
+ line_name = line[:pos]
698
+ line_param = parse_limits_string (line_param)
699
+ # If we've seen a limit section for this already
700
+ if (line_name in source.parameters):
701
+ # Combine the limits
702
+ source.parameters[line_name].add_range (line_param)
703
+ # Else start a new range
704
+ else:
705
+ new_range = ModuleRangeParam ()
706
+ new_range.add_range (line_param)
707
+ source.parameters[line_name] = new_range
708
+ # Else add as dictionary item
709
+ else:
710
+ pos = line.find('=')
711
+ line_param = line[pos+1:]
712
+ line_name = line[:pos]
713
+ source.parameters[line_name] = line_param
714
+ else:
715
+ break
716
+
717
+ # Jump file back a line
718
+ file_access.seek(read_pos)
719
+
720
+
721
+ # will return a list of QTLXXXX numbers for each module type
722
+ def return_module_type_list(module_type=None):
723
+ current_path = os.path.dirname(os.path.abspath(__file__))
724
+ only_dirs = [f for f in os.listdir(current_path) if os.path.isdir(os.path.join(current_path, f))]
725
+ # only_dirs = ['Cable_Modules', 'Card_Modules', 'Drive_Modules', 'Power_Margining', 'Switch_Modules']
726
+
727
+ if any(str(module_type).lower() in str(s).lower() for s in only_dirs):
728
+
729
+ # capitalizing first letter - Linux path is case sensitive
730
+ module_type = str(module_type).capitalize()
731
+ post_fix = "_Modules"
732
+ if module_type in "power":
733
+ post_fix = "_Margining"
734
+
735
+ # e.g. "card" + "_modules" > Not case sensitive
736
+ module_type_path = os.path.join(current_path, module_type + post_fix)
737
+
738
+ # getting all files from specified directory
739
+ onlyfiles = [os.path.join(module_type_path, f) for f in os.listdir(module_type_path) if os.path.isfile(os.path.join(module_type_path, f))]
740
+
741
+ filtered_modules = []
742
+ # Grabbing first 7 character (QTLXXXX) for list without duplicates
743
+ # [filtered_modules.append(f[:7]) for f in onlyfiles if f[:7] not in filtered_modules]
744
+
745
+ for item in onlyfiles:
746
+ x = parse_config_file(item)
747
+ if x.config_data['HEADER']['DeviceNumbers'][:7] not in filtered_modules:
748
+ filtered_modules.append(x.config_data['HEADER']['DeviceNumbers'][:7])
749
+
750
+ return filtered_modules
751
+
752
+ def return_module_signals(module_connection=None, idn_string=None):
753
+
754
+ config_path = get_config_path_for_module(idn_string=idn_string, module_connection=module_connection)
755
+ parsed_file = parse_config_file(config_path)
756
+
757
+ signals = []
758
+ for item in parsed_file.config_data["SIGNALS"]:
759
+ signals.append(item.name)
760
+
761
+ # Future may need signal groups too
762
+ # signal_groups = []
763
+ # for item in parsed_file.config_data["SIGNAL_GROUPS"]:
764
+ # signal_groups.append(item)
765
+
766
+ return signals
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quarchpy
3
- Version: 2.1.10.dev5
3
+ Version: 2.1.10.dev6
4
4
  Summary: This packpage offers Python support for Quarch Technology modules.
5
5
  Home-page: UNKNOWN
6
6
  Author: Quarch Technology ltd
@@ -2,7 +2,7 @@ quarchpy/LICENSE.rst,sha256=SmYK6o5Xs2xRaUwYT-HqNDRu9eygu6y9QwweXNttiRc,3690
2
2
  quarchpy/QuarchPy Function Listing.xlsx,sha256=NE68cZPn7b9P3wcnCsnQr3H6yBkt6D5S6dH6457X520,31245
3
3
  quarchpy/README.txt,sha256=-LbrJ5rCPGSxoBmvr9CxJVokQUDwZSjoHa43eN8bWck,980
4
4
  quarchpy/__init__.py,sha256=NQsPKeV7AkmRNKqRsVRXBhzzx0TV0eE3GnmofJEwpTg,3002
5
- quarchpy/_version.py,sha256=iapf82i-Mqzp-0_CeMzAo_a3UoJik6esKI9Li0JVpeA,27
5
+ quarchpy/_version.py,sha256=tfeFT6hXEFiIcb31XwNOeAsWBTy1zmtr6ig_zqUOMhM,27
6
6
  quarchpy/connection.py,sha256=9lPIUP4LCWkFAedc8CWorDY-3ujdAA-cyeNkSBdIv9E,2226
7
7
  quarchpy/run.py,sha256=FS9sa8Z09NLNYhm4ZYtJuZBfMDAPzSB8QHFK2DZnfZA,8702
8
8
  quarchpy/__pycache__/__init__.cpython-38.pyc,sha256=9HU6ut0FlgdY1atAWxzdke2mg6Jgm_pgq8TUuBvIoDA,2142
@@ -10,7 +10,8 @@ quarchpy/__pycache__/_version.cpython-38.pyc,sha256=UMEBhGFNnnj6QDVy5VwDzyjftNMZ
10
10
  quarchpy/__pycache__/connection.cpython-38.pyc,sha256=ir7cO3aYWjVJJw8H42AJxBcPnrZ5z2q5P9vNk3f-6ws,2118
11
11
  quarchpy/__pycache__/run.cpython-38.pyc,sha256=9Gh8Ppc0qsNaz-PofzISrCtuvE4JP5n8lK_zENxAkVc,7201
12
12
  quarchpy/config_files/__init__.py,sha256=bP9FM5hjP0r7s8qmWqcsVjAfHM0B4XY7cz5sTpe2HDA,290
13
- quarchpy/config_files/quarch_config_parser.py,sha256=KgBnDnJIkRHM72lsz5Aerl6h6_1dk6jIibks_Q04La0,28262
13
+ quarchpy/config_files/quarch_config_parser.py,sha256=RA6KJ3UqDIlCCARMGfqTEZTr-giL7pJ3KdRWUTN6NB8,28342
14
+ quarchpy/config_files/quarch_config_parser.py.bak,sha256=KgBnDnJIkRHM72lsz5Aerl6h6_1dk6jIibks_Q04La0,28262
14
15
  quarchpy/config_files/Cable_Modules/QTL1253-01 - Mini SAS Module Config v3.5 c1.3.qfg,sha256=hJnFfjsqiNZXYWHx2kn43Gnr2xqmJth4GGVd8VKU5_k,4923
15
16
  quarchpy/config_files/Cable_Modules/QTL1253-01 - Mini SAS Module Config v4.000 c1.3.qfg,sha256=yqU30RxHOo6ltRvK5MhnAhJuEzDpPnt1hQwEC2iQCA8,4989
16
17
  quarchpy/config_files/Cable_Modules/QTL1253-02 - Mini SAS Module Config v3.5 c1.3.qfg,sha256=bpdt3f6sc5wmDNn6g1L-KzhoSB7AwUtDHad2jYgtD-A,4924
@@ -718,7 +719,7 @@ quarchpy/utilities/__pycache__/TimeValue.cpython-38.pyc,sha256=4GIB56U829vIZZlXC
718
719
  quarchpy/utilities/__pycache__/__init__.cpython-37.pyc,sha256=kqRsK20ckULi5CPPWZW8gDE162DCmVi2uLz968a8z4U,183
719
720
  quarchpy/utilities/__pycache__/__init__.cpython-38.pyc,sha256=nijt9QMW1V99GuRUUOgpVawrPoKQA3WSyjOcz5E4oq0,150
720
721
  quarchpy/utilities/__pycache__/__init__.cpython-39.pyc,sha256=ZE-6REfAg8i2Gw4x6YrmDrBn-RhVipmJpw793mzuaVE,155
721
- quarchpy-2.1.10.dev5.dist-info/METADATA,sha256=6i_vY2qBuZyClmD3Sxx2IZt-IoRqAMkWKRWLqBCdOV8,8124
722
- quarchpy-2.1.10.dev5.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
723
- quarchpy-2.1.10.dev5.dist-info/top_level.txt,sha256=Vc7qsvkVax7oeBaBy_e7kvJvqb_VAAJcW_MuDMmi5W8,9
724
- quarchpy-2.1.10.dev5.dist-info/RECORD,,
722
+ quarchpy-2.1.10.dev6.dist-info/METADATA,sha256=KIXl_YTfFCl0hpaORYeJlCchAbXB-HOWuTnoV5b81XA,8124
723
+ quarchpy-2.1.10.dev6.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
724
+ quarchpy-2.1.10.dev6.dist-info/top_level.txt,sha256=Vc7qsvkVax7oeBaBy_e7kvJvqb_VAAJcW_MuDMmi5W8,9
725
+ quarchpy-2.1.10.dev6.dist-info/RECORD,,