Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/examples/kiprim/ex_kiprim_dc310s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python

import instruments as ik

psu = ik.kiprim.DC310S.open_serial("COM8", baud=115200, timeout=0.5)

print(psu.name)
print(f"Setpoint: {psu.voltage}, {psu.current}, output={psu.output}")
print(f"Measured: {psu.voltage_sense}, {psu.current_sense}, {psu.power_sense}")

# Uncomment to configure the supply explicitly.
# psu.voltage = 5 * ik.units.volt
# psu.current = 0.25 * ik.units.ampere
# psu.output = True
1 change: 1 addition & 0 deletions doc/source/apiref/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Contents:
holzworth
hp
keithley
kiprim
lakeshore
minghe
mettler_toledo
Expand Down
12 changes: 12 additions & 0 deletions doc/source/apiref/kiprim.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. currentmodule:: instruments.kiprim

======
Kiprim
======

:class:`DC310S` Power Supply
============================

.. autoclass:: DC310S
:members:
:undoc-members:
1 change: 1 addition & 0 deletions src/instruments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from . import holzworth
from . import hp
from . import keithley
from . import kiprim
from . import lakeshore
from . import mettler_toledo
from . import minghe
Expand Down
6 changes: 6 additions & 0 deletions src/instruments/kiprim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env python
"""
Module containing Kiprim instruments.
"""

from .dc310s import DC310S
180 changes: 180 additions & 0 deletions src/instruments/kiprim/dc310s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env python
"""
Driver for the Kiprim DC310S single-output power supply.
"""

# IMPORTS #####################################################################

from instruments.abstract_instruments import PowerSupply
from instruments.units import ureg as u
from instruments.util_fns import bounded_unitful_property, unitful_property

# FUNCTIONS ###################################################################


def _parse_output_state(reply):
"""
Normalize the DC310S output-state reply into a boolean value.

The DC310S has been observed to report either ``ON``/``OFF`` or ``1``/``0``
depending on firmware and transport state.
"""

reply = reply.strip().upper()
if reply in {"1", "ON"}:
return True
if reply in {"0", "OFF"}:
return False
raise ValueError(f"Unexpected output-state reply: {reply}")


# CLASSES #####################################################################


class DC310S(PowerSupply, PowerSupply.Channel):
"""
The Kiprim DC310S is a single-output programmable DC power supply.

Because the supply has one programmable output, this object inherits from
both `~instruments.abstract_instruments.power_supply.PowerSupply` and
`~instruments.abstract_instruments.power_supply.PowerSupply.Channel`.

Example usage:

>>> import instruments as ik
>>> psu = ik.kiprim.DC310S.open_serial("COM8", baud=115200, timeout=0.5)
>>> psu.voltage = 5 * ik.units.volt
>>> psu.current = 0.25 * ik.units.ampere
>>> psu.output = True
>>> psu.voltage_sense
<Quantity(5.0, 'volt')>
"""

voltage, voltage_min, voltage_max = bounded_unitful_property(
"VOLT",
u.volt,
format_code="{:.3f}",
input_decoration=str.strip,
valid_range=(0 * u.volt, 30 * u.volt),
doc="""
Gets/sets the programmed output voltage.

The DC310S product documentation specifies a programmable output range
of 0 V to 30 V.

:units: As specified, or assumed to be :math:`\\text{V}` otherwise.
:type: `float` or `~pint.Quantity`
""",
)

current, current_min, current_max = bounded_unitful_property(
"CURR",
u.amp,
format_code="{:.3f}",
input_decoration=str.strip,
valid_range=(0 * u.amp, 10 * u.amp),
doc="""
Gets/sets the programmed output current limit.

The DC310S product documentation specifies a programmable output range
of 0 A to 10 A.

:units: As specified, or assumed to be :math:`\\text{A}` otherwise.
:type: `float` or `~pint.Quantity`
""",
)

voltage_sense = unitful_property(
"MEAS:VOLT",
u.volt,
readonly=True,
input_decoration=str.strip,
doc="""
Gets the measured output voltage.

:units: :math:`\\text{V}`
:rtype: `~pint.Quantity`
""",
)

current_sense = unitful_property(
"MEAS:CURR",
u.amp,
readonly=True,
input_decoration=str.strip,
doc="""
Gets the measured output current.

:units: :math:`\\text{A}`
:rtype: `~pint.Quantity`
""",
)

power_sense = unitful_property(
"MEAS:POW",
u.watt,
readonly=True,
input_decoration=str.strip,
doc="""
Gets the measured output power.

:units: :math:`\\text{W}`
:rtype: `~pint.Quantity`
""",
)

@property
def output(self):
"""
Gets/sets the output state.

:type: `bool`
"""

return _parse_output_state(self.query("OUTP?"))

@output.setter
def output(self, newval):
if not isinstance(newval, bool):
raise TypeError("Output state must be specified with a boolean value.")
self.sendcmd(f"OUTP {'ON' if newval else 'OFF'}")

@property
def name(self):
"""
Gets the instrument name as reported by ``*IDN?``.

:rtype: `str`
"""

idn_string = self.query("*IDN?")
idn_parts = [part.strip() for part in idn_string.split(",")]
if len(idn_parts) >= 2:
return " ".join(idn_parts[:2])
return idn_string.strip()

@property
def mode(self):
"""
Unimplemented.
"""

raise NotImplementedError("The DC310S does not expose a stable mode query.")

@mode.setter
def mode(self, newval):
"""
Unimplemented.
"""

raise NotImplementedError("The DC310S does not expose a stable mode query.")

@property
def channel(self):
"""
Return the single output channel.

:rtype: `tuple`
"""

return (self,)
Empty file added tests/test_kiprim/__init__.py
Empty file.
127 changes: 127 additions & 0 deletions tests/test_kiprim/test_dc310s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python
"""
Unit tests for the Kiprim DC310S single-output power supply.
"""

# IMPORTS #####################################################################

import pytest

import instruments as ik
from instruments.units import ureg as u
from tests import expected_protocol, unit_eq

# TESTS #######################################################################


def test_channel():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
assert psu.channel[0] == psu
assert len(psu.channel) == 1


def test_name():
with expected_protocol(
ik.kiprim.DC310S, ["*IDN?"], ["KIPRIM,DC310S,22371243,FV:V3.7.0"], sep="\n"
) as psu:
assert psu.name == "KIPRIM DC310S"


def test_name_single_field_reply():
with expected_protocol(ik.kiprim.DC310S, ["*IDN?"], ["DC310S"], sep="\n") as psu:
assert psu.name == "DC310S"


def test_voltage():
with expected_protocol(
ik.kiprim.DC310S, ["VOLT 5.000", "VOLT?"], ["5.000"], sep="\n"
) as psu:
psu.voltage = 5 * u.volt
unit_eq(psu.voltage, 5 * u.volt)


def test_voltage_bounds():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
unit_eq(psu.voltage_min, 0 * u.volt)
unit_eq(psu.voltage_max, 30 * u.volt)
with pytest.raises(ValueError):
psu.voltage = 30.001 * u.volt
with pytest.raises(ValueError):
psu.voltage = -0.001 * u.volt


def test_current():
with expected_protocol(
ik.kiprim.DC310S, ["CURR 0.250", "CURR?"], ["0.250"], sep="\n"
) as psu:
psu.current = 0.25 * u.amp
unit_eq(psu.current, 0.25 * u.amp)


def test_current_bounds():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
unit_eq(psu.current_min, 0 * u.amp)
unit_eq(psu.current_max, 10 * u.amp)
with pytest.raises(ValueError):
psu.current = 10.001 * u.amp
with pytest.raises(ValueError):
psu.current = -0.001 * u.amp


def test_voltage_sense():
with expected_protocol(
ik.kiprim.DC310S, ["MEAS:VOLT?"], ["12.340"], sep="\n"
) as psu:
unit_eq(psu.voltage_sense, 12.34 * u.volt)


def test_current_sense():
with expected_protocol(
ik.kiprim.DC310S, ["MEAS:CURR?"], ["0.456"], sep="\n"
) as psu:
unit_eq(psu.current_sense, 0.456 * u.amp)


def test_power_sense():
with expected_protocol(ik.kiprim.DC310S, ["MEAS:POW?"], ["5.624"], sep="\n") as psu:
unit_eq(psu.power_sense, 5.624 * u.watt)


def test_output_on():
with expected_protocol(
ik.kiprim.DC310S, ["OUTP ON", "OUTP?"], ["ON"], sep="\n"
) as psu:
psu.output = True
assert psu.output


def test_output_off_numeric_reply():
with expected_protocol(
ik.kiprim.DC310S, ["OUTP OFF", "OUTP?"], ["0"], sep="\n"
) as psu:
psu.output = False
assert psu.output is False


def test_output_invalid_reply():
with expected_protocol(ik.kiprim.DC310S, ["OUTP?"], ["MAYBE"], sep="\n") as psu:
with pytest.raises(ValueError):
_ = psu.output


def test_output_setter_requires_bool():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
with pytest.raises(TypeError):
psu.output = 1


def test_mode_getter_unimplemented():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
with pytest.raises(NotImplementedError):
_ = psu.mode


def test_mode_setter_unimplemented():
with expected_protocol(ik.kiprim.DC310S, [], [], sep="\n") as psu:
with pytest.raises(NotImplementedError):
psu.mode = "cv"