Source code for QInstrument.instruments.IPGPhotonics.IPGLaser.instrument

import logging
from QInstrument.lib.QSerialInstrument import QSerialInstrument


logger = logging.getLogger(__name__)


[docs] class QIPGLaser(QSerialInstrument): '''IPG Photonics YLR Ytterbium Fiber Laser. The IPG command interface does not follow the ``CMD?`` / ``CMDvalue`` convention used by SRS instruments. Each command is a short mnemonic that either queries the instrument (e.g. ``STA``, ``ROP``) or commands it (e.g. ``ABN``, ``EMOFF``). All responses echo the command name followed by the value. Properties are therefore registered with bespoke getters and setters rather than a ``_register()`` helper. Properties ========== Control ------- current : float [%] Diode current setpoint as a percentage of maximum. Range: ``minimum_current``--``maximum_current``. Commands ``SDC``; read back via ``RCS``. maximum_current : float [%] Software upper bound on the current setpoint. Range: 0--100. Default: 100. Values above this limit are clamped and a warning is logged. aiming : bool True: aiming beam on (``ABN``). False: aiming beam off (``ABF``). emission : bool True: laser emission enabled (``EMON``). False: emission off (``EMOFF``). Status (read-only) ------------------ power : float [W] Current output power (``ROP``). power_supply : bool True: power supply is on. keyswitch : bool True: keyswitch in REM (remote) position. fault : bool True: one or more fault conditions are active. Fault conditions: over-temperature, excessive backreflection, power supply off, unexpected emission detected. minimum_current : float [%] Minimum effective diode current percentage. Read once at startup via ``RNC`` and cached for the session. firmware : str Firmware version string (``RFV``). temperature : float [deg C] Laser diode temperature (``RCT``). Reference ========= IPG Photonics Fiber Laser User Manual, dated February 26, 2010. Interface Commands +---------+--------------------------+--------------------------+ | Command | Description | Response | +=========+==========================+==========================+ | SDC v | Set Diode Current | SDC: v | | | v: percent of full range | ERR: Out of range | +---------+--------------------------+--------------------------+ | RCS | Read Current Setpoint | RCS: v | +---------+--------------------------+--------------------------+ | RNC | Read Minimum Current | RNC: v | +---------+--------------------------+--------------------------+ | RDC | Read Diode Current | RDC: v | | | v: Amps | | +---------+--------------------------+--------------------------+ | ROP | Read Output Power | ROP: v | | | v: Watts | ROP: Off | | | | ROP: Low | +---------+--------------------------+--------------------------+ | RFV | Read Firmware Version | RFV: version | +---------+--------------------------+--------------------------+ | RCT | Read Laser Temperature | RCT: v | | | v: degrees Centigrade | | +---------+--------------------------+--------------------------+ | STA | Read Device Status | STA: v | | | v: int: status bits | | +---------+--------------------------+--------------------------+ | EMON | Start Emission | EMON | | | | ERR: Keyswitch in remote | +---------+--------------------------+--------------------------+ | EMOFF | Stop Emission | EMOFF | | | | ERR: Keyswitch in remote | +---------+--------------------------+--------------------------+ | EMOD | Enable Modulation | EMOD | | | | ERR: Emission is on | +---------+--------------------------+--------------------------+ | DMOD | Disable Modulation | DMOD | | | | ERR: Emission is on | +---------+--------------------------+--------------------------+ | EEC | Enable External Control | EEC | | | analog control | ERR: Emission is on | +---------+--------------------------+--------------------------+ | DEC | Disable External Control | DEC | | | | ERR: Emission is on | +---------+--------------------------+--------------------------+ | RERR | Reset Errors | RERR | +---------+--------------------------+--------------------------+ | ABN | Aiming Beam On | ABN | +---------+--------------------------+--------------------------+ | ABF | Aiming Beam Off | ABF | +---------+--------------------------+--------------------------+ | EEABC | Enable External | EEABC | | | Aiming Beam Control | | +---------+--------------------------+--------------------------+ | DEABC | Disable EABC | DEABC | +---------+--------------------------+--------------------------+ | SFWS v | Set Filter Window Size | SFWS: v | | | v: averaging time [s] | ERR: Out of Range | | | multiple of 0.2 s | | +---------+--------------------------+--------------------------+ | RFWS | Read Filter Window Size | RFWS: v | +---------+--------------------------+--------------------------+ ''' flag = {'TMP': 0x2, # over-temperature condition 'EMX': 0x4, # laser emission active 'BKR': 0x8, # excessive backreflection 'ACL': 0x10, # analog control mode enabled 'MDC': 0x40, # module communication disconnected 'MFL': 0x80, # module(s) failed 'AIM': 0x100, # aiming beam on 'PWR': 0x800, # power supply off 'MOD': 0x1000, # modulation enabled 'ENA': 0x4000, # laser enable asserted 'EMS': 0x8000, # emission startup 'UNX': 0x20000, # unexpected emission detected 'KEY': 0x200000, # keyswitch in REM position 'ERR': 0x2 | 0x8 | 0x800 | 0x20000} # composite fault mask comm = dict(baudRate=QSerialInstrument.BaudRate.Baud57600, dataBits=QSerialInstrument.DataBits.Data8, stopBits=QSerialInstrument.StopBits.OneStop, parity=QSerialInstrument.Parity.NoParity, flowControl=QSerialInstrument.FlowControl.NoFlowControl, eol='\r') def _registerProperties(self) -> None: '''Register all instrument properties via ``registerProperty()``. Called once from ``__init__``. ``minimum_current`` defaults to ``0.`` until ``identify()`` reads the hardware value at open time. Subclasses that extend the property set should call ``super()._registerProperties()`` first. ''' self._minimum_current = getattr(self, '_minimum_current', 0.) self._maximum_current = getattr(self, '_maximum_current', 100.) self.registerProperty('current', ptype=float, getter=lambda: float(self._command('RCS')), setter=self._setCurrent, minimum=0., maximum=100., debounce=500) self.registerProperty('maximum_current', ptype=float, getter=lambda: self._maximum_current, setter=self._setMaximumCurrent, minimum=0., maximum=100.) self.registerProperty('aiming', ptype=bool, getter=lambda: self._flagSet('AIM'), setter=self._setAiming) self.registerProperty('emission', ptype=bool, getter=lambda: self._flagSet('EMX'), setter=self._setEmission) self.registerProperty('power', ptype=float, setter=None, getter=self._getPower) self.registerProperty('power_supply', ptype=bool, setter=None, getter=lambda: not self._flagSet('PWR')) self.registerProperty('keyswitch', ptype=bool, setter=None, getter=lambda: self._flagSet('KEY')) self.registerProperty('fault', ptype=bool, setter=None, getter=lambda: self._flagSet('ERR')) self.registerProperty('minimum_current', ptype=float, setter=None, getter=lambda: self._minimum_current) self.registerProperty('firmware', ptype=str, setter=None, getter=lambda: self._command('RFV')) self.registerProperty('temperature', ptype=float, setter=None, getter=lambda: float(self._command('RCT')))
[docs] def identify(self) -> bool: '''Return True if the connected device responds as an IPG laser. Queries the firmware version string (``RFV``) and checks that the response contains more than 3 characters. On success, reads and caches ``minimum_current`` via ``RNC``. ''' firmware = self._command('RFV') if len(firmware) <= 3: return False self._minimum_current = float(self._command('RNC')) return True
def _command(self, cmd: str) -> str: '''Send a command and return the value portion of the echoed response. The IPG protocol echoes the command mnemonic before the value in every response (e.g. ``'ROP: 10.5'``). If the mnemonic echo is present, returns the value token; otherwise returns the full response and logs an informational message. Commands that carry an argument (e.g. ``'SDC 50.0'``) are matched by their mnemonic only, so the argument does not interfere with the echo check. Parameters ---------- cmd : str IPG command string, optionally followed by a value argument (e.g. ``'STA'``, ``'ABN'``, ``'SDC 50.0'``). Returns ------- str Value token from the echoed response, or the full response if the mnemonic echo is absent. ''' response = self.handshake(cmd) mnemonic = cmd.split()[0] if mnemonic not in response: logger.info(f'Unexpected response to {cmd!r}: {response!r}') return response parts = response.split() return parts[1] if len(parts) >= 2 else response def _flags(self) -> int: '''Return the raw instrument status word.''' return int(self._command('STA')) def _flagSet(self, flagname: str) -> bool: '''Return True if the named status flag is set. Parameters ---------- flagname : str Key into :attr:`flag` (e.g. ``'KEY'``, ``'AIM'``). ''' return bool(self._flags() & self.flag[flagname]) def _getPower(self) -> float: '''Return current output power [W]. The instrument responds with ``'Off'`` when emission is disabled, ``'Low'`` when power is below the measurable threshold, or a numeric string otherwise. ''' value = self._command('ROP') if 'Off' in value: return 0. if 'Low' in value: return 0.1 return float(value) def _setCurrent(self, v: float) -> None: '''Set the diode current setpoint, clamped to ``maximum_current``. Parameters ---------- v : float Requested diode current [%]. ''' limit = self._maximum_current clamped = min(float(v), limit) if clamped < float(v): logger.warning( f'current {v:.1f}% exceeds maximum_current ' f'{limit:.1f}%; clamped to {clamped:.1f}%') self._command(f'SDC {clamped:.1f}') def _setMaximumCurrent(self, v: float) -> None: '''Set the software upper bound on the diode current setpoint. Values outside [0, 100] are rejected and a warning is logged. Parameters ---------- v : float New maximum diode current [%]. ''' v = float(v) if not 0. <= v <= 100.: logger.warning( f'maximum_current {v:.1f}% out of range [0, 100]; ignored') return self._maximum_current = v def _setAiming(self, state: bool) -> None: '''Enable or disable the aiming beam. Parameters ---------- state : bool True to enable (``ABN``), False to disable (``ABF``). ''' self._command('ABN' if bool(state) else 'ABF') def _setEmission(self, state: bool) -> None: '''Enable or disable laser emission. Parameters ---------- state : bool True to enable (``EMON``), False to disable (``EMOFF``). ''' self._command('EMON' if bool(state) else 'EMOFF')
[docs] def status(self) -> dict[str, bool | float]: '''Return a snapshot of all polled status properties. Reads the status word (``STA``) and output power (``ROP``) once each, avoiding redundant ``STA`` queries. Intended for use by the widget poll loop. Returns ------- dict[str, bool | float] Mapping of property name to current value for ``power_supply``, ``keyswitch``, ``aiming``, ``emission``, ``fault``, and ``power``. ''' flags = self._flags() return { 'power_supply': not bool(flags & self.flag['PWR']), 'keyswitch': bool(flags & self.flag['KEY']), 'aiming': bool(flags & self.flag['AIM']), 'emission': bool(flags & self.flag['EMX']), 'fault': bool(flags & self.flag['ERR']), 'power': self._getPower(), }
[docs] def fault_detail(self) -> list[str]: '''Return a list of active fault condition names. Returns an empty list when no faults are active. Returns ------- list[str] Human-readable names of active fault conditions. Possible values: ``'over-temperature'``, ``'excessive backreflection'``, ``'power supply off'``, ``'unexpected emission'``. ''' flags = self._flags() conditions = [ ('TMP', 'over-temperature'), ('BKR', 'excessive backreflection'), ('PWR', 'power supply off'), ('UNX', 'unexpected emission'), ] return [label for key, label in conditions if flags & self.flag[key]]
if __name__ == '__main__': QIPGLaser.example() __all__ = ['QIPGLaser']