スクリプトで動的応答を作る(C# で状態保持・分岐)

このチュートリアルは 固定値やテンプレートでは表現できない「状態保持・計算・分岐」を、インライン C# スクリプトによる動的応答で実現することを試すためのものです。題材は小さなテキスト行プロトコル CounterDev。同梱プロジェクトを開いてスクリプト実行を有効化(トラスト)し、起動すると、リクエストのたびに増えるカウンタコマンドでの分岐状態のリセットを一連で試せて、そこから「自分の機器の動的な応答をどう書くか」を掴めます。

CounterDevTCP 7400テキスト行(READ / PING / RESET)State でカウンタ保持受信で分岐トラスト(有効化)実機不要

このページでできること

スクリプト応答の主役は 3 つです。①と②でスクリプトならではの価値を実感でき、③まで行くと自分で書き換える足がかりになります。

  • ① 状態保持(State でカウンタ)READ を送るたびに VAL:1 → VAL:2 → VAL:3 …毎回違う値が返る。固定値テンプレートでは絶対にできない「前回値を覚えて次を返す」をスクリプトで実現する。
  • ② 受信での分岐PINGPONGRESET → カウンタを 0 に戻す、未知コマンド → 受信内容をエコー。1 つのスクリプトが受信内容を見て応答を組み立てる
  • ③ 書き換え(構文チェック):応答文字列やコマンドを自分で書き換え、「構文チェック」で確認してから反映する。
前提:このチュートリアルは オートメーションを使いこなす を済ませていると、トリガー(パケット一致)と送信ステップの関係がスムーズに理解できます(スクリプト応答は送信ステップの一種です)。
セキュリティ:スクリプトはこのアプリと同じ権限で実行される任意コード実行です。だから既定で無効で、プロジェクトごとに自分で有効化(トラスト)したときだけ動きます(ステップ 1)。自分が記述・信頼できるコードのみ有効化してください。詳しくは スクリプト応答のセキュリティ

題材プロトコル CounterDev とスクリプト

CounterDev は、本チュートリアル用の小さなテキスト行プロトコルです。クライアントが 1 行のコマンド(末尾 \r\n)を送ると、CommSim 本体の1 つのインライン C# スクリプトが内容に応じて応答を生成します。この 1 台に、あらゆる受信で起動するパケット一致オートメーションが 1 つあり、その送信ステップが Script(スクリプト応答)です。

受信(① が送る)スクリプトの応答教える概念
READVAL:1VAL:2VAL:3 …(毎回 +1)State でカウンタ保持(テンプレートでは不可能)
PINGPONG受信での分岐
RESETOK reset(カウンタを 0 へ)State の書き換えLog()
上記以外(例 HELLOERR unknown:HELLO受信エコー・未知コマンド処理

スクリプト本体(メソッド本体だけを書きます。クラス定義・using は不要):

// 受信した1行コマンドに応じて動的に応答する(State でカウンタを保持)
var cmd = ReceivedText.Trim();
if (cmd == "PING")  return Utf8("PONG\r\n");
if (cmd == "RESET") { State["seq"] = 0; Log("カウンタをリセットしました"); return Utf8("OK reset\r\n"); }
if (cmd == "READ")
{
    var n = (int)(State.TryGetValue("seq", out var v) ? v! : 0) + 1;
    State["seq"] = n;                 // セッション内で持続(毎回違う値)
    return Utf8($"VAL:{n}\r\n");
}
return Utf8($"ERR unknown:{cmd}\r\n"); // 未知コマンドは受信内容をエコー
3 つの読みどころ
State はセッション内で持続する辞書READ のたびに State["seq"] を +1 して返すので、毎回違う値になります(セッションを停止するとクリア)。
ReceivedText は受信を UTF-8 でテキスト化したもの.Trim() で末尾の \r\n を落としてコマンドを判定します。
Utf8(...) / Hex(...) は応答バイト列を作るヘルパbyte[]return すると送信、null を返すと無応答です。使える変数・ヘルパの一覧は スクリプト応答 を参照。

用意するもの

ファイル内容
script-response/script-response.commsimCounterDev スタブのプロジェクト(TCP 7400・あらゆる受信で起動するパケット一致オートメーション+Script ステップが設定済み)

これは CommSim.Sample の実行フォルダ内 samples/script-response/ に同梱されています。必要なのは CommSim 本体と CommSim.Sample の 2 本だけで、上流の実機は不要です(CommSim.Sample のクライアント役がアプリ役を演じます)。

ステップ1:スクリプト実行を有効化する(トラスト)

スクリプト応答は既定で無効です。スクリプトを含むプロジェクトを開くと、ウィンドウ上部に確認バナーが出ます。まずこれを有効化します。

  1. CommSim 本体で samples/script-response/script-response.commsim を開く(「ファイル」→「ファイルから開く…」/Ctrl+O)。
  2. 上部のバナー「このプロジェクトはスクリプトを含みます。実行するには有効化してください」の [有効化する] をクリックし、確認ダイアログで承認する。
    (または「プロジェクト」→「プロジェクト設定...」→「スクリプト / プラグイン」タブ →「スクリプト応答を有効にする」をオンにする。)
プロジェクト設定の「表示・スクリプト」タブ。スクリプト/プラグインは同じ権限で実行される旨の注意書きと「スクリプト応答を有効にする(このプロジェクトを信頼)」トグル・既定タイムアウトが表示され、背後の上部に確認バナーと「有効化する」ボタンが見える
スクリプト実行の有効化(トラスト)。プロジェクト設定(「プロジェクト」→「プロジェクト設定...」)の「表示・スクリプト」タブにある「スクリプト応答を有効にする(このプロジェクトを信頼)」トグル、または画面上部の確認バナーの[有効化する]から行う。トラストはこの PC・このファイルのパス単位で記録され、.commsim 自体には書かれない(他人に渡したプロジェクトは受け取った側が有効化しない限り動かない)。一度有効化すれば次回以降バナーは出ない。
なぜ有効化が要るのか:スクリプトは任意の C# を実行します。意図せず・他人のプロジェクト経由で危険なコードが動くのを防ぐため、既定オフ+明示的なオプトインにしています。これは事故防止のための仕組みで、Roslyn は完全なサンドボックスではありません(→ セキュリティについて)。

ステップ2:動かしてカウンタと分岐を見る(すぐ試せる)

① CommSim.Sampleテスト対象アプリ役:READ/PING/RESET を送信
CommSim 本体CounterDev 機器役(スタブ):スクリプトで動的応答
⇠ 実機不要
実機使わない
  1. オートメーション」タブを開き、「コマンド応答(スクリプト)」を選んで [編集]→ 送信データの Script ステップの [編集…] でコードを一望する(§題材プロトコルのコードが入っています)。
  2. セッション「スクリプト応答スタブ TCP 7400」を「起動」する。
  3. CommSim.Sample を起動し、「動かすサンプル」で「スクリプト応答(C# で動的応答・CounterDev)」を選ぶ。① ペインを「接続」(7400 へ接続)して「デモ送信」を押す(READ×3 → PINGRESETREADHELLO が順に送られます)。
オートメーション編集ダイアログ。トリガーが PacketMatch・Contains・値が空(あらゆる受信に一致)、送信データに種別 Script のステップが 1 つあり、スクリプト列に「編集…」ボタンが表示されている
オートメーション「コマンド応答(スクリプト)」の編集画面。トリガーは PacketMatch・Contains・値が空あらゆる受信でこのスクリプトが起動する。送信データはステップ 1 つで、種別が Script。スクリプト列の [編集…] を押すと、§題材プロトコルの C# コードを編集する「スクリプト応答の編集」ダイアログが開き、そこで「構文チェック」やステップ別タイムアウト上書きができる。

観察できること

  • READ① が送信(1 回目)
  • VAL:1カウンタ=1
  • READ① が送信(2 回目)
  • VAL:2State で +1 されて 2
  • READ① が送信(3 回目)
  • VAL:33(毎回違う=テンプレ不可)
  • PING分岐
  • PONGPING → PONG
  • RESET状態を書き換え
  • OK resetカウンタを 0 に
  • READRESET 後の READ
  • VAL:11 に戻る=State が効いた証明
  • HELLO未知コマンド
  • ERR unknown:HELLO受信内容をエコー
ログ:READ 受信に VAL:1 / VAL:2 / VAL:3 と増える応答、PING に PONG、RESET に OK reset、その後の READ に VAL:1(カウンタが戻る)、HELLO に ERR unknown:HELLO が自動応答される
受信コマンドに対してスクリプトが動的に応答するログ。READ のたびに VAL:1 → VAL:2 → VAL:3増えていくState のカウンタ)。PINGPONGRESETOK reset、その後の READVAL:1戻る(State が効いている証明)、未知の HELLOERR unknown:HELLO とエコー。[SCRIPT] 行は Log() 出力。
ここがステップ 2(すぐ試せる)の到達点です。同じ 1 つのスクリプトが、受信内容で応答を変え(PING/RESET/未知)、前回の状態を覚えて次を返す(READ のカウンタ)。これが固定値テンプレートやバイナリ構造応答との決定的な違いです。RESET 後に READ が VAL:1 に戻るのが、State が「セッション内で生きている」何よりの証拠です。

ステップ3:スクリプトを少し書き換える

スクリプトは自分で書き換えられます。応答を変えてみましょう。

  1. 「オートメーション」タブ →「コマンド応答(スクリプト)」→ [編集]→ Script ステップの [編集…]を開く。
  2. たとえば READ の応答を return Utf8($"VAL:{n} (count)\r\n"); に変える、PING の応答を "PONG v2\r\n" にする、新しいコマンド if (cmd == "HELLO") return Utf8("HI THERE\r\n");READ の前に足す、など。
  3. [構文チェック]を押してエラーが無いことを確認する(OK 表示)。エラーがあれば 行N: 内容 が出ます。
  4. [OK]で保存し、セッションを起動し直してから ① で再度デモ送信して、応答が変わったことを確認する。
うまくいかないとき
・応答が返らない → セッションが起動しているか/スクリプトが有効化(トラスト)されているか(ステップ 1)を確認。未トラストだと Script ステップはスキップされ、ログに「スクリプト未許可のためスキップ」と出ます。
・カウンタが増えない → State の読み書きキーが一致しているか確認。セッションを停止すると State はクリアされます。
VAL:READ\r\n 全体に反応してしまう → ReceivedText.Trim() で末尾の改行を落としているか確認。

自分のケースへの当てはめ方

自分の機器の「動的な応答」を、どう書くかの判断の糸口です。

やりたいこと書き方
固定値・長さ・受信エコーなど宣言的な応答スクリプトではなく送信ステップのテンプレート{..})が簡単(→ オートメーション
リクエストのたびに違う値(連番・センサ値)を返すState に値を持って読み書き(本チュートリアルの seq
受信内容で応答を分岐するReceived / ReceivedText を見て if / switch で組み立て
状態機械(起動フロー・ロック状態など)を再現するState に現在状態を持ち、受信で遷移させる(→ コード例3
構造体フィールドで分岐・計算するプロジェクトに構造定義があれば Parsed.Fields でフィールド値を参照(→ コード例4
応答にチェックサムや複数バイト列の連結が要るHex("AA 01") でバイト列化し Concat(a, b, …) で連結
ヒント:スクリプトが重い処理で固まらないよう、タイムアウト(既定 1000ms・ステップ単位で上書き可)が効きます。応答サイズは 1 MiB が上限です。実行時の例外やタイムアウトは当該応答だけスキップしてログに残り、アプリは落ちません(→ 実行時の動作)。

次に読む