1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2024 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import pytest
import serial
from pymeasure.adapters import SerialAdapter, VISAAdapter
from pymeasure.instruments import Instrument
# As an instrument contributor, I want to define default connection settings for my
# instrument with a minimum of boiler-plate. These settings should be easily
# user-overridable if appropriate without having to change the code/implementation
# of my instrument.
class MultiprotocolInstrument(Instrument):
"""Test instrument for testing the configuration and use of multiple connection methods.
The instrument support multiple transports methods(serial, TCP/IP, GPIB).
In case of serial connections, the default baud rate is low (2400), but configurable in
the device, so it can use faster communication if desired.
Two examples in our instruments are AMI430, Keithley2260B (for TCP/IP, there are more
with GPIB capability)
It also has an attribute to configure a non-serial (gpib) connection. This demonstrates
that there may be attributes that are not valid for a serial connection, only others.
"""
def __init__(self, adapter, name="Instrument with multiple connection methods",
baud_rate=2400, **kwargs):
# - baud_rate is placed in the signature if it's expected to be modified by the user
# - baud_rate is only valid for the asrl protocol
# - enable_repeat_addressing is only for gpib and always False for this device
# - timeout is the same for all pyvisa protocols, using setdefault it is user-modifiable
# without cluttering the signature:
kwargs.setdefault('timeout', 1500)
super().__init__(adapter,
name=name,
gpib=dict(enable_repeat_addressing=False,
read_termination='\r'),
asrl={'baud_rate': baud_rate,
'read_termination': '\r\n'},
**kwargs, # all others/generally valid kwargs
)
# The defined tests use the pyvisa-sim simulated connections to avoid the need for a connected
# instrument. The argument handling and connection creation happens in pyvisa just like for a
# real instrument, through.
def test_serial_default_settings():
"""As a user, I want to simply connect to an instrument using default settings"""
instr = MultiprotocolInstrument(adapter='ASRL1::INSTR', visa_library='@sim')
assert instr.adapter.connection.baud_rate == 2400
def test_serial_custom_baud_rate_is_set():
"""As a user, I want to easily override default settings to fit my needs"""
instr = MultiprotocolInstrument(adapter='ASRL1::INSTR', baud_rate=115200, visa_library='@sim')
assert instr.adapter.connection.baud_rate == 115200
def test_connections_use_tcpip():
"""As a user, I want to be free to choose which connection to use (e.g. serial-over RS-232,
USB, TCP/IP) should an instrument support more than one
"""
# here, baud_rate is invalid for pyvisa, but is a default kwarg anyway
instr = MultiprotocolInstrument(adapter='TCPIP::localhost:1111::INSTR', visa_library='@sim')
# method specific to pyvisa TCPIPInstrument
assert hasattr(instr.adapter.connection, 'control_ren')
def test_connections_use_gpib():
"""As a user, I want to be free to choose which connection to use (e.g. serial-over RS-232,
USB, TCP/IP) should an instrument support more than one
"""
# here, baud_rate is invalid for pyvisa, but is a default kwarg anyway
instr = MultiprotocolInstrument(adapter='GPIB::8::INSTR', visa_library='@sim')
# attribute specific to pyvisa GPIBInstrument
assert instr.adapter.connection.enable_repeat_addressing is False
def test_use_separate_SerialAdapter():
"""As a user, I want to be able to supply a self-generated Adapter instance
(e.g. to enable serial connection sharing over RS-485/-422)
"""
# note: baudrate, not baud_rate, as this goes to pyserial
ser = SerialAdapter(serial.serial_for_url("loop://", baudrate=4800))
instr = MultiprotocolInstrument(ser)
# don't need visa_library here as this does not go to pyvisa, kwargs are ignored if provided
assert instr.adapter.connection.baudrate == 4800
def test_separate_adapter_discards_additional_args():
"""When passing in a separate Adapter instance, all connection-specific args and settings
are ignored.
"""
ser = SerialAdapter(serial.serial_for_url("loop://"))
instr = MultiprotocolInstrument(ser, baud_rate=1234, wrong_kwarg='fizzbuzz')
assert instr.adapter.connection.baudrate == 9600 # default of serial
def test_use_separate_VISAAdapter():
"""As a user, I want to be able to supply my own VISAAdpter with my settings
"""
# User can use their own VISAAdapter
ser = VISAAdapter('ASRL1::INSTR', baud_rate=1200, visa_library='@sim')
instr = MultiprotocolInstrument(ser)
# don't need visa_library here as this does not go to pyvisa, kwargs are ignored if provided
assert instr.adapter.connection.baud_rate == 1200
def test_incorrect_arg_is_flagged():
"""As a user or instrument contributor that used an incorrect/invalid (kw)arg,
I want to be alerted to that fact.
"""
with pytest.raises(ValueError, match='bitrate'):
_ = MultiprotocolInstrument(adapter='ASRL1::INSTR', bitrate=1234, visa_library='@sim')
def test_incorrect_interface_type_is_flagged():
"""As a user or instrument contributor that used an incorrect/invalid interface type,
I want to be alerted to that fact.
"""
class WrongInterfaceInstrument(Instrument):
def __init__(self, adapter, name="Instrument with incorrect interface name", **kwargs):
super().__init__(adapter,
name=name,
arsl={'read_termination': '\r\n'}, # typo here
**kwargs,
)
with pytest.raises(ValueError, match='arsl'):
_ = WrongInterfaceInstrument(adapter='ASRL1::INSTR', visa_library='@sim')
def test_improper_arg_is_flagged():
"""As a user or instrument contributor that used a kwarg that is inappropriate for the present
connection, I want to be alerted to that fact.
"""
with pytest.raises(ValueError, match='enable_repeat_addressing'):
_ = MultiprotocolInstrument(adapter='ASRL1::INSTR', enable_repeat_addressing=True,
visa_library='@sim')
def test_common_kwargs_are_retained():
instr1 = MultiprotocolInstrument(adapter='ASRL1::INSTR', visa_library='@sim')
assert instr1.adapter.connection.timeout == 1500
instr2 = MultiprotocolInstrument(adapter='GPIB::8::INSTR', visa_library='@sim')
assert instr2.adapter.connection.timeout == 1500
def test_distinct_interface_specific_defaults():
"""As an instrument implementor, I can easily prescribe default settings
that are different between interfaces.
"""
instr1 = MultiprotocolInstrument(adapter='ASRL1::INSTR', visa_library='@sim')
assert instr1.adapter.connection.read_termination == '\r\n'
instr2 = MultiprotocolInstrument(adapter='GPIB::8::INSTR', visa_library='@sim')
assert instr2.adapter.connection.read_termination == '\r'
def test_modified_non_signature_kwargs_are_retained():
instr1 = MultiprotocolInstrument(adapter='ASRL1::INSTR', timeout=3000, visa_library='@sim')
assert instr1.adapter.connection.timeout == 3000
instr2 = MultiprotocolInstrument(adapter='GPIB::8::INSTR', timeout=3000, visa_library='@sim')
assert instr2.adapter.connection.timeout == 3000
def test_override_interface_specific_defaults():
"""As as user, I want to be able to override interface-specific hardcoded defaults.
"""
instr1 = MultiprotocolInstrument(adapter='ASRL1::INSTR', read_termination='\r',
visa_library='@sim')
assert instr1.adapter.connection.read_termination == '\r'
instr2 = MultiprotocolInstrument(adapter='GPIB::8::INSTR', enable_repeat_addressing=True,
visa_library='@sim')
assert instr2.adapter.connection.enable_repeat_addressing is True
|