fusaware-instruments 0.1.0__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.
- fusaware_instruments/__init__.py +0 -0
- fusaware_instruments/utils.py +108 -0
- fusaware_instruments/visa_instrument_configuration/__init__.py +0 -0
- fusaware_instruments/visa_instrument_configuration/analog_probe.py +441 -0
- fusaware_instruments/visa_instrument_configuration/analog_probe_rigol_mso5000.py +44 -0
- fusaware_instruments/visa_instrument_configuration/dc_voltage_dmm.py +75 -0
- fusaware_instruments/visa_instrument_configuration/dc_voltage_supply.py +205 -0
- fusaware_instruments/visa_instrument_configuration/dc_voltage_supply_rigol_dp800.py +38 -0
- fusaware_instruments/visa_instrument_configuration/dc_voltage_supply_simulator.py +165 -0
- fusaware_instruments/visa_instrument_configuration/oscilloscope.py +408 -0
- fusaware_instruments/visa_instrument_drivers/__init__.py +3 -0
- fusaware_instruments/visa_instrument_drivers/driver_dc_power_supply_simulator.py +151 -0
- fusaware_instruments/visa_instrument_drivers/driver_keysight_e36150.py +183 -0
- fusaware_instruments/visa_instrument_drivers/driver_lecroy_wavejet.py +41 -0
- fusaware_instruments/visa_instrument_drivers/driver_rigol_dm3000.py +134 -0
- fusaware_instruments/visa_instrument_drivers/driver_rigol_dp_base.py +192 -0
- fusaware_instruments/visa_instrument_drivers/driver_rigol_mso_base.py +256 -0
- fusaware_instruments/visa_instrument_drivers/driver_tektronix_mdo_3_series.py +1209 -0
- fusaware_instruments/visa_instrument_hal/__init__.py +0 -0
- fusaware_instruments/visa_instrument_hal/hal_dc_power_supply.py +113 -0
- fusaware_instruments/visa_instrument_hal/hal_dc_power_supply_rigol_dp.py +260 -0
- fusaware_instruments/visa_instrument_hal/hal_dc_power_supply_simulator.py +203 -0
- fusaware_instruments/visa_instrument_hal/hal_dmm.py +151 -0
- fusaware_instruments/visa_instrument_hal/hal_dmm_keithley_dmm_6500.py +226 -0
- fusaware_instruments/visa_instrument_hal/hal_dmm_rigol_dm3000.py +290 -0
- fusaware_instruments/visa_instrument_hal/hal_dmm_rigol_dm3000_v2.py +112 -0
- fusaware_instruments/visa_instrument_hal/hal_oscilloscope.py +344 -0
- fusaware_instruments/visa_instrument_hal/hal_oscilloscope_rigol_mso.py +653 -0
- fusaware_instruments/visa_instrument_hal/hal_oscilloscope_tektronix_mdo_3_series.py +907 -0
- fusaware_instruments-0.1.0.dist-info/METADATA +42 -0
- fusaware_instruments-0.1.0.dist-info/RECORD +33 -0
- fusaware_instruments-0.1.0.dist-info/WHEEL +4 -0
- fusaware_instruments-0.1.0.dist-info/licenses/LICENSE +1 -0
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# pyright: strict
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
import pyvisa as pv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VisaIO(Protocol):
|
|
12
|
+
def query(self, message: str) -> str: ...
|
|
13
|
+
|
|
14
|
+
def write(self, message: str) -> int: ...
|
|
15
|
+
|
|
16
|
+
def read(self) -> str: ...
|
|
17
|
+
|
|
18
|
+
def read_bytes(self, count: int) -> bytes: ...
|
|
19
|
+
|
|
20
|
+
def read_raw(self, size: int | None = None) -> bytes: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PyvisaIO(VisaIO):
|
|
24
|
+
def __init__(self, resource: pv.resources.MessageBasedResource):
|
|
25
|
+
self._resource = resource
|
|
26
|
+
|
|
27
|
+
def query(self, message: str) -> str:
|
|
28
|
+
resp = self._resource.query(message)
|
|
29
|
+
logging.debug(
|
|
30
|
+
f"Query: '{message}' -> Response: '{resp[:20] if len(resp) > 20 else resp}'"
|
|
31
|
+
)
|
|
32
|
+
return resp
|
|
33
|
+
|
|
34
|
+
def write(self, message: str) -> int:
|
|
35
|
+
logging.debug(f"Write: '{message}'")
|
|
36
|
+
return self._resource.write(message)
|
|
37
|
+
|
|
38
|
+
def read(self) -> str:
|
|
39
|
+
resp = self._resource.read()
|
|
40
|
+
logging.debug(f"Read: '{resp}'")
|
|
41
|
+
return resp
|
|
42
|
+
|
|
43
|
+
def read_bytes(self, count: int) -> bytes:
|
|
44
|
+
resp = self._resource.read_bytes(count)
|
|
45
|
+
logging.debug(f"Read bytes: {resp[:20] if len(resp) > 20 else resp}")
|
|
46
|
+
return resp
|
|
47
|
+
|
|
48
|
+
def read_raw(self, size: int | None = None) -> bytes:
|
|
49
|
+
resp = self._resource.read_raw(size)
|
|
50
|
+
logging.debug(f"Read raw: {resp[:20] if len(resp) > 20 else resp}")
|
|
51
|
+
return resp
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ScpiParseError(Exception):
|
|
56
|
+
response: str
|
|
57
|
+
query: str
|
|
58
|
+
message: str | None = None
|
|
59
|
+
|
|
60
|
+
def __str__(self):
|
|
61
|
+
if self.message is None:
|
|
62
|
+
return f"ScpiParseError: Could not parse response '{self.response}' for query '{self.query}'"
|
|
63
|
+
else:
|
|
64
|
+
return self.message
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class ReconcileException[T](Exception):
|
|
69
|
+
param_name: str
|
|
70
|
+
expected: T
|
|
71
|
+
actual: T
|
|
72
|
+
message: str | None = None
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
if self.message is None:
|
|
76
|
+
return f"ReconcileException: Parameter '{self.param_name}' did not reconcile: expected '{self.expected}' but got '{self.actual}'"
|
|
77
|
+
else:
|
|
78
|
+
return self.message
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class MappingDirection(Enum):
|
|
82
|
+
ConfigToHal = "CONFIG_TO_HAL"
|
|
83
|
+
HalToConfig = "HAL_TO_CONFIG"
|
|
84
|
+
HalToDriver = "HAL_TO_DRIVER"
|
|
85
|
+
DriverToHal = "DRIVER_TO_HAL"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class MappingException[T](Exception):
|
|
90
|
+
direction: MappingDirection
|
|
91
|
+
from_type: type
|
|
92
|
+
to_type: type
|
|
93
|
+
value: T
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
match self.direction:
|
|
97
|
+
case MappingDirection.ConfigToHal:
|
|
98
|
+
return f"Value '{self.value}' of type {self.from_type} cannot be mapped to HAL representation of type {self.to_type}."
|
|
99
|
+
case MappingDirection.HalToConfig:
|
|
100
|
+
return f"Value '{self.value}' of type {self.to_type} cannot be mapped to config representation of type {self.from_type}."
|
|
101
|
+
case MappingDirection.HalToDriver:
|
|
102
|
+
return f"Value '{self.value}' of type {self.to_type} cannot be mapped to driver representation of type {self.from_type}."
|
|
103
|
+
case MappingDirection.DriverToHal:
|
|
104
|
+
return f"Value '{self.value}' of type {self.from_type} cannot be mapped to HAL representation of type {self.to_type}."
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def compare_floats(a: float, b: float, tolerance: float = 1e-9) -> bool:
|
|
108
|
+
return abs(a - b) <= tolerance
|
|
File without changes
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# pyright: strict
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, validate_call
|
|
10
|
+
|
|
11
|
+
from fusaware_instruments.visa_instrument_hal.hal_oscilloscope import (
|
|
12
|
+
Attenuation,
|
|
13
|
+
HALOscilloscope,
|
|
14
|
+
ProbeAttenuation,
|
|
15
|
+
ProbeId,
|
|
16
|
+
UnknownProbe,
|
|
17
|
+
)
|
|
18
|
+
from fusaware_instruments.visa_instrument_hal.hal_oscilloscope import (
|
|
19
|
+
ProbeCoupling as HALCoupling,
|
|
20
|
+
)
|
|
21
|
+
from fusaware_instruments.visa_instrument_hal.hal_oscilloscope import Units as HALUnits
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Coupling(Enum):
|
|
25
|
+
AC = "ac"
|
|
26
|
+
DC = "dc"
|
|
27
|
+
Ground = "ground"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class State(Enum):
|
|
31
|
+
Disabled = "disabled"
|
|
32
|
+
Enabled = "enabled"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Units(str, Enum):
|
|
36
|
+
Amps = "amps"
|
|
37
|
+
Volts = "volts"
|
|
38
|
+
Other = "other"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AnalogProbeState(BaseModel):
|
|
42
|
+
attenuation: float
|
|
43
|
+
bandwidth_limit: float | int | str
|
|
44
|
+
coupling: Coupling
|
|
45
|
+
offset: float
|
|
46
|
+
scale: float
|
|
47
|
+
state: State
|
|
48
|
+
units: Units
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AnalogProbe(ABC):
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def configure_analog_probe(
|
|
54
|
+
self,
|
|
55
|
+
attenuation: float,
|
|
56
|
+
bandwidth_limit: float | int | str,
|
|
57
|
+
coupling: Coupling,
|
|
58
|
+
offset: float | int,
|
|
59
|
+
scale: float | int,
|
|
60
|
+
state: State,
|
|
61
|
+
units: Units,
|
|
62
|
+
) -> AnalogProbeState:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@validate_call
|
|
67
|
+
def configure_analog_probe(
|
|
68
|
+
op: HALOscilloscope,
|
|
69
|
+
attenuation: float | int,
|
|
70
|
+
bandwidth_limit: float | int | str,
|
|
71
|
+
coupling: Coupling,
|
|
72
|
+
scale: float | int,
|
|
73
|
+
offset: float | int,
|
|
74
|
+
state: State,
|
|
75
|
+
units: Units,
|
|
76
|
+
) -> tuple[AnalogProbeState, list[str]]:
|
|
77
|
+
desired_attenuation = Attenuation(attenuation=attenuation)
|
|
78
|
+
desired_bandwidth_limit = bandwidth_limit
|
|
79
|
+
desired_coupling = Coupling(coupling)
|
|
80
|
+
desired_scale = scale
|
|
81
|
+
desired_offset = offset
|
|
82
|
+
desired_state = State(state)
|
|
83
|
+
desired_units = Units(units)
|
|
84
|
+
|
|
85
|
+
probe_id = op.get_probe_id()
|
|
86
|
+
|
|
87
|
+
initial_state = get_state(op)
|
|
88
|
+
initial_units = get_units(op)
|
|
89
|
+
initial_bandwidth_limit = get_bandwidth_limit(op)
|
|
90
|
+
initial_coupling = get_coupling(op)
|
|
91
|
+
initial_attenuation = get_attenuation(op)
|
|
92
|
+
initial_scale = get_scale(op)
|
|
93
|
+
initial_offset = get_offset(op)
|
|
94
|
+
|
|
95
|
+
if not _is_state_equal(initial_state, desired_state):
|
|
96
|
+
set_state(op, desired_state)
|
|
97
|
+
|
|
98
|
+
if not _is_units_equal(desired_units, initial_units):
|
|
99
|
+
set_units(op, desired_units)
|
|
100
|
+
|
|
101
|
+
if not _is_attenuation_equal(desired_attenuation, initial_attenuation):
|
|
102
|
+
set_attenuation(op, desired_attenuation)
|
|
103
|
+
|
|
104
|
+
if not _is_scale_equal(desired_scale, initial_scale):
|
|
105
|
+
set_scale(op, desired_scale)
|
|
106
|
+
|
|
107
|
+
if not _is_offset_equal(desired_offset, initial_offset):
|
|
108
|
+
set_offset(op, desired_offset)
|
|
109
|
+
|
|
110
|
+
if not _is_bandwidth_limit_equal(desired_bandwidth_limit, initial_bandwidth_limit):
|
|
111
|
+
set_bandwidth_limit(op, desired_bandwidth_limit)
|
|
112
|
+
|
|
113
|
+
if not _is_coupling_equal(desired_coupling, initial_coupling):
|
|
114
|
+
set_coupling(op, desired_coupling)
|
|
115
|
+
|
|
116
|
+
final_state = get_state(op)
|
|
117
|
+
final_units = get_units(op)
|
|
118
|
+
final_attenuation = get_attenuation(op)
|
|
119
|
+
final_scale = get_scale(op)
|
|
120
|
+
final_offset = get_offset(op)
|
|
121
|
+
final_coupling = get_coupling(op)
|
|
122
|
+
final_bandwidth_limit = get_bandwidth_limit(op)
|
|
123
|
+
|
|
124
|
+
errors: list[str] = []
|
|
125
|
+
|
|
126
|
+
# TODO make these errors a type or something
|
|
127
|
+
if not _is_state_equal(final_state, desired_state):
|
|
128
|
+
errors.append(f"State mismatch: expected {desired_state}, got {final_state}")
|
|
129
|
+
|
|
130
|
+
if not _is_units_equal(final_units, desired_units):
|
|
131
|
+
match probe_id:
|
|
132
|
+
case ProbeId() | UnknownProbe():
|
|
133
|
+
errors.append(
|
|
134
|
+
f"Probe '{probe_id.name()}' does not support units '{desired_units.value}', final units: '{final_units.value}'"
|
|
135
|
+
)
|
|
136
|
+
case None:
|
|
137
|
+
errors.append(
|
|
138
|
+
f"Units mismatch: expected {desired_units}, got {final_units}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# For future use
|
|
142
|
+
|
|
143
|
+
# match probe_id:
|
|
144
|
+
# case ProbeId():
|
|
145
|
+
# valid_units = [unit.value for unit in probe_id.valid_units()]
|
|
146
|
+
# errors.append(
|
|
147
|
+
# f"""
|
|
148
|
+
# Probe '{probe_id.name()}' does not support units '{desired_units.value}'
|
|
149
|
+
# Supported units: {",".join(valid_units)}
|
|
150
|
+
# """
|
|
151
|
+
# )
|
|
152
|
+
# case UnknownProbe():
|
|
153
|
+
# errors.append(
|
|
154
|
+
# f"Probe '{probe_id.name()}' does not support units '{desired_units.value}', check its documentation for more information"
|
|
155
|
+
# )
|
|
156
|
+
# case None:
|
|
157
|
+
# errors.append(
|
|
158
|
+
# f"Units mismatch: expected {desired_units}, got {final_units}"
|
|
159
|
+
# )
|
|
160
|
+
|
|
161
|
+
if not _is_attenuation_equal(desired_attenuation, final_attenuation):
|
|
162
|
+
match probe_id:
|
|
163
|
+
case ProbeId() | UnknownProbe():
|
|
164
|
+
errors.append(
|
|
165
|
+
f"Probe '{probe_id.name()}' does not support attenuation '{desired_attenuation.attenuation}', final attenuation: '{final_attenuation.attenuation}'"
|
|
166
|
+
)
|
|
167
|
+
case None:
|
|
168
|
+
errors.append(
|
|
169
|
+
f"Attenuation mismatch: expected {desired_attenuation.attenuation}, got {final_attenuation.attenuation}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if not _is_scale_equal(desired_scale, final_scale):
|
|
173
|
+
match probe_id:
|
|
174
|
+
case ProbeId() | UnknownProbe():
|
|
175
|
+
errors.append(
|
|
176
|
+
f"Probe '{probe_id.name()}' does not support scale '{desired_scale}', final scale: '{final_scale}'"
|
|
177
|
+
)
|
|
178
|
+
case None:
|
|
179
|
+
errors.append(
|
|
180
|
+
f"Scale mismatch: expected {desired_scale}, got {final_scale}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not _is_offset_equal(desired_offset, final_offset):
|
|
184
|
+
match probe_id:
|
|
185
|
+
case ProbeId() | UnknownProbe():
|
|
186
|
+
errors.append(
|
|
187
|
+
f"Probe '{probe_id.name()}' does not support offset '{desired_offset}', final offset: '{final_offset}'"
|
|
188
|
+
)
|
|
189
|
+
case None:
|
|
190
|
+
errors.append(
|
|
191
|
+
f"Offset mismatch: expected {desired_offset}, got {final_offset}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if not _is_coupling_equal(desired_coupling, final_coupling):
|
|
195
|
+
match probe_id:
|
|
196
|
+
case ProbeId() | UnknownProbe():
|
|
197
|
+
logging.warning(
|
|
198
|
+
f"Probe '{probe_id.name()}' does not support coupling '{desired_coupling.value}', final coupling: '{final_coupling.value}'"
|
|
199
|
+
)
|
|
200
|
+
case None:
|
|
201
|
+
errors.append(
|
|
202
|
+
f"Coupling mismatch: expected {desired_coupling.value}, got {final_coupling.value}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if not _is_bandwidth_limit_equal(desired_bandwidth_limit, final_bandwidth_limit):
|
|
206
|
+
match probe_id:
|
|
207
|
+
case ProbeId() | UnknownProbe():
|
|
208
|
+
logging.warning(
|
|
209
|
+
f"Probe '{probe_id.name()}' does not support bandwidth limit '{desired_bandwidth_limit}', final bandwidth limit: '{final_bandwidth_limit}'"
|
|
210
|
+
)
|
|
211
|
+
case None:
|
|
212
|
+
errors.append(
|
|
213
|
+
f"Bandwidth limit mismatch: expected {desired_bandwidth_limit}, got {final_bandwidth_limit}"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return AnalogProbeState(
|
|
217
|
+
attenuation=final_attenuation.attenuation,
|
|
218
|
+
bandwidth_limit=final_bandwidth_limit,
|
|
219
|
+
coupling=final_coupling,
|
|
220
|
+
scale=final_scale,
|
|
221
|
+
offset=final_offset,
|
|
222
|
+
state=final_state,
|
|
223
|
+
units=final_units,
|
|
224
|
+
), errors
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _is_attenuation_equal(expected: ProbeAttenuation, actual: ProbeAttenuation) -> bool:
|
|
228
|
+
return math.isclose(
|
|
229
|
+
expected.attenuation, actual.attenuation, rel_tol=1e-4, abs_tol=1e-6
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def get_attenuation(op: HALOscilloscope) -> ProbeAttenuation:
|
|
234
|
+
attenuation = op.get_probe_attenuation()
|
|
235
|
+
return attenuation
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def set_attenuation(op: HALOscilloscope, attenuation: ProbeAttenuation) -> None:
|
|
239
|
+
op.set_probe_attenuation(attenuation.attenuation)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _map_bandwidth_limit_to_hal_bandwidth_limit(
|
|
243
|
+
bandwidth_limit: float | int | str,
|
|
244
|
+
) -> Tuple[bool, float | int]:
|
|
245
|
+
if isinstance(bandwidth_limit, str) and bandwidth_limit == "none":
|
|
246
|
+
hal_enable = False
|
|
247
|
+
hal_bandwidth_limit = 0.0
|
|
248
|
+
elif isinstance(bandwidth_limit, (float, int)):
|
|
249
|
+
hal_enable = True
|
|
250
|
+
hal_bandwidth_limit = bandwidth_limit
|
|
251
|
+
else:
|
|
252
|
+
raise ValueError(f"Invalid bandwidth limit {bandwidth_limit}")
|
|
253
|
+
|
|
254
|
+
return hal_enable, hal_bandwidth_limit
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _is_bandwidth_limit_equal(
|
|
258
|
+
expected: float | int | str, actual: float | int | str
|
|
259
|
+
) -> bool:
|
|
260
|
+
match expected, actual:
|
|
261
|
+
case str(), str():
|
|
262
|
+
return expected == actual
|
|
263
|
+
case float() | int(), float() | int():
|
|
264
|
+
return math.isclose(
|
|
265
|
+
expected,
|
|
266
|
+
actual,
|
|
267
|
+
rel_tol=1e-4,
|
|
268
|
+
abs_tol=1e-6,
|
|
269
|
+
)
|
|
270
|
+
case _:
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_bandwidth_limit(op: HALOscilloscope) -> float | int | str:
|
|
275
|
+
enable, bandwidth = op.get_probe_bandwidth_limit()
|
|
276
|
+
|
|
277
|
+
if enable:
|
|
278
|
+
return bandwidth
|
|
279
|
+
else:
|
|
280
|
+
return "none"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def set_bandwidth_limit(
|
|
284
|
+
op: HALOscilloscope, bandwidth_limit: float | int | str
|
|
285
|
+
) -> None:
|
|
286
|
+
hal_enable, hal_bandwidth_limit = _map_bandwidth_limit_to_hal_bandwidth_limit(
|
|
287
|
+
bandwidth_limit
|
|
288
|
+
)
|
|
289
|
+
op.set_probe_bandwidth_limit(hal_enable, hal_bandwidth_limit)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _map_hal_units_to_units(units: HALUnits) -> Units:
|
|
293
|
+
if units == HALUnits.AMPS:
|
|
294
|
+
return Units.Amps
|
|
295
|
+
elif units == HALUnits.CUSTOM:
|
|
296
|
+
return Units.Other
|
|
297
|
+
elif units == HALUnits.VOLTS:
|
|
298
|
+
return Units.Volts
|
|
299
|
+
raise ValueError(f"Invalid hal units: {units}")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _map_units_to_hal_units(units: Units) -> HALUnits:
|
|
303
|
+
if units == Units.Amps:
|
|
304
|
+
return HALUnits.AMPS
|
|
305
|
+
elif units == Units.Other:
|
|
306
|
+
return HALUnits.CUSTOM
|
|
307
|
+
elif units == Units.Volts:
|
|
308
|
+
return HALUnits.VOLTS
|
|
309
|
+
raise ValueError(f"Invalid units: {units}")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _is_units_equal(first: Units, second: Units) -> bool:
|
|
313
|
+
return first == second
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_units(op: HALOscilloscope) -> Units:
|
|
317
|
+
hal_units = op.get_probe_units()
|
|
318
|
+
units = _map_hal_units_to_units(hal_units)
|
|
319
|
+
return units
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def set_units(op: HALOscilloscope, units: Units):
|
|
323
|
+
hal_units = _map_units_to_hal_units(units)
|
|
324
|
+
op.set_probe_units(hal_units)
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _map_hal_coupling_to_coupling(coupling: HALCoupling) -> Coupling:
|
|
329
|
+
if coupling == HALCoupling.AC:
|
|
330
|
+
return Coupling.AC
|
|
331
|
+
elif coupling == HALCoupling.DC:
|
|
332
|
+
return Coupling.DC
|
|
333
|
+
elif coupling == HALCoupling.GROUND:
|
|
334
|
+
return Coupling.Ground
|
|
335
|
+
raise ValueError(f"Invalid hal coupling: {coupling}")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _map_coupling_to_hal_coupling(coupling: Coupling) -> HALCoupling:
|
|
339
|
+
if coupling == Coupling.AC:
|
|
340
|
+
return HALCoupling.AC
|
|
341
|
+
elif coupling == Coupling.DC:
|
|
342
|
+
return HALCoupling.DC
|
|
343
|
+
elif coupling == Coupling.Ground:
|
|
344
|
+
return HALCoupling.GROUND
|
|
345
|
+
raise ValueError(f"Invalid coupling: {coupling}")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _is_coupling_equal(first: Coupling, second: Coupling) -> bool:
|
|
349
|
+
return first == second
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def get_coupling(op: HALOscilloscope) -> Coupling:
|
|
353
|
+
coupling_param = op.get_probe_coupling()
|
|
354
|
+
coupling = _map_hal_coupling_to_coupling(coupling_param)
|
|
355
|
+
return coupling
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def set_coupling(op: HALOscilloscope, coupling: Coupling) -> None:
|
|
359
|
+
coupling_param = _map_coupling_to_hal_coupling(coupling)
|
|
360
|
+
op.set_probe_coupling(coupling_param)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _map_hal_scale_to_scale(hal_scale: float | int) -> float | int:
|
|
364
|
+
scale = hal_scale
|
|
365
|
+
return scale
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _map_scale_to_scale_param(scale: float | int) -> float | int:
|
|
369
|
+
scale_param = scale
|
|
370
|
+
return scale_param
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _is_scale_equal(first: float | int, second: float | int) -> bool:
|
|
374
|
+
return math.isclose(first, second, rel_tol=1e-4, abs_tol=1e-6)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_scale(op: HALOscilloscope) -> float | int:
|
|
378
|
+
scale_param = op.get_probe_scale()
|
|
379
|
+
scale = _map_hal_scale_to_scale(scale_param)
|
|
380
|
+
return scale
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def set_scale(op: HALOscilloscope, scale: float | int) -> None:
|
|
384
|
+
scale_param = _map_scale_to_scale_param(scale)
|
|
385
|
+
op.set_probe_scale(scale_param)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _map_offset_param_to_offset(hal_offset: float | int) -> float | int:
|
|
389
|
+
offset = hal_offset
|
|
390
|
+
return offset
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _map_offset_to_hal_offset(offset: float | int) -> float | int:
|
|
394
|
+
hal_offset = offset
|
|
395
|
+
return hal_offset
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _is_offset_equal(first: float | int, second: float | int) -> bool:
|
|
399
|
+
return math.isclose(first, second, rel_tol=1e-4, abs_tol=1e-6)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def get_offset(op: HALOscilloscope) -> float | int:
|
|
403
|
+
offset_param = op.get_probe_offset()
|
|
404
|
+
offset = _map_offset_param_to_offset(offset_param)
|
|
405
|
+
return offset
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def set_offset(op: HALOscilloscope, offset: float | int) -> None:
|
|
409
|
+
offset_param = _map_offset_to_hal_offset(offset)
|
|
410
|
+
op.set_probe_offset(offset_param)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _map_hal_display_to_state(display_param: bool) -> State:
|
|
414
|
+
if display_param:
|
|
415
|
+
return State.Enabled
|
|
416
|
+
elif not display_param:
|
|
417
|
+
return State.Disabled
|
|
418
|
+
raise Exception(f"Invalid display param {display_param}")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _map_state_to_hal_display(state: State) -> bool:
|
|
422
|
+
if state == State.Disabled:
|
|
423
|
+
return False
|
|
424
|
+
elif state == State.Enabled:
|
|
425
|
+
return True
|
|
426
|
+
raise Exception(f"Invalid state {state}")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _is_state_equal(first: State, second: State) -> bool:
|
|
430
|
+
return first == second
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def get_state(op: HALOscilloscope) -> State:
|
|
434
|
+
display = op.get_probe_display()
|
|
435
|
+
state = _map_hal_display_to_state(display)
|
|
436
|
+
return state
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def set_state(op: HALOscilloscope, state: State) -> None:
|
|
440
|
+
display = _map_state_to_hal_display(state)
|
|
441
|
+
op.set_probe_display(display)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from fusaware_instruments.visa_instrument_configuration.analog_probe import (
|
|
2
|
+
AnalogProbe,
|
|
3
|
+
AnalogProbeState,
|
|
4
|
+
configure_analog_probe,
|
|
5
|
+
Coupling,
|
|
6
|
+
State,
|
|
7
|
+
Units,
|
|
8
|
+
)
|
|
9
|
+
from fusaware_instruments.visa_instrument_hal.hal_oscilloscope_rigol_mso import (
|
|
10
|
+
HALOscilloscopeProbeRigolMSOBase,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AnalogVoltageProbeRigolMSO5000(AnalogProbe):
|
|
15
|
+
def __init__(self, address: str, channel_name: str):
|
|
16
|
+
self._address = address
|
|
17
|
+
self._channel_name: str = channel_name
|
|
18
|
+
self._channel_number: int = int(channel_name[-1])
|
|
19
|
+
self._op = HALOscilloscopeProbeRigolMSOBase(
|
|
20
|
+
self._address, self._channel_number, ""
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def configure_analog_probe(
|
|
24
|
+
self,
|
|
25
|
+
attenuation: float,
|
|
26
|
+
bandwidth_limit: float | int | str,
|
|
27
|
+
coupling: Coupling,
|
|
28
|
+
offset: float | int,
|
|
29
|
+
scale: float | int,
|
|
30
|
+
state: State,
|
|
31
|
+
units: Units,
|
|
32
|
+
) -> AnalogProbeState:
|
|
33
|
+
probe_state, _ = configure_analog_probe(
|
|
34
|
+
self._op,
|
|
35
|
+
attenuation,
|
|
36
|
+
bandwidth_limit,
|
|
37
|
+
coupling,
|
|
38
|
+
offset,
|
|
39
|
+
scale,
|
|
40
|
+
state,
|
|
41
|
+
units,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return probe_state
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
from fusaware_instruments.visa_instrument_hal.hal_dmm import HALDigitalMultimeter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Function stubs for conversions
|
|
7
|
+
def is_range_equal(desired: float, actual: float) -> bool: ...
|
|
8
|
+
def is_resolution_equal(desired: float, actual: float) -> bool: ...
|
|
9
|
+
def is_digits_equal(desired: float, actual: float) -> bool: ...
|
|
10
|
+
def is_time_aperture_equal(desired: float, actual: float) -> bool: ...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def configure_dc_voltage_meter(
|
|
14
|
+
dmm: HALDigitalMultimeter,
|
|
15
|
+
range: float,
|
|
16
|
+
resolution: float,
|
|
17
|
+
digits: float,
|
|
18
|
+
time_aperture: float,
|
|
19
|
+
error: object | None,
|
|
20
|
+
) -> Tuple[float, float, float, float, object | None]:
|
|
21
|
+
desired_range = range
|
|
22
|
+
desired_resolution = resolution
|
|
23
|
+
desired_digits = digits
|
|
24
|
+
desired_time_aperture = time_aperture
|
|
25
|
+
|
|
26
|
+
dmm.connect()
|
|
27
|
+
|
|
28
|
+
initial_range = dmm.get_range()
|
|
29
|
+
initial_resolution = dmm.get_resolution()
|
|
30
|
+
initial_digits = dmm.get_digits()
|
|
31
|
+
initial_time_aperture = dmm.get_time_aperture()
|
|
32
|
+
|
|
33
|
+
if not is_range_equal(desired_range, initial_range):
|
|
34
|
+
dmm.set_range(desired_range)
|
|
35
|
+
|
|
36
|
+
if not is_resolution_equal(desired_resolution, initial_resolution):
|
|
37
|
+
dmm.set_resolution(desired_resolution)
|
|
38
|
+
|
|
39
|
+
if not is_digits_equal(desired_digits, initial_digits):
|
|
40
|
+
dmm.set_digits(desired_digits)
|
|
41
|
+
|
|
42
|
+
if not is_time_aperture_equal(desired_time_aperture, initial_time_aperture):
|
|
43
|
+
dmm.set_time_aperture(desired_time_aperture)
|
|
44
|
+
|
|
45
|
+
final_range = dmm.get_range()
|
|
46
|
+
final_resolution = dmm.get_resolution()
|
|
47
|
+
final_digits = dmm.get_digits()
|
|
48
|
+
final_time_aperture = dmm.get_time_aperture()
|
|
49
|
+
|
|
50
|
+
dmm.disconnect()
|
|
51
|
+
|
|
52
|
+
err = None
|
|
53
|
+
|
|
54
|
+
if not is_range_equal(final_range, desired_range):
|
|
55
|
+
err = {
|
|
56
|
+
"type": "ValueError",
|
|
57
|
+
"message": f"range: expected {desired_range}, got {final_range}.",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if not is_resolution_equal(final_resolution, desired_resolution):
|
|
61
|
+
err = {
|
|
62
|
+
"type": "ValueError",
|
|
63
|
+
"message": f"resolution: expected {desired_resolution}, got {final_resolution}.",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if not is_digits_equal(final_digits, desired_digits):
|
|
67
|
+
err = {
|
|
68
|
+
"type": "ValueError",
|
|
69
|
+
"message": f"digits: expected {desired_digits}, got {final_digits}.",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if not is_time_aperture_equal(final_time_aperture, desired_time_aperture):
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
return final_range, final_resolution, final_digits, final_time_aperture, err
|