from qcodes.instrument_drivers.Keithley import Keithley2636B
from qcodes.instrument_drivers.Keithley._Keithley_2600 import Keithley2600Channel
from semi_cr.core.lab.station.routing_v1.models import ConnectionRoutable, Routable
from collections.abc import Sequence
from typing import Any, Literal
import networkx as nx
from semi_cr.core.lab.station.graphing.models import Node, Edge
from semi_cr.core.lab.station.graphing.operations import build_nx_graph
from semi_cr.core.lab.station.graphing.ids import (
qcodes_node_id,
terminal_node_id,
)
from qcodes.instrument import InstrumentModule
SMUTerminalKind = Literal[
"force_hi",
"force_lo",
"sense_hi",
"sense_lo",
]
[docs]
class RoutableSMUChannel(Keithley2600Channel, Routable):
@property
def capabilities(self) -> frozenset[str]:
return frozenset({
"voltage_source",
"voltage_meter",
"current_source",
"current_meter",
})
@property
def graph(self) -> nx.MultiDiGraph:
channel_id = qcodes_node_id(self)
attrs = {
"capabilities": {
"voltage_source",
"voltage_meter",
"current_source",
"current_meter",
},
"quantities": {
"voltage",
"current",
},
"modality": "dc",
}
return build_nx_graph(
nodes=[
Node(
id=channel_id,
kind="instrument_channel",
name=self.short_name,
obj=self,
attrs=attrs,
),
],
edges=[],
)
[docs]
class SMUTerminal(InstrumentModule, Routable):
def __init__(
self,
parent: "RoutableSMUChannel",
name: str,
terminal_type: SMUTerminalKind,
):
super().__init__(
parent=parent,
name=name,
)
self._terminal_type = terminal_type
self.add_parameter(
"voltage",
unit="V",
label=f"SMU voltage",
get_cmd=self.get_voltage,
set_cmd=self.set_voltage,
get_parser=float,
set_parser=float,
)
self.add_parameter(
"current",
unit="V",
label=f"SMU current",
get_cmd=self.get_current,
set_cmd=False,
get_parser=float,
set_parser=float,
)
[docs]
def set_voltage(self, voltage: float) -> None:
channel = self.parent
channel.volt(voltage)
[docs]
def get_voltage(self) -> float:
channel = self.parent
return float(channel.volt())
[docs]
def get_current(self) -> float:
channel = self.parent
return float(channel.curr())
@property
def graph(self) -> nx.MultiDiGraph:
channel_id = qcodes_node_id(self.parent)
terminal_id = terminal_node_id(
self.root_instrument.config_name,
f"{self.parent.short_name}_{self.short_name}",
)
polarity = (
"hi"
if self._terminal_type.endswith("_hi")
else "lo"
)
if self._terminal_type.startswith("force"):
attrs = {
"terminal_type": "force",
"polarity": polarity,
"capabilities": {
"voltage_source",
"current_source",
"voltage_meter",
"current_meter",
},
"quantities": {
"voltage",
"current",
},
"modality": "dc",
}
elif self._terminal_type.startswith("sense"):
attrs = {
"terminal_type": "sense",
"polarity": polarity,
"capabilities": {
"voltage_meter",
# "current_meter",
},
"quantities": {
"voltage",
# "current",
},
"modality": "dc",
}
else:
raise ValueError(
f"Unknown SMU terminal type: "
f"{self._terminal_type!r}"
)
return build_nx_graph(
nodes=[
Node(
id=terminal_id,
kind="terminal",
name=self.short_name,
obj=self,
attrs=attrs,
),
],
edges=[
Edge(
source=channel_id,
target=terminal_id,
kind="represents",
),
],
)
[docs]
class QcodesSMU(
Keithley2636B,
ConnectionRoutable,
):
def __init__(
self,
name: str,
connections: Sequence[dict[str, Any]] | None = None,
**kwargs: Any,
):
super().__init__(
name=name,
**kwargs,
)
self._init_connections(connections)
for connection in self.connections:
channel_name = connection["channel"]
terminal_type = connection["terminal"]
channel = getattr(
self,
channel_name,
)
channel.add_submodule(
terminal_type,
SMUTerminal(
parent=channel,
name=terminal_type,
terminal_type=terminal_type,
),
)