【问题标题】:What's the best way to implement an SCPI command tree structure as Python class methods?将 SCPI 命令树结构实现为 Python 类方法的最佳方法是什么?
【发布时间】:2017-01-23 02:17:25
【问题描述】:

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) 方法来手动构建其他命令是微不足道的,但我希望最终拥有一个完整的仪器界面,其中所有命令都由可自动完成的方法覆盖。

【问题讨论】:

  • 聪明的想法,但除非你是一个编译器的书呆子并且要机器生成这个,否则我会接受你的分析,那就是:兔子洞。 :-)
  • 我想我正在寻找在众多类兔子洞和决策逻辑兔子洞之间的平衡,只是一个 sense() 方法的决策逻辑兔子洞,其中包含多个必须解析、健全性检查等的参数. 为每个子系统制作大量方法,例如sense_volt(...)sense_curr(...)sense_freq(...) 等,这可能是可行的方法,但如果有人有更聪明的想法......
  • 您是否有所有可能命令的完整列表,例如作为文本文件?那么自动生成这两种方法中的任何一种都应该很容易。
  • 我可以很容易地将它们从仪器手册中复制出来(从 PDF 中剪切粘贴),我很确定我可以处理所有这些。不过,我不确定处理命令时的实际代码生成情况。
  • @Jim,旧线程但是,你能做你想做的事吗?我很想知道你是否让它工作以及如何工作!

标签: python class interface instrumentation


【解决方案1】:

我做到了,尽管我对结果很不满意。自从我接触到这些已经三年了,所以我不知道为什么我会以这种方式做一些事情。我知道我在组织一切以实现便携性方面遇到了很多麻烦。我敢肯定,在漫长的等待之后,我会收到大量关于我应该如何做的建议。

我编写了解析函数来解释 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)

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多