今回も計測アプリケーションエンジニア向け、しかも半導体のアナライザと、とってもニッチなHow Toモノです。99.97%の人は恐らくスルーでしょうね。
では行きましょう。
Keysight EasyEXPERTで独自の測定シーケンスを作りたいとき、スクリプトをBuilt-in Functionだけで構成できればいいですが、機能がそれほど多くないので、Widows Command Executionという機能を駆使して独自シーケンスを作成することになります。
このWidows Command Executionという機能、外部アプリケーション(以降「外部アプリ」とします)に機能を委託することができるということなのですが、EasyEXPERTのヘルプやサンプルを探すこと数時間、この機能
は3rdパーティーのプラグインかな?ってくらいに説明がありません。サンプルも。
スタックしたのはテスト定義画面のパラメータと、アプリケーションのINPUT/OUTPUTの関係がどうなっているのかさっぱりわからなかった点です。
以降、数日の格闘の記録を冒険の書としてここに記します。
テストの定義
まずはEasyEXPERTをスタートして、起動したら新規テストを定義するためのテスト定義画面(本当はなんと言うのでしょう)を表示します。
続いてテスト定義画面のTest Contentsタブ、続いてMiscellaneousタブを開きます。
|
テスト定義画面 |
テスト定義画面の左側、スクリプト・ビュー(赤い線の矩形の部分です。本当はなんと言うのでしょうか)のBLOCK~END BLOCK間にCommand文を挿入するため、Command Executionアイコンをポイントした状態でInsertボタンをクリックします。
ここまでの操作でテスト定義画面が上記の画面表示になっていればOKです。
変数の宣言
次のステップとして、今回のサンプルで必要な変数4つをテスト定義画面で宣言します。
スクリプトビューのLocal Variables Definitionをクリックして変数の宣言ペインを表示し、上部のLocal Variables Definition statementの下にあるAdd xxxx Variableボタンで変数の宣言を追加します。
宣言する変数は次の通りです。
return_code (Numeric)
外部アプリが返す実行結果を格納します。
num_var (Numeric)
外部アプリとの間で数値を送受する際に使用します。
str_var (String)
外部アプリとの間で文字列を送受する際に使用します。
num_array (Vector)
外部アプリとの間で配列データを送受する際に使用します。
※ 0から9の数列を初期値として設定しています
|
変数の宣言(定義?)画面 |
Command関数のパラメータ
ここではCommand関数のパラメータについて説明します。
まずは上段のWindows Command Executionにあるパラメータ4つです。
Command Filename
実行する外部アプリのファイル名、今回はあとでビルドするサンプル・アプリケーションのパスを設定します。
Argument
コマンドライン引数、外部アプリのMain関数の引数に、文字列配列で入ってくるアレです。EasyEXPERT側から変数の値を送る場合は、Bult-in Functionを使って
string(<VARNAME>)
とする必要があります。また文字列と変数値の組み合わせを送ることも可能です。
例として、--hold_time=<変数num_varの値> を外部アプリに渡したいばあい
string("--hold_time="+string(num_var))
となり、なかなかにトリッキーです。
Write Type
外部アプリに送るパラメータの型を指定します。
Stringを指定したばあい、テキストボックスに入力した文字列はそのまま文字列として外部アプリに渡ります。
変数の値を外部アプリへ送るには、string(<VARNAME>) とテキストボックスに記述します。
文字列と変数の値を組み合わせて外部アプリへ渡すことも可能です(Argumentsの例参照)。
StringまたはListで設定したパラメータは変数の型にかかわらず、常にパラメータ値をhひとつの文字列として結合し、外部アプリの標準入力の入力データになります。
スクリプトが複数の文字列あるいはListを単一の文字列に結合する際、変数もしくは要素の区切りにCRLFを設定します。 このため外部アプリでは、受け取った文字列をCRLFで分割することで、元のデータに分離することが可能です。
Read Type
外部アプリから実行結果を受け取るパラメータの型を指定します。Read TypeにStringもしくはValueを指定したばあい、外部アプリから受けた文字列は、ひとつのデータとして解釈します。
Read TypeにListを指定したばあい、外部アプリから受け取った文字列はCRLFまたはカンマで要素に分割します。
続いて中段のWrite ・・・の部分と下段のRead ・・・の部分ですが、その前に、ほとんど間違い探しの2つの画像をご覧ください。
上がWrite Type/Read TypeともにString型を指定した時の画面、下がWrite Type/Read TypeともにList型を指定した時の画面です。
|
Write Type/Read TypeともにString型を指定 |
Write String
外部アプリに送りたい文字列を指定します。
パラメータを画像のとおりに設定したばあい、外部アプリは標準入力で、
aaaaa<CRLF>bbbb<CRLF>--number=<num_barの値><EOF>
という文字列を受け取ることになります。
仮に外部アプリに20個以上のパラメータを設定するばあいは、前述のArgumentを使用すれば対応できます。
ただしこのとき高確率でカオスになります。
Read String
ResultとString、2つのテキストボックスがあります。
Resultは、外部アプリ終了時の終了ステータス(外部アプリのMain関数の戻り値)を受け取ります。
Stringは、外部アプリの標準出力で出力したデータを受け取ります。
|
Write Type/Read TypeともにList型を指定 |
Write List
外部アプリに送りたいVector型の変数を指定します。
num_arrayには、0から9までの数値が入っていることを前提に、パラメータを画像のとおり設定したばあい、外部アプリは標準入力で、
0<CRLF>1<CRLF>2<CRLF>3<CRLF>4<CRLF>5<CRLF>6<CRLF>7<CRLF>8<CRLF>9<CRLF><EOF>
という文字列を受け取ることになります。
Read List
Read Listにも、Read Stringのときとおなじく2つのテキストボックスがあります。
ResultはRead Stringのときと同様、外部アプリ終了時の終了ステータスを受け取ります。
Valuesは、Command関数が実行した外部アプリが標準出力で出力した文字列をカンマ、TABもしくはCRLFで分割し、配列にした結果を保持します。
外部アプリの作成
では実際にスクリプトのWindows Command Executionで実行する外部アプリ(コンソールアプリケーション)を作成して行きます。
開発環境には、
Visual Studio Express 2013 for Windows Desktop を使用します。
Visual Studioをスタートし、起動したらメニューの ファイル > 新しいプロジェクト でダイアログを開き、Visual C# > コンソールアプリケーション を選択します。
名前テキストボックスに任意のアプリケーション名を付け(ここでは「WinComExec」としています)、OKボタンをクリックしてプロジェクトを生成します。
エラー発生時とデバッグのときに変数値をMessageBoxで確認するため、アセンブリSystem.Windows.Formsをプロジェクトの参照設定で追加しています。
今回ビルドするアプリケーションのProgram.csは以下のとおりです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; // MessageBoxを使用します。参照設定にも追加してください。
namespace WinComExec
{
class Program
{
/// <summary>
/// コマンドラインパラメータが "--help(-h)" を含んでいるばあいに表示するヘルプメッセージを定義します
/// </summary>
const string help = "";
/// <summary>
/// コマンドラインパラメータが "--debug(-d)" を含むばあい値をtrueに設定します
/// </summary>
static bool _debug = false;
/// <summary>
/// 機能を実装します
/// </summary>
/// <param name="args">--help(-h)または--debug(―d)が有効です</param>
/// <returns>Read TypeにNone以外を指定したばあい、Resultで設定したパラメータに値を設定します</returns>
static int Main(string[] args)
{
int stat = 0;
try
{
// コマンドラインパラメータを解析します
foreach (string arg in args)
{
string a = arg.Trim().ToLower();
switch (a)
{
case "--help":
case "-h":
Console.Out.Write(help);
return 0;
case "--debug":
case "-d":
_debug = true;
break;
default:
throw new ArgumentException("無効なコマンドラインパラメータです\n" + arg);
}
}
// --helpオプションの指定がなく、このあとのReadToEndで何も入力がないと、アプリケーションは入力
// 待ちのまま何もしません。 別スレッドを起動し、一定時間入力が無いばあいにあプログラムを強制終了
// するようにします。
System.Threading.Thread timeoutProc = new System.Threading.Thread(ReadToEndTimeoutProc);
timeoutProc.Start();
// 標準入力からのデータ入力完了を待ちます
string src = Console.In.ReadToEnd();
// 標準入力からのデータ入力が完了したのでスレッドを終了します
timeoutProc.Abort();
// (SAMPLE) srcをセパレータで分割、文字列配列化して、要素を逆順に並び替えたうえでCSVにします。
string[] inputs = src.Split(new string[] { ",", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
string result = string.Join(",", inputs.Reverse());
// 実行結果を標準出力に出力します
Console.Out.Write(result);
// コマンドラインパラメータで--debug(―d)の指定があるばあい、インプットとアウトプットをファイルに
// 出力したうでメッセージボックスに表示します
if (_debug)
{
string logMessage = string.Format("IN:\n{0}\n\nOUT:\n{1}", src, result);
System.IO.File.WriteAllText("C:\\source.txt", logMessage);
MessageBox.Show(logMessage);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, typeof(Program).Namespace, MessageBoxButtons.OK, MessageBoxIcon.Error);
stat = -1;
}
return stat;
}
/// <summary>
/// ReadToEndの入力タイムアウト処理を実装します
/// </summary>
/// <param name="obj"></param>
private static void ReadToEndTimeoutProc(object obj)
{
// 5秒間で入力が完了しなければメッセージボックスでエラーを表示します。
System.Threading.Thread.Sleep(5000);
MessageBox.Show("文字列の読み込みがタイムアウトしました。\nヘルプを表示するにはコマンドラインオプションで --help または -h を指定してください。",
typeof(Program).Namespace, MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(258); // =WAIT_TIMEOUT;
}
}
}
主要なロジックについて解説していきます。カッコ内の数字は行番号です。
Main関数の戻り値 (26)
新規プロジェクトの追加ウィザードで生成したプロジェクトのMain関数の戻り値は、デフォルトではvoidですが、スクリプトでは実行結果を取得することが出来、かつ実行結果に応じて処理を変えることが可能になるので、intにします。
コマンドラインパラメータの解析 (31-47)
コマンドラインパラメータには、デバッグモードでの起動スイッチとなる --debug およびヘルプを表示するための --help を指定できるようにしています。
Console.In.ReadToEndの入力待ちタイムアウト処理・スレッドの起動 (51-52)
単純なスレッドを起動します。82行目以降がスレッドの本体です。
標準入力 (54)
アプリケーションは、テスト定義のWrite StringまたはWrite Listで設定したパラメータをここで読み込みます。
テスト定義のWrite TypeでListを選択するか、Stringで複数のデータを送るばあい、スクリプトはデータとデータのセパレータとしてCRLFを挿入した一つの文字列としてアプリケーションの標準入力に書き込みます。
入力待ちタイムアウト処理/スレッドの中止 (56)
標準入力に入力があったばあい、タイムアウト処理スレッドをAbortします。
標準出力 (61)
実行結果を標準出力で出力します。
テスト定義でRead TypeをStringにしたばあい、外部アプリが出力した結果データは、スクリプトが文字列としてそのまま変数に代入します。
同じくRead TypeをValueにしたばあいは、スクリプトが数値に変換して変数に代入します。
またRead TypeをListにしたばあい、スクリプトがセパレータ(カンマ、TAB、LF、CRLF)で分割して、Vectorの各要素に値を設定します。
デバッグのコツ
デバッグはコマンドラインパラメータに --debug を設定した時だけ実施するロジックを埋め込んでおき、インプットとアウトプットが期待する動きになっているかをモニタするのがベストプラクティスです。
アプリケーションの規模が大きくなるばあいは、ログファイルの併用も有効です。
その他個人的な感想としては、テスト定義画面のDebugにあるInspect機能はやや癖があるので、必要な時だけに使用を限定したほうが無難でしょう。
さらにはEasyEXPERTのビルトイン関数を組み合わせてなんとか実装できるような複雑な処理であれば、新たに外部アプリを作ってしまったほうが結果的に早く実装ができると思います。
これは先ほどのInspect機能の使い難さからくるデバッグ工数の増加と、ビルトイン関数の機能を確認するためサンプルを作って動作確認してみたものの、期待していた結果にならないことがあるためです。
C#やVB.net、C++などでのプログラミングに抵抗がないなら、迷わず外部アプリでの実装を検討しましょう。
ということで、今回も役に立つのか立たないのか微妙なTipsでした。