实现SCPI命令树结构作为Python类方法的最佳方法是什么?

时间:2017-01-23 02:17:26

标签: python class interface instrumentation

SCPI命令是由助记符构成的字符串,发送到仪器以修改/检索其设置并读取测量值。我希望能够构建和发送这样的字符串:"SENSe:VOLTage:DC:RANGe 10 V"

使用这样的代码:

inst.sense.voltage.dc.range('10 V')

但它似乎是一个非常深的兔子洞,我不确定我是否要开始它。每个子系统和选项的类加载....

更好的方法是:

def sense_volt(self, currenttype='DC', RANG='10 V'):
    cmdStr = 'SENS:VOLT:' + currenttype + ':RANG: ' + RANG
    return self.inst.write(cmdStr)

仅仅实现我需要的命令并保留inst.query(arg)inst.write(arg)方法来手动构建其他命令是微不足道的,但我希望最终拥有一个完整的仪器界面,其中所有命令都由自动完成方法。

1 个答案:

答案 0 :(得分:0)

我成功了,虽然我对结果很不满意。我已经三年没有接触过这些了,所以我不知道为什么我会按照自己的方式做一些事情。我知道我在组织一切以实现便携性方面遇到了很多麻烦。我敢肯定,经过漫长的等待,现在我会收到很多关于我应该如何做的建议。

我编写了解析函数来解释 SCPI 命令。它们可以将命令与参数分开,确定必需和可选参数,识别查询等。例如:

def command_name(scpi_command):
    cmd = scpi_command.split(' ')[0]
    new = ''
    for i, c in enumerate(cmd):
        if c in '[]':
            continue

        if c.isupper() or c.isdigit():
            new += c.lower()
            continue

        if c == ':':
            new += '_'
            continue

        if c == '?':
            new += '_qry'
            break
    return new

来自 CONFigure[:VOLTage]:DC [{<range>|AUTO|MIN|MAX|DEF} [, {<resolution>|MIN|MAX|DEF}]] 的命令可以解析为 conf_volt_dcconf_dc。我在实验中没有使用删节选项。我认为我的一些不满源于超长的方法名称。

我编写了一个构建器脚本来从文件中读取命令,解析它们,然后编写一个带有扩展“命令处理程序”的“命令集”类的新脚本。每个命令都被解析并转换为几个样板方法之一,例如:

query_str_no_args = r'''
    def {name}(self):
        """SCPI instrument query.
         {s}
         """
        cmd = '{s}'
        return self._command_handler(command=cmd)
'''

解析器还包括一个命令处理程序类来处理命令和参数。每个命令都是从其示例文本中解析出来的,ala >>> _command_handler(0.1, 'MAX', command='CONFigure[:VOLTage]:DC [{<range>|AUTO|MIN|MAX|DEF} [, {<resolution>|MIN|MAX|DEF}]]'。参数(如果有)被验证,命令字符串被重建并发送到仪器,

class Cmd_Handler():
    def _command_handler(self, *args, command):
        # command string 'SENS:VOLT:RANG'
        cmd_str = command_string(command)

        # argument dictionary {0: [True, '<range>, 'AUTO', 'MIN', 'MAX', 'DEF'],
        #                      1: [False, '<resolution>', 'MIN', 'MAX', 'DEF']}
        arg_dict = command_args(command)
        if debug_mode: print(arg_dict)
        for k, v in arg_dict.items():
            for i, j in enumerate(v[1:]):
   ...

        # Validate arguments
        # count mandatory arguments
        if len(args) < len([arg_dict[k] for k in arg_dict.keys() if arg_dict[k][0]]):

   ...

        if '?' in cmd_str:
            return self._query(cmd_str + arg_str)
        else:
            return self._write(cmd_str + arg_str)

构建器完成后,您最终会得到一个大脚本(Ag34401 和 Ag34461 文件分别为 2600 和 3600 行),如下所示:

#!/usr/bin/env python3

from scpi.scpi_parse import Cmd_Handler


class Ag34461A_CS(Cmd_Handler):

...

    def calc_scal_stat(self, *args):
        """SCPI instrument command.
         CALCulate:SCALe[:STATe] {OFF|ON}
         """
        cmd = 'CALCulate:SCALe[:STATe] {OFF|ON}'
        return self._command_handler(*args, command=cmd)

    def calc_scal_stat_qry(self):
        """SCPI instrument query.
         CALCulate:SCALe[:STATe]?
         """
        cmd = 'CALCulate:SCALe[:STATe]?'
        return self._command_handler(command=cmd)

然后你用你简短的、甜美的乐器类来扩展这个类:

#!/usr/bin/env python3

import pyvisa as visa
from scpi.cmd_sets.ag34401a_cs import Ag34401A_CS   # Extend the command set


class Ag34401A(Ag34401A_CS):
    def __init__(self, resource_name=None):
        rm = visa.ResourceManager()
        self._inst = None
        if resource_name:
            inst = rm.open_resource(resource_name)
        else:
            for resource in rm.list_resources():
                inst = rm.open_resource(resource)
                if '34401' in inst.query("*IDN?"):
                    self._inst = inst
                    break
        if not self._inst:
            raise visa.errors.VisaIOError

        assert isinstance(self._inst, visa.resources.GPIBInstrument)

        self._query = self._inst.query
        self._write = self._inst.write

我还有一个 SCPI 仪器父类,我试图用所有星号命令(*IDN?、*CLS 等)填充它。我对之前的结果不满意并且被一些星命令的安装复杂性吓倒了(其中一些看起来真的是特定于仪器的),并放弃了它。这是第一点

class SCPI_Instrument(object, metaclass=ABCMeta):

    @abstractmethod
    def query(self, message, delay):
        pass

    @abstractmethod
    def write(self, message, delay):
        pass

    # *CLS - Clear Status
    def cls(self, termination = None, encoding = None):
        """Clears the instrument status byte by emptying the error queue and clearing all event registers.
    Also cancels any preceding *OPC command or query."""

        return self.write("*CLS", termination, encoding)

最后,我能够挤进去的文档并没有我想要的那么有用。自动完成功能无法按名称提示输入参数,并且建议 每个命令 的帮助比我预期的要小。试图保持导入路径直对我来说已经够困难了,更不用说最终可能会使用我的代码的人了。

最终,编码从来都不是我工作描述的一部分,我被调到了一个更不可能尝试仪器控制的位置。