spikesafe-python 1.11.22__tar.gz → 1.13.5__tar.gz

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.
Files changed (50) hide show
  1. {spikesafe_python-1.11.22/spikesafe_python.egg-info → spikesafe_python-1.13.5}/PKG-INFO +1 -1
  2. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/pyproject.toml +1 -1
  3. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerDataFetch.py +6 -7
  4. spikesafe_python-1.13.5/spikesafe_python/Discharge.py +105 -0
  5. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/PulseWidthCorrection.py +21 -14
  6. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/ReadAllEvents.py +15 -3
  7. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SpikeSafeEvents.py +5 -0
  8. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SpikeSafeInfo.py +2 -0
  9. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SpikeSafeInfoParser.py +1 -0
  10. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/TcpSocket.py +2 -1
  11. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5/spikesafe_python.egg-info}/PKG-INFO +1 -1
  12. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python.egg-info/SOURCES.txt +1 -0
  13. spikesafe_python-1.13.5/tests/test_get_new_voltage_data_estimated_complete_time.py +26 -0
  14. spikesafe_python-1.13.5/tests/test_optimum_pulse_width_correction.py +38 -0
  15. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_optimum_pulse_width_correction_old.py +2 -2
  16. spikesafe_python-1.11.22/spikesafe_python/Discharge.py +0 -75
  17. spikesafe_python-1.11.22/tests/test_optimum_pulse_width_correction.py +0 -29
  18. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/LICENSE +0 -0
  19. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/README.md +0 -0
  20. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/setup.cfg +0 -0
  21. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/setup.py +0 -0
  22. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/ChannelData.py +0 -0
  23. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/Compensation.py +0 -0
  24. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerData.py +0 -0
  25. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerEnums.py +0 -0
  26. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerInfo.py +0 -0
  27. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerVfCustomSequence.py +0 -0
  28. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/DigitizerVfCustomSequenceStep.py +0 -0
  29. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/EventData.py +0 -0
  30. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/MemoryTableReadData.py +0 -0
  31. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/Precision.py +0 -0
  32. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/ScpiFormatter.py +0 -0
  33. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SerialPortConnection.py +0 -0
  34. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SpikeSafeEnums.py +0 -0
  35. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/SpikeSafeError.py +0 -0
  36. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/TemperatureData.py +0 -0
  37. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/Threading.py +0 -0
  38. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python/__init__.py +0 -0
  39. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python.egg-info/dependency_links.txt +0 -0
  40. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python.egg-info/requires.txt +0 -0
  41. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/spikesafe_python.egg-info/top_level.txt +0 -0
  42. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_custom_compensation.py +0 -0
  43. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_custom_compensation_old.py +0 -0
  44. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_digitizer_fetch_time_of_sampling.py +0 -0
  45. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_digitizer_fetch_time_of_sampling_old.py +0 -0
  46. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_event_data.py +0 -0
  47. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_event_data_old.py +0 -0
  48. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_optimum_compensation.py +0 -0
  49. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_optimum_compensation_old.py +0 -0
  50. {spikesafe_python-1.11.22 → spikesafe_python-1.13.5}/tests/test_spikesafe_info_compare_rev_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spikesafe-python
3
- Version: 1.11.22
3
+ Version: 1.13.5
4
4
  Summary: SpikeSafe Python Library
5
5
  Author-email: Vektrex <support@vektrex.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spikesafe-python"
7
- version = "1.11.22"
7
+ version = "1.13.5"
8
8
  authors = [
9
9
  { name="Vektrex", email="support@vektrex.com" },
10
10
  ]
@@ -299,7 +299,7 @@ class DigitizerDataFetch:
299
299
  @staticmethod
300
300
  def wait_for_new_voltage_data(
301
301
  spike_safe_socket: TcpSocket,
302
- wait_time: float = 0.0,
302
+ wait_time: float = 0.010,
303
303
  enable_logging: bool | None = None,
304
304
  timeout: float | None = None,
305
305
  digitizer_number: int | None = None
@@ -312,8 +312,8 @@ class DigitizerDataFetch:
312
312
  ----------
313
313
  spike_safe_socket : TcpSocket
314
314
  Socket object used to communicate with SpikeSafe
315
- wait_time: float
316
- Wait time in between each set of VOLT:NDAT? queries in seconds. Use get_new_voltage_data_estimated_complete_time() for the recommended value
315
+ wait_time: float, Optional
316
+ Wait time in between each set of VOLT:NDAT? queries in seconds (default 0.010s). Use get_new_voltage_data_estimated_complete_time() for the recommended value
317
317
  enable_logging : bool, Optional
318
318
  Overrides spike_safe_socket.enable_logging attribute (default to None will use spike_safe_socket.enable_logging value)
319
319
  timeout : float, Optional
@@ -392,12 +392,11 @@ class DigitizerDataFetch:
392
392
 
393
393
  if hardware_trigger_count == 1:
394
394
  # 𝑀𝑖𝑛𝑖𝑚𝑢𝑚 𝑇𝑜𝑡𝑎𝑙 𝐴𝑐𝑞𝑢𝑖𝑠𝑖𝑡𝑖𝑜𝑛 𝑇𝑖𝑚𝑒 = 𝑇𝑟𝑖𝑔𝑔𝑒𝑟 𝐶𝑜𝑢𝑛𝑡 (𝑇𝑟𝑖𝑔𝑔𝑒𝑟 𝐷𝑒𝑙𝑎𝑦+𝐴𝑝𝑒𝑟𝑡𝑢𝑟𝑒 𝑇𝑖𝑚𝑒×𝑅𝑒𝑎𝑑𝑖𝑛𝑔 𝐶𝑜𝑢𝑛𝑡)
395
- estimated_complete_time_seconds = (hardware_trigger_count * (hardware_trigger_delay_microseconds + aperture_microseconds * reading_count)) / 100000
395
+ estimated_complete_time_seconds = (hardware_trigger_count * (hardware_trigger_delay_microseconds + aperture_microseconds * reading_count)) / 1_000_000.0
396
396
  else:
397
397
  retrigger_time_microseconds = 600
398
398
  # 𝑀𝑖𝑛𝑖𝑚𝑢𝑚 𝑇𝑜𝑡𝑎𝑙 𝐴𝑐𝑞𝑢𝑖𝑠𝑖𝑡𝑖𝑜𝑛 𝑇𝑖𝑚𝑒 = 𝑇𝑟𝑖𝑔𝑔𝑒𝑟 𝐶𝑜𝑢𝑛𝑡 (𝑇𝑟𝑖𝑔𝑔𝑒𝑟 𝐷𝑒𝑙𝑎𝑦 + Retrigger Time + 𝐴𝑝𝑒𝑟𝑡𝑢𝑟𝑒 𝑇𝑖𝑚𝑒 × 𝑅𝑒𝑎𝑑𝑖𝑛𝑔 𝐶𝑜𝑢𝑛𝑡) - Retrigger Time (ignore time last trigger)
399
- estimated_complete_time_seconds = (hardware_trigger_count * (hardware_trigger_delay_microseconds + retrigger_time_microseconds + aperture_microseconds * reading_count) - retrigger_time_microseconds) / 100000
400
-
399
+ estimated_complete_time_seconds = (hardware_trigger_count * (hardware_trigger_delay_microseconds + retrigger_time_microseconds + aperture_microseconds * reading_count) - retrigger_time_microseconds) / 1_000_000.0
401
400
  # wait time cannot be less than 0s
402
401
  estimated_complete_time_seconds = max(estimated_complete_time_seconds, 0)
403
402
 
@@ -616,7 +615,7 @@ def get_new_voltage_data_estimated_complete_time(
616
615
 
617
616
  def wait_for_new_voltage_data(
618
617
  spike_safe_socket: TcpSocket,
619
- wait_time: float = 0.0,
618
+ wait_time: float = 0.010,
620
619
  enable_logging: bool | None = None,
621
620
  timeout: float | None = None,
622
621
  digitizer_number: int | None = None
@@ -0,0 +1,105 @@
1
+ import time
2
+ from .SpikeSafeInfo import SpikeSafeInfo
3
+ from .TcpSocket import TcpSocket
4
+ from .Threading import wait
5
+
6
+ class Discharge:
7
+ """
8
+ Class for calculating SpikeSafe channel discharge time based on compliance voltage.
9
+
10
+ Methods
11
+ -------
12
+ get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float
13
+ Returns the time in seconds to fully discharge the SpikeSafe channel based on the compliance voltage
14
+ wait_for_spikesafe_channel_discharge(spike_safe_socket: TcpSocket, spikesafe_info: SpikeSafeInfo, compliance_voltage: float, channel_number: int = 1, enable_logging: bool | None = None) -> None
15
+ Automatically waits for the SpikeSafe channel to fully discharge based on SpikeSafe capabilities.
16
+ If the SpikeSafe supports the Discharge Complete query, it will poll the channel until discharge is complete.
17
+ If not, it will wait for a calculated time based on the compliance voltage.
18
+ """
19
+
20
+ @staticmethod
21
+ def get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float:
22
+ """
23
+ Returns the time in seconds to fully discharge the SpikeSafe channel based on the compliance voltage
24
+
25
+ Parameters
26
+ ----------
27
+ compliance_voltage : float
28
+ Compliance voltage to factor in discharge time
29
+
30
+ Returns
31
+ -------
32
+ float
33
+ Discharge time in seconds
34
+
35
+ Raises
36
+ ------
37
+ None
38
+ """
39
+ # Discharge time accounting for compliance voltage, voltage readroom, and discharge voltage per second
40
+ voltage_headroom_voltage = 7
41
+ discharge_voltage_per_second = 1000
42
+ discharge_time = (compliance_voltage + voltage_headroom_voltage) / discharge_voltage_per_second
43
+ return discharge_time
44
+
45
+ @staticmethod
46
+ def wait_for_spikesafe_channel_discharge(
47
+ spikesafe_socket: TcpSocket,
48
+ spikesafe_info: SpikeSafeInfo,
49
+ compliance_voltage: float,
50
+ channel_number: int = 1,
51
+ enable_logging: bool | None = None
52
+ ) -> None:
53
+ """
54
+ Automatically waits for the SpikeSafe channel to fully discharge based on SpikeSafe capabilities.
55
+ If the SpikeSafe supports the Discharge Complete query, it will poll the channel until discharge is complete.
56
+ If not, it will wait for a calculated time based on the compliance voltage.
57
+
58
+ Parameters
59
+ ----------
60
+ spikesafe_socket : TcpSocket
61
+ Socket object used to communicate with SpikeSafe.
62
+ spikesafe_info : SpikeSafeInfo
63
+ An object containing the SpikeSafe information.
64
+ compliance_voltage : float
65
+ Compliance voltage to factor in discharge time.
66
+ channel_number : int, optional
67
+ Channel number to wait for discharge. By default, this is 1.
68
+ enable_logging : bool, optional
69
+ Overrides spikesafe_socket.enable_logging attribute (default to None will use spikesafe_socket.enable_logging value).
70
+
71
+ Raises
72
+ ------
73
+ TimeoutError
74
+ Thrown when the wait times out.
75
+ Exception
76
+ On any error.
77
+ """
78
+ if spikesafe_info.supports_discharge_query:
79
+ # when Discharge Complete query is supported, query for completion
80
+ expected_compliance_voltage_discharge_time = Discharge.get_spikesafe_channel_discharge_time(compliance_voltage)
81
+ expected_max_model_discharge_time = Discharge.get_spikesafe_channel_discharge_time(spikesafe_info.maximum_compliance_voltage + 35) # 35V gives extra time, some windows pcs return early from wait function
82
+
83
+ start_time = time.time()
84
+ # wait until the channel is fully discharged before disconnecting the load
85
+ is_discharge_complete = ""
86
+ while True:
87
+ spikesafe_socket.send_scpi_command(f"OUTP{channel_number}:DISC:COMP?", enable_logging)
88
+ is_discharge_complete = spikesafe_socket.read_data(enable_logging)
89
+
90
+ if is_discharge_complete == "TRUE":
91
+ break
92
+
93
+ elapsed_seconds = time.time() - start_time
94
+ if elapsed_seconds > expected_max_model_discharge_time:
95
+ raise TimeoutError(f"SpikeSafe channel discharge expected discharge time is {expected_compliance_voltage_discharge_time} seconds, but this exceeded the model {spikesafe_info.maximum_compliance_voltage}V maximum time of {expected_max_model_discharge_time} seconds.")
96
+ else:
97
+ # when Discharge Complete query is not supported, use a calculated wait time
98
+ wait_time = Discharge.get_spikesafe_channel_discharge_time(compliance_voltage + 35) # 35V gives extra time, some windows pcs return early from wait function
99
+ wait(wait_time)
100
+
101
+ def get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float:
102
+ """
103
+ Obsolete: use Discharge.get_spikesafe_channel_discharge_time instead
104
+ """
105
+ return Discharge.get_spikesafe_channel_discharge_time(compliance_voltage)
@@ -1,4 +1,5 @@
1
1
  from .SpikeSafeEnums import LoadImpedance, RiseTime
2
+ from .SpikeSafeInfo import SpikeSafeInfo
2
3
 
3
4
  class PulseWidthCorrection:
4
5
  """
@@ -12,7 +13,7 @@ class PulseWidthCorrection:
12
13
 
13
14
  @staticmethod
14
15
  def get_optimum_pulse_width_correction(
15
- spikesafe_model_max_current_amps: float,
16
+ spikesafe_info: SpikeSafeInfo,
16
17
  set_current_amps: float,
17
18
  load_impedance: LoadImpedance,
18
19
  rise_time: RiseTime
@@ -22,8 +23,8 @@ class PulseWidthCorrection:
22
23
 
23
24
  Parameters
24
25
  ----------
25
- spikesafe_model_max_current_amps : float
26
- Maximum current of the SpikeSafe model.
26
+ spikesafe_info : SpikeSafeInfo
27
+ An object containing the SpikeSafe information.
27
28
  set_current_amps : float
28
29
  Current to be set on SpikeSafe.
29
30
  load_impedance : LoadImpedance
@@ -48,25 +49,25 @@ class PulseWidthCorrection:
48
49
  default_correction = "1.250"
49
50
 
50
51
  # Test to see if the current is above the model capability
51
- if set_current_amps > spikesafe_model_max_current_amps:
52
- raise ValueError(f'Measurement current {set_current_amps}A exceeds SpikeSafe model maximum current capability of {spikesafe_model_max_current_amps}A.')
52
+ if set_current_amps > spikesafe_info.maximum_set_current:
53
+ raise ValueError(f'Measurement current {set_current_amps}A exceeds SpikeSafe model maximum current capability of {spikesafe_info.maximum_set_current}A.')
53
54
 
54
55
  # Dictionary to store values for different model max currents, arranged by max current of model number. The 9th order coefficient is first.
55
56
  model_params = PulseWidthCorrection.__get_optimum_pulse_width_correction_table()
56
57
 
57
58
  # Check if the given spikesafe_model_max_current_amps is in the model_params, if it is not, just return the default correction
58
- if spikesafe_model_max_current_amps not in model_params:
59
+ if spikesafe_info.maximum_set_current not in model_params:
59
60
  return default_correction
60
61
 
61
62
  # Determine if we are in the high or low range
62
- low_current_range_maximum = model_params[spikesafe_model_max_current_amps]['low_current_range_maximum']
63
+ low_current_range_maximum = model_params[spikesafe_info.maximum_set_current]['low_current_range_maximum']
63
64
  if set_current_amps > low_current_range_maximum:
64
65
  current_range = 'HIGH'
65
66
  else:
66
67
  current_range = 'LOW'
67
68
 
68
69
  # Retrieve dictionary
69
- dictionary = model_params[spikesafe_model_max_current_amps][(current_range, load_impedance, rise_time)]
70
+ dictionary = model_params[spikesafe_info.maximum_set_current][(current_range, load_impedance, rise_time)]
70
71
 
71
72
  # Check if the set current exceeds the max test current, if it does evaluate polynomial at max test current
72
73
  if set_current_amps > dictionary['max_test_current']:
@@ -74,7 +75,7 @@ class PulseWidthCorrection:
74
75
 
75
76
  # Retrieve the polynomial coefficients for the given conditions, if there is no definition for these conditions, return the default correction value
76
77
  try:
77
- coefficient_data = model_params[spikesafe_model_max_current_amps][(current_range, load_impedance, rise_time)]
78
+ coefficient_data = model_params[spikesafe_info.maximum_set_current][(current_range, load_impedance, rise_time)]
78
79
  # Extract nested coefficients
79
80
  polynomial_coefficients = coefficient_data['coefficients']
80
81
  except KeyError:
@@ -86,10 +87,12 @@ class PulseWidthCorrection:
86
87
  # Evaluate the polynomial
87
88
  correction_value = sum(c * (set_current_amps ** i) for i, c in enumerate(reversed(polynomial_coefficients)))
88
89
 
89
- # If needed, coerce the Pulse Width Correction Value to be within firmware programmable limits
90
- if correction_value > 50:
91
- correction_value = 50
92
- if correction_value < 0: # do not allow negative correction, usually this is caused by polynomial calculation issues
90
+ # Force to maximum pulse correction due to firmware limitations
91
+ if correction_value > spikesafe_info.maximum_pulse_width_correction_override:
92
+ correction_value = spikesafe_info.maximum_pulse_width_correction_override
93
+
94
+ # Negative values are not valid and should not be taken into account when generated by the polynomial
95
+ if correction_value < 0:
93
96
  correction_value = 0
94
97
 
95
98
  return f"{correction_value:.3f}"
@@ -2624,4 +2627,8 @@ def get_optimum_pulse_width_correction(
2624
2627
  """
2625
2628
  Obsolete: use PulseWidthCorrection.get_optimum_pulse_width_correction() instead
2626
2629
  """
2627
- return PulseWidthCorrection.get_optimum_pulse_width_correction(spikesafe_model_max_current_amps, set_current_amps, load_impedance, rise_time)
2630
+ spikesafe_info = SpikeSafeInfo()
2631
+ spikesafe_info.maximum_pulse_width_correction_override = 9
2632
+ spikesafe_info.maximum_set_current = spikesafe_model_max_current_amps
2633
+
2634
+ return PulseWidthCorrection.get_optimum_pulse_width_correction(spikesafe_info, set_current_amps, load_impedance, rise_time)
@@ -7,6 +7,7 @@
7
7
 
8
8
  from __future__ import annotations
9
9
  import logging
10
+ import time
10
11
  from .EventData import EventData
11
12
  from .SpikeSafeError import SpikeSafeError
12
13
  from .TcpSocket import TcpSocket
@@ -99,7 +100,8 @@ class ReadAllEvents:
99
100
  def read_until_event(
100
101
  spike_safe_socket: TcpSocket,
101
102
  code: int,
102
- enable_logging: bool | None = None
103
+ enable_logging: bool | None = None,
104
+ timeout: float | None = None
103
105
  ) -> list[EventData]:
104
106
  """Returns an array of all events from the SpikeSafe event queue until a specific event is read.
105
107
 
@@ -111,6 +113,8 @@ class ReadAllEvents:
111
113
  Event code for desired event
112
114
  enable_logging : bool, Optional
113
115
  Overrides spike_safe_socket.enable_logging attribute (default to None will use spike_safe_socket.enable_logging value)
116
+ timeout: float, Optional
117
+ Maximum time in seconds to wait for the desired event before raising an exception (default to None will wait indefinitely)
114
118
 
115
119
  Returns
116
120
  -------
@@ -130,7 +134,10 @@ class ReadAllEvents:
130
134
  event_data_list = []
131
135
 
132
136
  # initialize flag to check if event queue is empty
133
- has_desired_event_occurred = False
137
+ has_desired_event_occurred = False
138
+
139
+ # Record the start time
140
+ start_time = time.time()
134
141
 
135
142
  # run as long as there is an event in the SpikeSafe queue
136
143
  while has_desired_event_occurred == False:
@@ -157,7 +164,12 @@ class ReadAllEvents:
157
164
  has_desired_event_occurred = True
158
165
  else:
159
166
  # unexpected response detected from SpikeSafe, end checking
160
- raise Exception('No event response from SpikeSafe: {}'.format(event_response))
167
+ raise Exception('No event response from SpikeSafe: {}'.format(event_response))
168
+
169
+ # if a timeout value was provided, check if the timeout has been exceeded and raise an exception if so
170
+ if timeout is not None:
171
+ if time.time() - start_time > timeout:
172
+ raise Exception(f'Timeout of {timeout} seconds exceeded while waiting for event code {code}')
161
173
 
162
174
  # return event list to caller
163
175
  return event_data_list
@@ -28,6 +28,9 @@ class SpikeSafeEvents(IntEnum):
28
28
  MAC_ADDRESS_IS_NOT_CONFIGURED = 123
29
29
  NO_EXTERNAL_SOURCE_TRIGGER_OUTPUT_DUE_TO_PULSE_WIDTH_ADJUSTMENT_IS_DISABLED_INTERNALLY = 124
30
30
  MAX_COMPLIANCE_VOLTAGE_EXCEEDED_LIMIT = 125
31
+ CURRENT_RAMP_RATE_RESTORED = 126
32
+ STAIRCASE_SWEEP_IS_COMPLETED = 127
33
+ STAIRCASE_SWEEP_SHUTDOWN_DUE_TO_ERROR = 128
31
34
  MAX_COMPLIANCE_VOLTAGE_EXCEEDED = 200
32
35
  HIGH_SIDE_OVER_CURRENT = 201
33
36
  LOW_SIDE_OVER_CURRENT = 202
@@ -180,3 +183,5 @@ class SpikeSafeEvents(IntEnum):
180
183
  EXCEED_MAX_SAMPLING_CUSTOM_SEQUENCE_APERTURE = 509
181
184
  CANNOT_COMMUNICATE_WITH_DIGITIZER = 510
182
185
  INVALID_VOLTAGE_PROTECTION_MODE = 600
186
+ INVALID_STAIRCASE_SWEEP_ON_TIME = 602
187
+ INVALID_STAIRCASE_SWEEP_STEP_COUNT = 603
@@ -76,6 +76,7 @@ class SpikeSafeInfo:
76
76
  maximum_set_current: float | None
77
77
  minimum_pulse_width: float | None
78
78
  maximum_pulse_width: float | None
79
+ maximum_pulse_width_correction_override: float | None
79
80
  minimum_pulse_width_offset: float | None
80
81
  maximum_pulse_width_offset: float | None
81
82
 
@@ -107,6 +108,7 @@ class SpikeSafeInfo:
107
108
  self.maximum_set_current = None
108
109
  self.minimum_pulse_width = None
109
110
  self.maximum_pulse_width = None
111
+ self.maximum_pulse_width_correction_override = None
110
112
  self.minimum_pulse_width_offset = None
111
113
  self.maximum_pulse_width_offset = None
112
114
 
@@ -49,6 +49,7 @@ class SpikeSafeInfoParser:
49
49
  spikesafe_info.maximum_pulse_width = SpikeSafeInfoParser._query_float(spike_safe_socket, 'SOUR0:PULS:TON? MAX', enable_logging)
50
50
  spikesafe_info.minimum_pulse_width_offset = SpikeSafeInfoParser._query_float(spike_safe_socket, 'SOUR0:PULS:OFFS? MIN', enable_logging)
51
51
  spikesafe_info.maximum_pulse_width_offset = SpikeSafeInfoParser._query_float(spike_safe_socket, 'SOUR0:PULS:OFFS? MAX', enable_logging)
52
+ spikesafe_info.maximum_pulse_width_correction_override = 50 if SpikeSafeInfoParser.compare_rev_version(spikesafe_info.version, "3.0.15.5") else 9 # Older firmware had a max correction of 9us, but newer firmware supports up to 50us to compensate for the Pulse Width Adjustment algorithm's maximum adjustment
52
53
 
53
54
  spike_safe_socket.send_scpi_command('MEM:DATA ZINNUM?')
54
55
  zin_number = spike_safe_socket.read_data(enable_logging)
@@ -63,9 +63,10 @@ class TcpSocket:
63
63
  try:
64
64
  self.socket_ip_address = ip_address
65
65
 
66
- # create socket with 2 second timeout and connect to SpikeSafe
66
+ # create socket with 2 second timeout, 512 byte send max buffer to match SpikeSafe's command buffer, and connect to SpikeSafe
67
67
  self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
68
68
  self.tcp_socket.settimeout(2)
69
+ self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 512)
69
70
 
70
71
  if (self.enable_logging):
71
72
  log.log(self.default_log_level, self.__get_formatted_log_message__('Connecting...'))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spikesafe-python
3
- Version: 1.11.22
3
+ Version: 1.13.5
4
4
  Summary: SpikeSafe Python Library
5
5
  Author-email: Vektrex <support@vektrex.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -38,6 +38,7 @@ tests/test_digitizer_fetch_time_of_sampling.py
38
38
  tests/test_digitizer_fetch_time_of_sampling_old.py
39
39
  tests/test_event_data.py
40
40
  tests/test_event_data_old.py
41
+ tests/test_get_new_voltage_data_estimated_complete_time.py
41
42
  tests/test_optimum_compensation.py
42
43
  tests/test_optimum_compensation_old.py
43
44
  tests/test_optimum_pulse_width_correction.py
@@ -0,0 +1,26 @@
1
+ import pytest
2
+ from spikesafe_python.DigitizerDataFetch import DigitizerDataFetch
3
+
4
+ @pytest.mark.parametrize(
5
+ "aperture_microseconds, reading_count, hardware_trigger_count, hardware_trigger_delay_microseconds, expected",
6
+ [
7
+ # 488us aperture, 1 reading, 1 hardware trigger, 200us delay
8
+ (488, 1, 1, 200, 0.000688),
9
+ # 500us aperture, 1 reading, 1 hardware trigger, 150us delay
10
+ (500, 1, 1, 150, 0.00065),
11
+ # 694us aperture, 1 reading, 1 hardware trigger, 0us delay
12
+ (694, 1, 1, 0, 0.000694),
13
+ # 25ms aperture, 1 reading, 1 hardware trigger, 3ms delay
14
+ (25000, 1, 1, 3000, 0.028),
15
+ ]
16
+ )
17
+ def test_get_new_voltage_data_estimated_complete_time(
18
+ aperture_microseconds, reading_count, hardware_trigger_count, hardware_trigger_delay_microseconds, expected
19
+ ):
20
+ result = DigitizerDataFetch.get_new_voltage_data_estimated_complete_time(
21
+ aperture_microseconds,
22
+ reading_count,
23
+ hardware_trigger_count,
24
+ hardware_trigger_delay_microseconds
25
+ )
26
+ assert pytest.approx(result, rel=1e-6) == expected
@@ -0,0 +1,38 @@
1
+ import pytest
2
+ import spikesafe_python
3
+
4
+ @pytest.mark.parametrize("maximum_pulse_width_correction_override, model_max_current, set_current, load_impedance, rise_time, expected", [
5
+ (50, 10, 4, spikesafe_python.SpikeSafeEnums.LoadImpedance.VERY_LOW, spikesafe_python.SpikeSafeEnums.RiseTime.VERY_SLOW, '2.476'), # Add expected value
6
+ (9, 0.05, 0.0039, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '9.000'), # test 50mA low range first entry for older firmware with 9us max correction override
7
+ (50, 0.05, 0.0039, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '11.289'), # test 50mA low range first entry
8
+ (50, 0.5, 0.0159, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.943'), # test 500mA low range first entry < 0.016 max_test_current
9
+ (50, 0.5, 0.0160, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range first entry = 0.016 max_test_current
10
+ (50, 0.5, 0.0161, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range first entry > 0.016 max_test_current
11
+ (50, 0.5, 0.038, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range second entry = 0.038 max_test_current
12
+ (50, 0.5, 0.470, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.495'), # test high 500mA range first entry < 0.470 max_test_current
13
+ (50, 0.5, 0.471, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high 500mA range first entry = 0.471 max_test_current
14
+ (50, 0.5, 0.472, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high 500mA range first entry > 0.472 max_test_current
15
+ (50, 0.5, 0.5, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high model_max_current 500mA range = 0.5 set_current_amps
16
+ (50, 5, 0.001, spikesafe_python.SpikeSafeEnums.LoadImpedance.VERY_LOW, spikesafe_python.SpikeSafeEnums.RiseTime.VERY_SLOW, '50.000'), # test calculated >9us correction coerced to 9us
17
+ (50, 0.5, 0.0438, spikesafe_python.SpikeSafeEnums.LoadImpedance.MEDIUM, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '0.000'), # test calculated negative correction coerced to 0
18
+ (50, 25, 21, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.250'), # test invalid 25A model
19
+ ])
20
+ def test_get_optimum_pulse_width_correction_value(maximum_pulse_width_correction_override, model_max_current, set_current, load_impedance, rise_time, expected):
21
+ spikesafe_info = spikesafe_python.SpikeSafeInfo()
22
+ spikesafe_info.maximum_pulse_width_correction_override = maximum_pulse_width_correction_override
23
+ spikesafe_info.maximum_set_current = model_max_current
24
+
25
+ correction_value = spikesafe_python.PulseWidthCorrection.get_optimum_pulse_width_correction(spikesafe_info, set_current, load_impedance, rise_time)
26
+ assert correction_value == expected
27
+
28
+ @pytest.mark.parametrize("model_max_current, set_current, load_impedance, rise_time, expected_error_message", [
29
+ (0.05, 0.051, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, 'Measurement current 0.051A exceeds SpikeSafe model maximum current capability of 0.05A.'), # test set_current > model_max_current
30
+ ])
31
+ def test_test_get_optimum_pulse_width_correction_exceptions(model_max_current, set_current, load_impedance, rise_time, expected_error_message):
32
+ spikesafe_info = spikesafe_python.SpikeSafeInfo()
33
+ spikesafe_info.maximum_pulse_width_correction_override = 9
34
+ spikesafe_info.maximum_set_current = model_max_current
35
+
36
+ with pytest.raises(ValueError) as exc_info:
37
+ spikesafe_python.PulseWidthCorrection.get_optimum_pulse_width_correction(spikesafe_info, set_current, load_impedance, rise_time)
38
+ assert expected_error_message in str(exc_info.value)
@@ -4,7 +4,7 @@ from spikesafe_python.SpikeSafeEnums import LoadImpedance, RiseTime
4
4
 
5
5
  @pytest.mark.parametrize("model_max_current, set_current, load_impedance, rise_time, expected", [
6
6
  (10, 4, LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW, '2.476'), # Add expected value
7
- (0.05, 0.0039, LoadImpedance.HIGH, RiseTime.FAST, '11.289'), # test 50mA low range first entry
7
+ (0.05, 0.0039, LoadImpedance.HIGH, RiseTime.FAST, '9.000'), # test 50mA low range first entry
8
8
  (0.5, 0.0159, LoadImpedance.HIGH, RiseTime.FAST, '1.943'), # test 500mA low range first entry < 0.016 max_test_current
9
9
  (0.5, 0.0160, LoadImpedance.HIGH, RiseTime.FAST, '1.924'), # test 500mA low range first entry = 0.016 max_test_current
10
10
  (0.5, 0.0161, LoadImpedance.HIGH, RiseTime.FAST, '1.924'), # test 500mA low range first entry > 0.016 max_test_current
@@ -13,7 +13,7 @@ from spikesafe_python.SpikeSafeEnums import LoadImpedance, RiseTime
13
13
  (0.5, 0.471, LoadImpedance.HIGH, RiseTime.FAST, '1.496'), # test high 500mA range first entry = 0.471 max_test_current
14
14
  (0.5, 0.472, LoadImpedance.HIGH, RiseTime.FAST, '1.496'), # test high 500mA range first entry > 0.472 max_test_current
15
15
  (0.5, 0.5, LoadImpedance.HIGH, RiseTime.FAST, '1.496'), # test high model_max_current 500mA range = 0.5 set_current_amps
16
- (5, 0.001, LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW, '50.000'), # test calculated >50us correction coerced to 50us
16
+ (5, 0.001, LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW, '9.000'), # test calculated >9us correction coerced to 9us
17
17
  (0.5, 0.0438, LoadImpedance.MEDIUM, RiseTime.FAST, '0.000'), # test calculated negative correction coerced to 0
18
18
  (25, 21, LoadImpedance.HIGH, RiseTime.FAST, '1.250'), # test invalid 25A model
19
19
  ])
@@ -1,75 +0,0 @@
1
- from .TcpSocket import TcpSocket
2
-
3
- class Discharge:
4
- """
5
- Class for calculating SpikeSafe channel discharge time based on compliance voltage.
6
-
7
- Methods
8
- -------
9
- get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float
10
- Returns the time in seconds to fully discharge the SpikeSafe channel based on the compliance voltage
11
- wait_for_spikesafe_channel_discharge(tcp_socket: TcpSocket, channel_number: int) -> None
12
- Waits for the SpikeSafe channel to fully discharge
13
- """
14
-
15
- @staticmethod
16
- def get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float:
17
- """
18
- Returns the time in seconds to fully discharge the SpikeSafe channel based on the compliance voltage
19
-
20
- Parameters
21
- ----------
22
- compliance_voltage : float
23
- Compliance voltage to factor in discharge time
24
-
25
- Returns
26
- -------
27
- float
28
- Discharge time in seconds
29
-
30
- Raises
31
- ------
32
- None
33
- """
34
- # Discharge time accounting for compliance voltage, voltage readroom, and discharge voltage per second
35
- voltage_headroom_voltage = 7
36
- discharge_voltage_per_second = 1000
37
- discharge_time = (compliance_voltage + voltage_headroom_voltage) / discharge_voltage_per_second
38
- return discharge_time
39
-
40
- @staticmethod
41
- def wait_for_spikesafe_channel_discharge(
42
- tcp_socket: TcpSocket,
43
- channel_number: int,
44
- enable_logging: bool | None = None
45
- ) -> None:
46
- """
47
- Waits for the SpikeSafe channel to fully discharge
48
-
49
- Parameters
50
- ----------
51
- tcp_socket : TcpSocket
52
- TCP socket connection to the SpikeSafe device
53
- channel_number : int
54
- Channel number to poll discharge state of
55
-
56
- Returns
57
- -------
58
- None
59
-
60
- Raises
61
- ------
62
- Exception
63
- On any error
64
- """
65
- # wait until the channel is fully discharged before disconnecting the load
66
- is_discharge_complete = ''
67
- while is_discharge_complete != 'TRUE':
68
- tcp_socket.send_scpi_command(f'OUTP{channel_number}:DISC:COMP?', enable_logging)
69
- is_discharge_complete = tcp_socket.read_data(enable_logging)
70
-
71
- def get_spikesafe_channel_discharge_time(compliance_voltage: float) -> float:
72
- """
73
- Obsolete: use Discharge.get_spikesafe_channel_discharge_time instead
74
- """
75
- return Discharge.get_spikesafe_channel_discharge_time(compliance_voltage)
@@ -1,29 +0,0 @@
1
- import pytest
2
- import spikesafe_python
3
-
4
- @pytest.mark.parametrize("model_max_current, set_current, load_impedance, rise_time, expected", [
5
- (10, 4, spikesafe_python.SpikeSafeEnums.LoadImpedance.VERY_LOW, spikesafe_python.SpikeSafeEnums.RiseTime.VERY_SLOW, '2.476'), # Add expected value
6
- (0.05, 0.0039, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '11.289'), # test 50mA low range first entry
7
- (0.5, 0.0159, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.943'), # test 500mA low range first entry < 0.016 max_test_current
8
- (0.5, 0.0160, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range first entry = 0.016 max_test_current
9
- (0.5, 0.0161, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range first entry > 0.016 max_test_current
10
- (0.5, 0.038, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.924'), # test 500mA low range second entry = 0.038 max_test_current
11
- (0.5, 0.470, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.495'), # test high 500mA range first entry < 0.470 max_test_current
12
- (0.5, 0.471, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high 500mA range first entry = 0.471 max_test_current
13
- (0.5, 0.472, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high 500mA range first entry > 0.472 max_test_current
14
- (0.5, 0.5, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.496'), # test high model_max_current 500mA range = 0.5 set_current_amps
15
- (5, 0.001, spikesafe_python.SpikeSafeEnums.LoadImpedance.VERY_LOW, spikesafe_python.SpikeSafeEnums.RiseTime.VERY_SLOW, '50.000'), # test calculated >50us correction coerced to 50us
16
- (0.5, 0.0438, spikesafe_python.SpikeSafeEnums.LoadImpedance.MEDIUM, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '0.000'), # test calculated negative correction coerced to 0
17
- (25, 21, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, '1.250'), # test invalid 25A model
18
- ])
19
- def test_get_optimum_pulse_width_correction_value(model_max_current, set_current, load_impedance, rise_time, expected):
20
- correction_value = spikesafe_python.PulseWidthCorrection.get_optimum_pulse_width_correction(model_max_current, set_current, load_impedance, rise_time)
21
- assert correction_value == expected
22
-
23
- @pytest.mark.parametrize("model_max_current, set_current, load_impedance, rise_time, expected_error_message", [
24
- (0.05, 0.051, spikesafe_python.SpikeSafeEnums.LoadImpedance.HIGH, spikesafe_python.SpikeSafeEnums.RiseTime.FAST, 'Measurement current 0.051A exceeds SpikeSafe model maximum current capability of 0.05A.'), # test set_current > model_max_current
25
- ])
26
- def test_test_get_optimum_pulse_width_correction_exceptions(model_max_current, set_current, load_impedance, rise_time, expected_error_message):
27
- with pytest.raises(ValueError) as exc_info:
28
- spikesafe_python.PulseWidthCorrection.get_optimum_pulse_width_correction(model_max_current, set_current, load_impedance, rise_time)
29
- assert expected_error_message in str(exc_info.value)