OWON VDS3104 をpythonで制御 (その3)

[前回](https://qiita.com/nachtele/items/d80c6c51208f1dbf0397)に引き続き、OWON の USBオシロスコープ VDS3104(LAN無しモデル) をpythonで制御してみます。

#環境
OS : Windows 10
対象機器 : VDS3104
アプリ : OWON VDS 2.0.19.4
言語 : python 3.7

#データと波形イメージを自動採取
実用的な例として、pythonで信号源を操作しながらデータと波形イメージを複数回採取してみます。このようなプログラムにしておけば、オシロのモード設定やオフセット設定などの地味に面倒くさい作業から解放されますし、後日同じような波形を採取する時にプログラムを実行するだけで完全に同じ条件を再現できます。
今回の例では、USB-シリアル変換ICのRTS出力をpySerialで操作しながら、立ち下り波形と立ち上り波形を順次採取してみました。(無負荷だと面白くないので)RTS出力にRCフィルター(1kΩ, 0.1uF)を接続し、オシロのCH1にRTS出力を、CH2にフィルター後の信号を接続しています。

ポイントとしては、

* *RUNSTop? クエリで現在の状態を確認してから、必要に応じて *RUNSTop コマンドを送る。
* *RUNSTop コマンドでRUN状態にするとトリガーモードがAUTOになってしまうので、必要ならその後に :TRIGger:MODE SINGle または :TRIGger:MODE NORMal コマンドを送る。
* RUN状態で *LDM? , *RDM? , または :SAVE:CSV コマンドを送ると STOP 状態になるので、次の採取をするには上記の *RUNSTop コマンドと、必要なら :TRIGger:MODE SINGle または :TRIGger:MODE NORMal コマンドを送る。
* 最初に *RST コマンドで初期化しておくと良いが、 :MEASure と :SAVE:IMAGe:INVert の設定は残るようなので、必要なら :MEASure:DELete ALL と :SAVE:IMAGe:INVert OFF コマンドを送っておく。

```python:vds03.py
from socket import AF_INET, socket, SOCK_STREAM, setdefaulttimeout
from time import sleep
import serial

class LanSource:
    def __init__(self, address):
        #param address: (ip, port) -> (str, int)
        super(LanSource, self).__init__()
        setdefaulttimeout(5)
        client = socket(AF_INET, SOCK_STREAM)
        client.connect(address)
        client.settimeout(5)
        self.address = address
        self.client = client

    def close(self):
        self.client.close()

def PlayCmds(ls, cmdlist):
    for cmd, slp, dst in cmdlist:
        print(cmd)
        ls.client.send(cmd)
        sleep(slp)
        dat = b''
        while True:
            rsv = ls.client.recv(2048)
            dat += rsv
            if len(rsv) < 2048: break
        if dst:
            with open(dst,"wb") as f:
                f.write(dat)
        else:
            print(dat)
    return dat

def RUNSingle(ls):
    tg =  b'STOP\n\r'
    res = PlayCmds(ls, [[b'*RUNStop?', 0, '']])
    if (res == tg):
        PlayCmds(ls, [[b'*RUNStop', 0, '']])
    PlayCmds(ls, [[b':TRIGger:MODE SINGle', 1, '']])

Commands_Init = [
     [b'*IDN?', 0, '']
    ,[b'*RST', 1, '']
    ,[b':MEASure:DELete ALL', 0, '']
    ,[b':SAVE:IMAGe:INVert OFF', 0, '']
    ,[b':CHANnel1:DISPlay ON', 0, '']
    ,[b':CHANnel1:COUPling DC', 0, '']
    ,[b':CHANnel1:SCALe 1', 0, '']
    ,[b':CHANnel1:OFFSet -75', 0, '']
    ,[b':CHANnel2:DISPlay ON', 0, '']
    ,[b':CHANnel2:COUPling DC', 0, '']
    ,[b':CHANnel2:SCALe 1', 0, '']
    ,[b':CHANnel2:OFFSet -75', 0, '']
    ,[b':CHANnel3:DISPlay OFF', 0, '']
    ,[b':CHANnel4:DISPlay OFF', 0, '']
    ,[b':ACQuire:MDEPth 10K', 0, '']
    ,[b':TIMebase:SCALe 100us', 0, '']
    ,[b':TIMebase:HOFFset 400', 0, '']
    ,[b':TRIGger:TYPE SINGle', 0, '']
    ,[b':TRIGger:SINGle:EDGE:SOURce CH1', 0, '']
    ,[b':TRIGger:SINGle:EDGE:SLOPe FALL', 0, '']
    ,[b':TRIGger:SINGle:EDGE:LEVel 0', 0, '']
]
Commands1 = [
     [b'*LDM? C:\\tmp\\DAT', 2, '']
    ,[b':SAVE:IMAGe C:\\TMP\\FALL.png', 1, '']
    ,[b':TRIGger:SINGle:EDGE:SLOPe RISE', 0, '']
]
Commands2 = [
     [b'*LDM? C:\\tmp\\DAT', 2, '']
    ,[b':SAVE:IMAGe C:\\TMP\\RISE.png', 1, '']
]

if __name__ == '__main__':
    ls = LanSource(('localhost', 5188))
    PlayCmds(ls, Commands_Init)
    ser = serial.Serial()
    ser.baudrate = 9600
    ser.port = 'COM5'
    ser.setRTS(0)
    ser.open()
    RUNSingle(ls)
    ser.setRTS(1)
    PlayCmds(ls, Commands1)
    RUNSingle(ls)
    ser.setRTS(0)
    PlayCmds(ls, Commands2)
    ser.close()
    ls.close()
```

#動作例(動画)
<iframe width="941" height="716" src="https://www.youtube.com/embed/Vbf4x-SJPvA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

[次回](https://qiita.com/nachtele/items/e5b9e57b0375c4ad1bae)に続く。