VISA計測器制御で周波数vs振幅特性の自動計測をするのに、オブジェクト指向的には継承の使い時だという例①

# 概要
ファンクションジェネレータの周波数設定を変化させ、それに伴うオシロスコープの観測波形の画面キャプチャとPeak to Peakの計測値をロギングする作業をPythonで自動化したときの例を紹介します

## 話の要点
 実用的な自動計測を組むにはVISAのオブジェクト(計測器)は複数登場してきます。
 計測器は各々で個別の機能をもっていますが、共通する部分も多々あるので、規定クラスを作って継承するには良いテーマと思い紹介します

 ## お断り事項
 * とりあえず出来る計測器制御の最小構成を示している訳ではないので、コードがやや長いです
 * VISAのコマンドについてのお作法の詳細までは説明しません
 * オブジェクト指向と継承は分かっている前提とします
 * 計測器によって微妙にコマンドが異なる場合もありますので,他の計測器では適宜変更が必要
 * 話の要点を関連が薄い部分のコードは省略していますので、脳死でコピペしても動きません
  
# 環境構築
## VISAドライバインストール
VISAのドライバを入手してインストールしてください。テクトロニクス様、NI様、HIOKI様などから提供されています。

## Python と pyvisa のインストール
Python をインストールして pipで pyvisaを入れてください。

# 使用した計測器
今回使用した計測器です。　
* Tektronix （オシロスコープ）TBS1202C
* NF回路 （ファンクションジェネレータ）WF1968

# コード

## 基底クラス
基底クラスの`list_up()`という静的メソッドではVISAのドライバで認識しているアイテムをリストアップします。インスタンスするよりも前にlist_up()を使いたいと思いますので静的メソッドにしています。

コンストラクタでリソース名を利用して計測器オープンし、オープンしっぱなしという思想としています。

```py:VisaBase.py
#//////////////////////////////////////////////////////////////////////
# VISA計測器の基底クラス。　pyvisaのインストールが必要でなおかつ
# Visaのドライバーがインストールされている必要があります。
#//////////////////////////////////////////////////////////////////////
import pyvisa
import sys
import time
class VisaBase:
    #------------------------------------------------------
    #キーワードと接続方式が一致するVISAリソースをリストで返す
    @staticmethod
    def serch(key_word,connection_type):
        __resource_name= []
        _rm = pyvisa.ResourceManager()
        _visa_list = _rm.list_resources() #VISAリソースのリストを取得
        for _item in _visa_list:
            #接続方式が一致するものだけOPENしてIDを尋ねる
            if connection_type in _item:
                _inst =_rm.open_resource(_item)
                _response = _inst.query('*IDN?')
                #IDNで尋ねた結果キーワードと部分一致したらリソース名として覚える
                if key_word in _response:
                    __resource_name.append(_item)
                _inst.close()
        return __resource_name
    #------------------------------------------------------
    #VISAリソースをリストで返す
    @staticmethod
    def list_up():
        #_resource_list= []
        _rm = pyvisa.ResourceManager()
        _visa_list = _rm.list_resources()
        for _item in _visa_list:
            print(f"Available resource: {_item}")
    #------------------------------------------------------
    #コンストラクタ：
    def __init__(self,resource_name):
        self.rm = pyvisa.ResourceManager()
        self.inst = self.rm.open_resource(resource_name)
    #------------------------------------------------------
    #破棄する時にクローズ
    def __del__(self):
        '''--コード省略--'''
    #------------------------------------------------------
    @property
    def name(self):
        '''--コード省略--'''
    #------------------------------------------------------
    #コマンド書き込み：リストやタプルなら複数コマンドとして扱う
    def write(self, command):
        if isinstance(command, (list, tuple)):
            for cmd in command:
                self.inst.write(cmd)
        else:
            self.inst.write(command)
    #-----------------------------------------------
    #待ち時間を表示するプログレスバー
    def wait(self,title,seconds, bar_length=20):
        '''--コード省略--'''
################################################################
if __name__ == "__main__":
    VisaBase.list_up()

```

## ファンクションジェネレータの派生クラス

ファンクションジェネレータを呼び出す上位のアプリケーションコードで、
`wf.Frequency(100)`ではなく、`wf.Frequency=100` みたいに書きたかったので、@propertyを使っています。


```py:WF1968
#//////////////////////////////////////////////////////////////////////
# NF回路ファンクションジェネレータ（WF1968シリーズ）用
#//////////////////////////////////////////////////////////////////////
from VisaBase import VisaBase  # Visa計測器の基底クラス
   
class WF1968(VisaBase):
    def __init__(self,connection_type="USB"):
        #機器名をキーワードにしてリソース名を検索
        resource_list = VisaBase.serch("WF1968", connection_type)
        if not resource_list:
            '''--コード省略--'''
        super().__init__(resource_list[0])
    #------------------------------------------------------
    # 出力ON/OF状態を設定するプロパティ
    @property
    def Output(self):
        return self.inst.query("OUTPut?").strip()
    @Output.setter
    def Output(self, value):
        if value in ["ON", "OFF"]:
            self.inst.write(f"OUTPut {value}")
        else:
            '''--コード省略--'''
    #------------------------------------------------------
    #周波数を設定するプロパティ（例なのでch1固定です）
    @property
    def Frequency(self):
        return self.inst.query("FREQ?").strip()
    @Frequency.setter
    def Frequency(self, value):
        self.inst.write(f"SOURce1:FREQ {value}")
        self.wait(f"Frequency {value}", 1)
################################################################
# 出力周波数を100kから200kまで10k刻みで変化させるテストコード
if __name__ == "__main__":
    import time
    wf = WF1968("USB")
    wf.Output = "ON"  # 出力をONに設定
    for i in range(11):
        wf.Frequency = 100000 + (i * 10000)  # 周波数を1kHzから10kHzまで設定
    wf.Output = "OFF"  # 出力をONに設定

```

長くなってきましたので、２つめの派生クラスであるオシロスコープは[次の記事](https://qiita.com/takeru0x5569/items/d7f0da1bd3fb8e71da80)にします。
オシロスコープではスクリーンキャプチャの処理がポイントになります。