Source code for console.spcm_control.abstract_device
"""Device interface class."""
import json
from abc import ABC, abstractmethod
from ctypes import _SimpleCData, byref, c_char_p, c_int32, create_string_buffer
from logging import Logger
try:
import spcm_core as spcm
except Exception as exc: # noqa: BLE001, spcm_core raises a bare Exception if the driver is missing or outdated
_driver_error: Exception | None = exc
else:
_driver_error = None
[docs]
class SpectrumDriverError(ConnectionError):
"""Spectrum driver library is not installed or not supported."""
[docs]
def type_to_name(card_type: int) -> str:
"""Name translation for card type.
Parameters
----------
card_type
Card type code, read from register SPC_PCITYP
Returns
-------
Card name as string
"""
version = card_type & spcm.TYP_VERSIONMASK
match card_type & spcm.TYP_SERIESMASK:
case spcm.TYP_M2ISERIES:
return f"M2i.{version:04x}"
case spcm.TYP_M2IEXPSERIES:
return f"M2i.{version:04x}-Exp"
case spcm.TYP_M3ISERIES:
return f"M3i.{version:04x}"
case spcm.TYP_M3IEXPSERIES:
return f"M3i.{version:04x}-Exp"
case spcm.TYP_M4IEXPSERIES:
return f"M4i.{version:04x}-x8"
case spcm.TYP_M4XEXPSERIES:
return f"M4x.{version:04x}-x4"
case spcm.TYP_M2PEXPSERIES:
return f"M2p.{version:04x}-x4"
case spcm.TYP_M5IEXPSERIES:
return f"M5i.{version:04x}-x16"
case _:
return "unknown type"
[docs]
class SpectrumDevice(ABC):
"""Spectrum device abstract base class."""
card: c_char_p | None
card_type: c_int32
name: str | None
path: str
log: Logger
def __init__(self, path: str, log: Logger) -> None:
"""Init function of spectrum device.
Parameters
----------
path
Path of the spectrum card device, e.g. /dev/spcm1
log
Logger instance of the spectrum device.
"""
super().__init__()
self.card = None
self.card_type = c_int32(0)
self.name = None
self.path = path
self.log = log
[docs]
def dict(self) -> dict:
"""Abstract method which returns variables for logging in dictionary."""
attributes = {}
for key, var in vars(self).items():
# Check if var exists, is not None and its variable name
# does not have a leading or ending double underscore
if not key.startswith("__") and not key.endswith("__"):
if not var or var is None:
continue
if isinstance(var, _SimpleCData):
# Is a ctypes type
attributes[key] = var.value
try:
# Check if variable can be json serialized
json.dumps(var)
except TypeError:
continue
attributes[key] = var
return attributes
[docs]
def disconnect(self) -> None:
"""Disconnect card."""
# Closing the card
if self.card:
self.log.info(f"Stopping and closing card {self.name}...")
spcm.spcm_dwSetParam_i32(self.card, spcm.SPC_M2CMD, spcm.M2CMD_CARD_STOP)
spcm.spcm_dwSetParam_i32(self.card, spcm.SPC_M2CMD, spcm.M2CMD_CARD_RESET)
spcm.spcm_vClose(self.card)
# Reset card information
self.card = None
self.name = None
[docs]
def connect(self) -> bool:
"""Establish card connection.
Raises
------
ConnectionError
Connection to card already exists
ConnectionError
Connection could not be established
"""
if _driver_error is not None:
raise SpectrumDriverError(str(_driver_error)) from _driver_error
self.log.debug("Connecting to card")
if self.card:
# Raise connection error if card object already exists
self.log.error("Already connected to card")
# Only connect, if card is not already defined
self.card = spcm.spcm_hOpen(create_string_buffer(str.encode(self.path)))
if self.card:
# Read card information
spcm.spcm_dwGetParam_i32(self.card, spcm.SPC_PCITYP, byref(self.card_type))
self.name = type_to_name(self.card_type.value)
self.log.debug(f"Connection to card {self.name} established!")
self.setup_card()
else:
self.log.critical("Could not connect to card")
raise ConnectionError("Could not connect to card")
return True
[docs]
def handle_error(self, error: int) -> None:
"""General error handling function."""
if error != spcm.ERR_OK:
# spcm.ERR_OK = 0, this corresponds to "if error:"
if error == spcm.ERR_TIMEOUT:
# Check for timeout, could be logged but occurs in normal operation
# self.log.debug("Received timeout")
return
# Read error message, register and value from driver
reg, val = spcm.uint32(0), spcm.int32(0)
err_msg = create_string_buffer(spcm.ERRORTEXTLEN)
spcm.spcm_dwGetErrorInfo_i32(self.card, byref(reg), byref(val), err_msg)
msg = f"{err_msg.value.decode(errors='replace')} (error {error}, register {reg.value}, value {val.value})"
self.log.critical(f"{msg}; Stopping card {self.name}")
spcm.spcm_dwSetParam_i32(self.card, spcm.SPC_M2CMD, spcm.M2CMD_CARD_STOP)
raise RuntimeError(msg)
[docs]
def get_status(self) -> int:
"""Get current card status and log the names of all set status flags.
Returns
-------
Status register value, see M2STAT_* constants of spcm_core.
"""
try:
status = spcm.int32(0)
spcm.spcm_dwGetParam_i32(self.card, spcm.SPC_M2STATUS, byref(status))
if self.log:
flags = [
name for name, flag in vars(spcm).items()
if name.startswith(("M2STAT_CARD", "M2STAT_DATA")) and status.value & flag
]
self.log.debug("Card status: %s", flags)
except Exception:
self.log.exception("Error getting card status.")
return status.value
[docs]
@abstractmethod
def setup_card(self) -> None:
"""Abstract method to setup the card."""
[docs]
@abstractmethod
def start_operation(self) -> None:
"""Abstract method to start card operation.
Parameters
----------
data, optional
Replay data in correct spcm format as numpy array, by default None
"""
[docs]
@abstractmethod
def stop_operation(self) -> None:
"""Abstract method to stop card operation."""