PyVISA + NI-VISA + 32 bit Python を Mac で使う

(追記 2014.6.9) 最近の NI-VISA は OS X でも部分的に 64 bit 対応になっているので、通常使用の範囲であれば 32 bit の Python を無理やり使う必要はありません。
Python から USB 接続の実験装置を動かすときに、PySerial はよく使うんですが、VISA という規格もあって、PyVISA というので制御できます。GPIB と USB は備えてるけど GPIB を Mac で動かすのは面倒だし、かつその USB は PySerial で動かないしなんて装置があった場合、PyVISA を使うという手があります。

あと、LAN 接続も可能な機種だと PyVXI11 を使ったりなんてのも可能です。ただ、環境によっては IP 貰えなかったりすることがあるので、PyVISA を使うという解を持っているのは良いことです。

Mac の場合、まずは VISA の library が必要になります。Mac 用の VISA library は恐らく National Instruments (NI) が出しているやつしかなくて、NI-VISA をいうのを使うことになります。Mac 版の最新版は 5.4 です。

.dmg を落としてきて、Mac に install しましょう。10.7 と 10.8 に対応しています。10.9 は試していません。で、ちょっと問題があって、NI-VISA の Mac 版は 32 bit にしか対応していないので (10.8 のみ?)、PyVISA を呼ぶ Python は 32 bit のものを使わないといけません。でも PyROOT とかは 64 bit で動かしたいし、PyVISA のためだけに他の Python 関連のものを全て 32 bit にするのも敗北感があるので、PyVISA だけ 32 bit で動かしたい、と。

1. NI-VISA を入れる

さっきも書いたように、NI-VISAMac に入れます。/Library/Frameworks/Visa.framework/ に色々と入ります。

2. PyVISA を入れる

$ sudo easy_install pyvisa

これで PyVISA が入ります。

3. PyVISA + NI-VISA を試す

普通に MacPython を起動すると、64 bit のものが起動するはずです。32 bit のものを明示的に起動させるには、次のようにします。

$ VERSIONER_PYTHON_PREFER_32_BIT=yes python

さて、Tektronix の AFG3251 という function generator で実験してみます。

from pyvisa.vpp43 import visa_library
visa_library.load_library("/Library/Frameworks/Visa.framework/VISA")
import visa

afg3251 = visa.Instrument("USB0::0x0699::0x0344::C020398::INSTR")
afg3251.write("*IDN?")
print afg3251.read()

PyVISA が NI-VISA の library を自動で見つけてくれないため、明示的に visa_library.load_library で load する必要があります。これをやらずに import visa をすると、ちゃんと動いてくれません。

その後、VISA の接続に必要な USB の情報を与えてやると、その USB 機器に接続できます。AFG3251 の場合は、本体のメニューを操作すると "USB0::" という情報が出てきます。

後は RS232C の制御と同じような感覚でコマンドを送信するだけです。Delimiter などのややこしい設定は考える必要がありません。

ここここを参考にしました。

4. subprocess.Process

さて、これだと 32 bit の Python でしか動かないので、64 bit で動いている PyROOT と一緒に使うのは面倒です。ということで、subprocess.Process を使って別の process で動かします。Function generator のように低速な機器であれば、これで全く問題ありません。オシロのように転送速度が効いてくる装置の場合、PyVXI11 などを検討したほうが良いでしょう。


まずは、afg3251.py という script を用意します。これは単純に、第一引数を AFG3251 に送信し、返事を返すことを期待するコマンドであれば stdout にその結果を出力する動作をします。

#!/usr/bin/env python

import sys
command = sys.argv[1]

from pyvisa.vpp43 import visa_library
visa_library.load_library("/Library/Frameworks/Visa.framework/VISA")
import visa

afg3251 = visa.Instrument("USB0::0x0699::0x0344::C020398::INSTR")
afg3251.write(command)

if command[-1] == "?":
    sys.stdout.write(afg3251.read())

このファイルは chmod で実行権限を与えておいて下さい。

$ chmod +x afg3251.py

次に、process.py を用意します。これは思いっきりここの真似ですね。32 bit の Python を別 process で起動するため、os.environ に VERSIONER_PYTHON_PREFER_32_BIT を設定しています。これを設定しておけば、subprocess.Popen で別の Python を開いても 32 bit で起動してくれます。

import subprocess
import os

class AFG3251(object):
    def __init__(self):
        pass

    def excecute(self, command):
        os.environ["VERSIONER_PYTHON_PREFER_32_BIT"] = "yes"

        args = ["./afg3251.py", command]
        logfilename = {"stdout" : "./stdout.log", "stderr" : "./stderr.log" }
        logfileobj = {}
        mode = "w"

        for stream, log in logfilename.iteritems():
            try:
                logfileobj[stream] = file(log, mode)
            except IOError:
                print "Cannot open logfile: %s" % log
                sys.exit(1)

        subproc_args = { 'stdin'     : None,
                         'stdout'    : logfileobj['stdout'],
                         'stderr'    : logfileobj['stderr'],
                         'close_fds' : True,                 }

        try:
            p = subprocess.Popen(args, **subproc_args)
            del os.environ["VERSIONER_PYTHON_PREFER_32_BIT"]
        except OSError:
            print "Failed to execute command: %s" % args[0]
            sys.exit(1)

        ret = p.wait()
        stdout = open(logfilename["stdout"]).read()
        stderr = open(logfilename["stderr"]).read()

        return (stdout, stderr, ret)

afg3251.py 内部で発生した stdout の出力を process.py でそのまま受け取ることはできないので、一度 stdout.log に吐いています。AFG3251 からの応答を見たければ、このファイルを覗けば出力が分ります。

後は、好きなように改造するだけです。