Go言語でNI-VISA続編(tviewでTUIアプリ化)

GoとVISAで計測器通信ができるようになったら、今度は表示をなんとかしたくなってきた。
温度計測にしたところで、画面的には単純に時間と温度をprintするだけだと、ひたすらスクロールしていくだけのものしか作れないのが寂しい。
 チャートやグラフとはいわないまでも、同じ位置でデジタル表示できるとか、ボタンやテキストボックスでなにか設定変更できたりするとよいなと思っていた。
 あまり凝ったものでなくてもいいので、1行程度のデータ表示と、数点の入力項目やボタンのある画面が作れればより実用的になるはず。
探してみると、Go言語用のTUIアプリケーションを作るためのtviewというライブラリがあった。

github.com

tviewのサンプルを見よう見まねで、VISA(232C)経由で温度・湿度表示させるサンプルを作成してみた。
usbrh2とUSB-232C経由で通信、1秒ごとに温度と湿度を取得して表示するだけのもの。
スクロールすることなく、画面の定位置でデータが1秒間隔ごとに更新される。
Control+Qキーで終了する。
これなら、SSH接続したターミナルで動くので、GUIの無いLinuxサーバやRasPiに計測器を接続してリモート操作するのに使えそう。

画面

example_visa_tview

ソース
[example_visa_tview.go]

package main

import (
    "fmt"
    "strings"
    "time"

    "github.com/gdamore/tcell/v2"
    vi "github.com/jpoirier/visa"
    "github.com/rivo/tview"
)

func currentTimeString() string {
    t := time.Now()
    return t.Format("15:04:05")
}

func updateTextView(app *tview.Application, textView *tview.TextView, instr vi.Object) {
    var tmpr, rhmd float64
    var tmp_msg, msg string
    for {
        time.Sleep(1 * time.Second)

        b := []byte("getrh\r\n")
        _, status := instr.Write(b, uint32(len(b)))
        if status < vi.SUCCESS {
            // fmt.Println("Error writing to the device, exiting...")
            msg = "Error writing to the device!"
            // return
        } else {
            buffer, _, status := instr.Read(100)
            if status < vi.SUCCESS {
                // fmt.Println("Error reading a response from the device")
                msg = "Error reading a response from the device!"
                // return
            } else {
                tmp_msg = string(buffer)
                tmp_msg = strings.TrimSpace(tmp_msg)
                fmt.Sscanf(tmp_msg, ":%f,%f,", &tmpr, &rhmd)
                msg = fmt.Sprintf("Temperature: %.2f `C, RHumidity: %.2f %%\n", tmpr, rhmd)

                // fmt.Printf("Count: %d Data: %s\n", retCount, buffer)
            }
        }

        app.QueueUpdateDraw(func() {
            textView.SetText(fmt.Sprintf("%s: %s", currentTimeString(), msg))
        })
    }
}

func main() {

    rm, status := vi.OpenDefaultRM()
    if status < vi.SUCCESS {
        fmt.Println("Could not open a session to the VISA Resource Manager!")
        return
    }
    defer rm.Close()

    instr, status := rm.Open("ASRL33::INSTR", vi.NULL, vi.NULL)
    //  instr, status := rm.Open("USB0::0xCAFE::0x4000::E661410403177E38::INSTR", vi.NULL, vi.NULL)
    if status < vi.SUCCESS {
        fmt.Println("An error occurred opening the session to ASRL33::INSTR")
        return
    }
    defer instr.Close()
    instr.SetAttribute(vi.ATTR_TMO_VALUE, 5000)
    instr.SetAttribute(vi.ATTR_TERMCHAR_EN, 1)
    instr.SetAttribute(vi.ATTR_TERMCHAR, 0x0A)

    app := tview.NewApplication()
    app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
        switch event.Key() {
        //      case tcell.KeyEnter: // Press EnterKey
        case tcell.KeyCtrlQ: // Press Ctrl+Q
            app.Stop() // Stop this app.
            return nil
        }
        return event
    })
    textView := tview.NewTextView()
    flex := tview.NewFlex().
        AddItem(textView, 0, 1, true)
        //  AddItem(textView2, 0, 1, true)
    flex.SetBorder(true).SetTitle(" usbrh data monitor - Press Ctrl+Q to exit this app.")

    go updateTextView(app, textView, instr)

    if err := app.SetRoot(flex, true).Run(); err != nil {
        panic(err)
    }

    rm.Close()
    instr.Close()
}

COMポートを"ASRL33::INSTR"としているので、接続したポートに合わせて修正が必要。
 具体的には"rm.Open("ASRL33::INSTR", vi.NULL, vi.NULL)"の"ASRL33:INSTR"の部分を書き換える
実行するには、モジュールの初期化とライブラリの取得後に go run で実行する

$ go mod init main
$ go mod tidy
$ go run example_visa_tview.go

オープンソースなUSB-GPIBコンバータ

NIのUSB-GPIBの定価が20万を超えていて、気軽に購入できる価格ではなくなっている。
個人レベルでも購入できそうなものはないかと探してみると、オープンソースでソフトや回路図が公開されているものが2つほど見つかった。
両方とも開発者は海外のようですが、組み立て済みのものを購入できるらしい。
どちらも1万円以下で購入できるようです。

1.ARDUINO UNO as a USB to GPIB controller
(https://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html)
 USB-シリアル I/F になっていて、独自コマンドでGPIBアドレスを指定したり、コマンド送信・受信を切り替えたりが必要なようです。
日本の方が実際に組み立てて動かすブログを公開されていて、組み立て済みのUSB-GPIBコンバーターを購入することができるようです。

www.akatsuki-lab.co.jp

2.UsbGpib (https://github.com/xyphro/UsbGpib)
 こちらはUSBTMC I/Fになっており、自動でGPIBアドレスを検出する機能もあるようです。
 組み立て済みのものをELECROWというところで購入できるようです。
 ELECROW UsbGpib Converter (https://www.elecrow.com/xyphrolabs-gpibusb.html)

どちらもGPIB I/Fとして認識されるわけではなく、1の方はCOMポート(USB-シリアル)、2の方はUSBTMCとして通信します。
1は独自コマンドで通信先のGPIBアドレスを設定したり、コマンド送信・受信時の前に特殊なコマンドを送信したりする必要があるようです。既存プログラムで利用する場合には修正が必要。
2は接続した計測器GPIBアドレスを自動的に調べることができて、その後の読み書きはそのままVISA Read/Writeでコマンド送受信できるようです。もしそうであれば、既存プログラムの修正はVISAリソース名だけで済むかもしれません。

ちなみに、どちらもArduinoやAVRマイコンを使っており、GPIB 通信用にGPIOが5VでHi-Z対応のマイコンが必要というのが理由のようです。

オープンソースなUSB-GPIBコンバータ

NIのUSB-GPIBの定価が20万を超えていて、気軽に購入できる価格ではなくなっている。
個人レベルでも購入できそうなものはないかと探してみると、オープンソースでソフトや回路図が公開されているものが2つほど見つかった。
両方とも開発者は海外のようですが、組み立て済みのものを購入できるらしい。
どちらも1万円以下で購入できるようです。

1.ARDUINO UNO as a USB to GPIB controller
(https://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html)
 USB-シリアル接続になっていて、特殊なコマンドでGPIBアドレスを指定したり、コマンド送信・受信を切り替えたりが必要なようです。
日本の方が実際に組み立てて動かすブログを公開されていて、組み立て済みのUSB-GPIBコンバーターを購入することができるようです。

www.akatsuki-lab.co.jp

2.UsbGpib (https://github.com/xyphro/UsbGpib)
 こちらはUSBTMCになっており、独自コマンドでGPIBアドレスを指定する方式のようです。
 組み立て済みのものをELECROWというところで購入できるようです。
 ELECROW UsbGpib Converter (https://www.elecrow.com/xyphrolabs-gpibusb.html)

実際にGPIB I/Fとして認識できるわけではなく、1の方はUSB-シリアル通信、2の方はUSBTMCとして通信、特殊なコマンドで通信相手先のGPIBアドレスを設定したり、コマンド送信・受信時の前に特殊なコマンドを送信したりする必要があるようです。

ちなみに、どちらもArduinoマイコンを使っている。GPIB I/F用にGPIOが5Vのマイコンが必要というのが理由のようです。

オープンソースなUSB-GPIBコンバータ

NIのUSB-GPIBの定価が20万を超えていて、ちょっと気軽に入手できなくなっている。 個人レベルでも購入できそうなものはないかと探してみると、オープンソースでソフトや回路図が公開されているものが2つほど見つかった。
両方とも開発者は海外のようですが、組み立て済みのものを購入できるらしい。
どちらも1万円以下で購入できるようです。

1.ARDUINO UNO as a USB to GPIB controller
(https://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html)
 USB-シリアル接続になっていて、特殊なコマンドでGPIBアドレスを指定したり、コマンド送信・受信を切り替えたりが必要なようです。
日本の方が実際に組み立てて動かすブログを公開されていて、組み立て済みのUSB-GPIBコンバーターを購入することができるようです。

www.akatsuki-lab.co.jp

2.UsbGpib (https://github.com/xyphro/UsbGpib)
 こちらはUSBTMCになっており、独自コマンドでGPIBアドレスを指定する方式のようです。
 組み立て済みのものをELECROWというところで購入できるようです。
 ELECROW UsbGpib Converter (https://www.elecrow.com/xyphrolabs-gpibusb.html)

実際にGPIB I/Fとして認識できるわけではなく、1の方はUSB-シリアル通信、2の方はUSBTMCとして通信、特殊なコマンドで通信相手先のGPIBアドレスを設定したり、コマンド送信・受信時の前に特殊なコマンドを送信したりする必要があるようです。

ちなみに、どちらもArduinoマイコンを使っている。GPIB I/F用にGPIOが5Vのマイコンが必要というのが理由のようです。

オープンソースなUSB-GPIBコンバータ

NIのUSB-GPIBの定価が20万を超えていて、ちょっと気軽に入手できなくなっている。 個人レベルでも購入できそうなものはないかと探してみると、オープンソースでソフトや回路図が公開されているものが2つほど見つかった。
両方とも開発者は海外のようですが、組み立て済みのものを購入できるらしい。
どちらも1万円以下で購入できるようです。

1.ARDUINO UNO as a USB to GPIB controller
(https://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html)
 USB-シリアル接続になっていて、特殊なコマンドでGPIBアドレスを指定したり、コマンド送信・受信を切り替えたりが必要なようです。
日本の方が実際に組み立てて動かすブログを公開されていて、組み立て済みのUSB-GPIBコンバーターを購入することができるようです。

2.UsbGpib (https://github.com/xyphro/UsbGpib)
 こちらはUSBTMCになっており、独自コマンドでGPIBアドレスを指定する方式のようです。
 組み立て済みのものをELECROWというところで購入できるようです。
 ELECROW UsbGpib Converter (https://www.elecrow.com/xyphrolabs-gpibusb.html)

実際にGPIB I/Fとして認識できるわけではなく、1の方はUSB-シリアル通信、2の方はUSBTMCとして通信、特殊なコマンドで通信相手先のGPIBアドレスを設定したり、コマンド送信・受信時の前に特殊なコマンドを送信したりする必要があるようです。

ちなみに、どちらもArduinoマイコンを使っている。GPIB I/F用にGPIOが5Vのマイコンが必要というのが理由のようです。

オープンソースなUSB-GPIBコンバータ

NIのUSB-GPIBの定価が20万を超えていて、ちょっと気軽に入手できなくなっている。 個人レベルでも購入できそうなものはないかと探してみると、オープンソースでソフトや回路図が公開されているものが2つほど見つかった。
両方とも開発者は海外のようですが、組み立て済みのものを購入できるらしい。
どちらも1万円以下で購入できるようです。

1.ARDUINO UNO as a USB to GPIB controller
(https://egirland.blogspot.com/2014/03/arduino-uno-as-usb-to-gpib-controller.html)
 USB-シリアル接続になっていて、特殊なコマンドでGPIBアドレスを指定したり、コマンド送信・受信を切り替えたりが必要なようです。
日本の方が実際に組み立てて動かすブログを公開されていて、組み立て済みのUSB-GPIBコンバーターを購入することができるようです。

2.UsbGpib (https://github.com/xyphro/UsbGpib)
 こちらはUSBTMCになっており、独自コマンドでGPIBアドレスを指定する方式のようです。
 組み立て済みのものをELECROWというところで購入できるようです。
 ELECROW UsbGpib Converter (https://www.elecrow.com/xyphrolabs-gpibusb.html)

どちらもArduinoマイコンを使っている。利用としてはGPIB I/F用にGPIOが5Vのマイコンが必要ということのようです。
実際にGPIB I/Fとして認識できるわけではなく、1の方はUSB-シリアル通信、2の方はUSBTMCとして通信、特殊なコマンドで通信相手先のGPIBアドレスを設定したり、コマンド送信・受信時の前に特殊なコマンドを送信したりする必要があるようです。

Go言語でNI-VISA続編(TCP接続)

Go言語でNI-VISAを使用できるかどうか、シリアル通信やUSBTMCを使って確認してきたが、TCP接続は未確認だったので、あらためて確認してみた。
結論としては、問題なく通信できることがわかった。RS-VISAとRasPiOS(Legacy32bit)の組み合わせでも同じ。
1点だけ、気をつける必要があったのは終端文字の設定で、232C/USBTMCの場合は特に設定しなくてもReadエラーにならなかったが、TCPの場合は、終端文字の設定をしないと、指定したバイト数までデータ受信できないとTimeoutエラーになる。(通信の相手がCR/LF、もしくは、LFを終端文字としている場合 )

なお、計測器通信の場合、TCPのリソース名の種類が2種類ある。
今回、使用したのは、通常のTCP SOCKETの方(TCPサーバ Listen待ちを想定)
(VXI-11対応の計測器が手元にないので調べられないが、いずれ機会があれば調べてみたい。おそらく問題ないとは思うが。)

TCP接続のリソース名
* TCP SOCKETの場合: "TCP::SOCKET" * VXI-11またはLXIの場合: "TCP::INSTR"

RdWrt.goをTCP接続用に終端文字の設定を追加したソース

package main

import (
    "fmt"

    vi "github.com/jpoirier/visa"
)

func main() {
    // First we must call viOpenDefaultRM to get the resource manager
    // handle.  We will store this handle in defaultRM.
    rm, status := vi.OpenDefaultRM()
    if status < vi.SUCCESS {
        fmt.Println("Could not open a session to the VISA Resource Manager!")
        return
    }
    defer rm.Close()

    instr, status := rm.Open("TCPIP::localhost::32767::SOCKET", vi.NULL, vi.NULL) // localhostの部分がIP/Hostname、32767はport番号
    if status < vi.SUCCESS {
        fmt.Println("An error occurred opening the session to TCPIP::localhost::32767")
        return
    }
    defer instr.Close()

    // Set timeout value to 5000 milliseconds (5 seconds).
    instr.SetAttribute(vi.ATTR_TMO_VALUE, 5000)

    instr.SetAttribute(vi.ATTR_TERMCHAR_EN, 1) // 終端文字有効に設定 1= 有効
    instr.SetAttribute(vi.ATTR_TERMCHAR, 0x0A) // 終端文字として 0x0A(LF)を設定


    // At this point we now have a session open to the instrument at
    // Primary Address 2.  We can use this session handle to write
    // an ASCII command to the instrument.  We will use the viWrite function
    // to send the string "*IDN?", asking for the device's identification.
    b := []byte("*IDN?\r\n")
    _, status = instr.Write(b, uint32(len(b)))
    if status < vi.SUCCESS {
        fmt.Println("Error writing to the device, exiting...")
        return
    }

    buffer, retCount, status := instr.Read(1024)
    if status < vi.SUCCESS {
        fmt.Println("Error reading a response from the device")
    } else {
        fmt.Printf("Count: %d Data: %s\n", retCount, buffer)
    }

    // Now we will close the session to the instrument using
    // viClose. This operation frees all system resources.

    fmt.Println("Closing Sessions...")
}