# アクション

> アクションの編集画面。識別と置き場所、スクリプトの区画、スクリプトが読み書きできるもの、すべての検証のメッセージ、実行の動詞とその記録、最後の実行を誰が呼び出したか、何がアクションを実行するか、有効化と無効化、複製と削除、そして実行の上限。

アクションは、アドレス空間を読み、内部値の変数とタグに書き込む JavaScript の文の集まりです。アクションは Logic の書き込みの道すじです。手動（実行）、[トリガー](logic-triggers)、それにつないだダッシュボードの[ボタン](component-button)、あるいは Process の実行の開始時か完了時に [Logic のアクションの指令](process-actions#logic-action-commands)から走ります。Logic の欄の Actions の節でアクションを選ぶか、そのフォルダーまたは節の最上位の表でその行の**開く**を押すと、その編集画面が開きます（新規… > 新しいアクションで作ったアクションは開かれず、その表で強調表示されます。[作成と複製](logic#creating-and-copying)を参照）。読むには **Logic の表示**、編集するには **Logic の構成**、実行するには **Logic の操作**が必要です。

見出しは、アクションの印、名前、「手動で実行するか、トリガーが実行してくれるスクリプトです。変数やタグに書き込みます。」の行、そしてアクションが切られている間は**無効**のピルを見せます。入力の欄には識別とスクリプトと**使っている場所**が、右側の帯には最後の実行が入ります。

## 識別 {#identity}

| 項目 | 意味 | 受け付ける値と既定 | 効果 |
| --- | --- | --- | --- |
| 名前 | 表示の名前で、実行が記録されるときの見出しでもあります。 | 自由記述。新しいアクションは `ActionN` です。空の名前は `Action` として保存されます。 | キーを押すたびに自動で保存されます。アクションは識別で参照されるので、名前を変えても何も書き換わらず、何も壊れません。 |
| 置き場所の行 | そのアクションがどこに入っているかを、言葉で述べます。`Actions / Startup / Walk setpoint` のようになります。 | 読み取り専用。 | アクションにアドレスはありません。この行は場所であって、式に入力するものでは決してありません。変えるには、ツリーの**移動先**を使います。 |
| 説明（任意） | 自由記述です。 | 既定は空です。 | ありません。 |

## スクリプト {#script}

スクリプトのカードは[スクリプトの編集画面](logic-expressions#the-script-editor)そのもので、次の助言が付きます。「JavaScript の文（let、if、for、関数）です。内部値の変数は代入で書き込み（Variables.Setpoint = 21.5;）、タグは Connector.Driver.Device.Tag.Write(value) で書き込みます。読み取りに品質の関門はないので、.State() を確かめ、計算の前に ?? で欠測を守ってください。」スクリプトはキーを押すたびに自動で保存され、0.5 秒ごとに検証されます。メッセージは助言の下に出ます。検証がスクリプトを実行することはありません。

スクリプトは、式と同じ[アドレス空間](logic-address-space)と同じ[読み取りの面](logic-expressions#the-read-surface-of-a-point)の上に書く、ふつうの厳格モードの JavaScript（局所変数、`if`、繰り返し、関数、`Math`、`Date`、`JSON`）です。違いは 2 つあります。読み取りに品質の関門は**ありません**。欠測は `null` として読まれるので、`.State()` を確かめ、計算は `??` で守ってください。悪い読み取りを扱うことこそ、アクションの役目です。そしてスクリプトは、式の変数を読めます（その公開された値です）。それを参照できないのは式だけです。

現在値のポイント（タグ、変数、警報、Process のチャンネル）は値ではなくポイントそのものなので、値はメンバーを通して読んでください：`if (Variables.Start.ValueToBool())`、`Variables.Level.ValueToReal() > 80`、`if (Alarms.High.IsActive)`。`if`、`while`、`do`、`for`、`?:` の条件、`!`、`&&`、`||` の項、比較、算術（`+=` などの複合代入を含む）に裸で書くと、常に真とみなされるか、値が決して読まれません。`if (Variables.Start) { … }` は、変数の値にかかわらず、実行のたびにそのブロックを実行してしまいます。検証はこれを拒否し、代わりに読むメンバーを示します（[検証が拒否するもの](#what-validation-refuses)）。関数に渡したポイントと、ポイントのメンバーは、書いたとおりのままです。裸のポイントに対する `switch` やポイントを示す `case`（`.Value` で読みます）、`??` の左側（`.Value` で読みます）、テンプレートリテラルの置き場所 `${…}`（`.ValueToString()` で読みます）も同じです。変数への複合代入（`Variables.Total += 1`、`-=`、`||=` など）は、書き込む前に変数をポイントとして読むので、正しく動く単純な代入を示して拒否されます：`Variables.Total = Variables.Total.ValueToReal() + 1`。

### アクションが書き込めるもの {#what-an-action-may-write}

現場を変えるのはちょうど 2 つの書き方で、どちらも記録される[書き込みの経路](logic#writing-values)を通ります。

| 書き込み | 書き方 | 相手 |
| --- | --- | --- |
| 内部値への代入 | `Variables.Setpoint = 21.5;` または `Variables.Forno.Setpoint = 21.5;` | 内部値の変数です。フォルダーの段も含みます。値はその変数の出力の型に合わせられ、その変数が保持する設定なら保持の印が付きます。これはその場で効くので、同じスクリプトの次の行は、いま書いたものを読みます。 |
| タグへの書き込み | `Connector.Simulated.Pump.Cmd.Write(true);` または `…Cmd.WriteAndWait(true);` | 実時間のタグです。値は工業値で、機器が受け取る生値まで、そのタグの[値の変換経路](connector-value-pipeline)を逆にたどります。そのうえで経路は、それをその機器自身の送り出しのキューへ渡します。そこでは、その機器に渡された値が、届いた順に 1 つずつ送られます。 |

タグは 2 つの書き込みの動詞を持ち、スクリプトは答えが要るかどうかで選びます。

| 動詞 | 引数 | 省いたとき | 返すもの | 機器を待つか |
| --- | --- | --- | --- | --- |
| `Write(value)` | 値です。 | 省けるものはありません。 | 何も返しません。 | 待ちません。値は機器のキューへ渡され、次の行が走ります。 |
| `WriteAndWait(value, timeoutMs, continueOnFailure)` | 値、それを何ミリ秒待つか、失敗したときに続けるかどうかです。 | `timeoutMs`：代わりにドライバー自身の待ち時間を使います。`continueOnFailure`：`true`。 | 機器がその値を受け取ったときだけ `true`、そのほかは `false` です。置き換えられた値も `false` です。 | 待ちます。受け取りの知らせが届くか、待ち時間が尽きるまでです。 |

待つのはアクションのレーンの上なので、遅い機器が足止めするのはその後ろのアクションだけですし、待っても書き込みが取り下げられることはありません。時間内に確認できなかった値はまだ道中にあり、届いたときにその知らせが記録されます。答えはそのとおりに述べます。「「Cmd」は 2000 ms 以内に確認できませんでした。値はまだ装置へ送信中です。」`continueOnFailure` を省いたか `true` のときは、動詞が `false` を答えてスクリプトは続きます。`false` のときは、その行で実行が終わり、相手と理由を名指しします。

**書き込みだけで実行が止まることは、決してありません。**何かが送られる前に拒まれた書き込みも、機器が拒んだり答えなかったりした書き込みも、その書き込みだけが失敗します。スクリプトの残りは走り、その実行は「書き込みが失敗した、成功した実行」として終わります。巻き戻しもありません。失敗した書き込みの前に適用されたものは適用されたままですし、記録はそれぞれの書き込みを、出どころをアクションとして古い値から新しい値へという形で見せます。拒否が読まれる理由は次のとおりです。

- 「「X」への書き込みを拒否しました：値が有限ではありません。」（NaN か無限大）
- 「「タグ」は connector に実体化されていません。」（無効な機器や、Connector が運んでいないタグ）
- 「「値」を「タグ」のために変換できませんでした。」
- 「「X」は「`<holder>`」によって書き込みから守られています。」（レシピの実行やインターロックがその信号源を押さえています）
- 「「Cmd」は書き込まれませんでした。機器が接続されていません。」（その機器が落ちていると既に分かっているので、そこへは何もキューに入りません）
- 「「タグ」への書き込みに失敗しました。Connector が動いていません。」
- 「変数「X」は見つかりませんでした。」と「「Variables.X」は式の変数です。計算される値なので、書き込むことはできません。」
- 拒否が電線から来たときは、機器やそのドライバーが答えたことを、その言葉のままに。

同じタグへの新しい書き込みが機器のキューの中で追い越した値は、失敗では**ありません**。「「Cmd」は送信前に新しい値に置き換えられました。」が起きたことそのもので、機器に伝えられるのは新しいほうの値です。これはどこにも報告されません。報告される先は `WriteAndWait` だけで、この値は出ていったほうではないので `false` を答えます。

実行を終わらせるのは、スクリプト自身のエラー（構文の誤り、何も指さないアドレス、例外）、書き込みが失敗した `WriteAndWait(…, false)`、そして[実行](#run)にある実行の上限です。

実行そのものがどの失敗を伝えられるかは、誰が待ったかで決まります。何かが送られる前に拒まれた `Write` は、スクリプトがまだ走っているうちに拒まれるので、実行がそれを数え、操作ログ帯がそう述べます。そのあとで機器が拒んだ `Write` は、実行が終わってからずっと後に答えが返るので、それを運ぶのはそのアクションの名前の下の[イベント](events)のコンソールであって、実行ではありません。`WriteAndWait` は答えを待ったので、答えがどちらに転んでも数えます。

## 検証が拒否するもの {#what-validation-refuses}

| メッセージ | 原因 |
| --- | --- |
| 「スクリプトは必須です。」 | 編集画面が空です。 |
| 「スクリプトのエラー：…」 | 文字が解析できません。 |
| 「アドレスは静的でなければなりません。「Root」は、計算した区切りで参照されています。」 | `Variables[name]` など、リテラルでない区切り。 |
| 「アドレス「…」が不完全です。」 | ポイントを名指しするには参照が短すぎる。 |
| 「「X」は見つかりませんでした。」 | 読み取り、または代入の相手が何にも解決しない。 |
| 「「X」は値です。.Value を付けずに読んでください。」 | 値としてスクリプトに入るポイントで、現在値のポイントのメンバー（`.Value`、`.ValueToBool()`、`.State()` など）を読んだ（`System.StartTime.Value`、`Process.Oven_soak.Oven_1.Procedure.Running.ValueToBool()`）。メッセージは書かれたメンバーの名前を示す。 |
| 「「X」は値ではなくポイントそのものです。条件として使うと常に真とみなされます。X.ValueToBool() で値を読んでください。」 | 真偽を調べる場所に裸で書いた現在値のポイント：`if`、`while`、`do`、`for`、`?:` の条件、または `!`、`&&`、`\|\|` の項（`if (Variables.Start)`）。警報ではメッセージは `.IsActive` を示す。 |
| 「「X」は値ではなくポイントそのものです。比較や計算ではその値は読まれません。X.ValueToReal() で値を読んでください。」 | 比較や算術に裸で書いた現在値のポイント（`Variables.Level > 80`、`total += Variables.Level`）。`==`、`!=`、`===`、`!==` ではメッセージは `.Value` を、警報では `.IsActive` を示す。裸のポイントに対する `switch`、またはポイントを示す `case` には、`.Value` のメッセージが出る。 |
| 「「X」は値ではなくポイントそのものです。決して空にならないので、?? は常にそれを残します。X.Value で値を読んでください。」 | `??` の左に裸で書いた現在値のポイント（`Variables.Level ?? 0`）。警報ではメッセージは `.IsActive` を示す。 |
| 「「X」は値ではなくポイントそのものです。文字列に埋め込んでも値は表示されません。X.ValueToString() で値を読んでください。」 | テンプレートリテラルの置き場所 `${…}` に裸で書いた現在値のポイント。警報ではメッセージは `.IsActive` を示す。 |
| 「「+=」は書き込む前に X を値ではなくポイントそのものとして読みます。X = X.ValueToReal() + 1 のように書いてください。」 | 変数への複合代入：`+=`、`-=`、`*=`、`/=`、`%=`、`**=`、ビット演算のもの、`&&=`、`\|\|=`、`??=`。メッセージは書かれた演算子と、その意味どおりに動く単純な代入を、右辺を書かれたとおりにして示す。算術では `.ValueToReal()`、ビット演算では `.ValueToInteger()`、`&&=` と `\|\|=` では `.ValueToBool()`、`??=` では `.Value`。 |
| 「変数そのものに代入してください：Variables.Name = value。」 | `=` の左側がメンバーを指している、または変数のアドレスより深くたどっている。 |
| 「「X」は式の変数です。計算される値なので、書き込むことはできません。」 | 式の変数に代入している。 |
| 「「Connector」のポイントは読み取り専用です。タグには Connector.Driver.Device.Tag.Write(value) で書き込んでください。」 | Variables 以外の最上位に代入している（Process には専用のメッセージがあります）。 |
| 「Process のポイントは、アクションでは読み取り専用です。実行コマンドや、用意のための書き込みは、スクリプトでは行えません。」 | Process のポイントに代入した、またはそれに対して `Write` を呼んだ。 |
| 「Process の実行コマンドは、アクションからは呼び出せません。コマンドのポイントは、読み取り専用の準備状態を公開します。」 | `Process.<Model>.<Unit>.Commands.Start()` などのコマンドのポイントを呼んだ。コマンドのポイントは、その指令が今実行できるかどうかを読むだけのものです。 |
| 「++ と -- の演算子は、ポイントには使えません。Variables.Name = Variables.Name.ValueToReal() + 1 のように書いてください。」 | `Variables.N++`。 |
| 「Write(value) はタグへの書き込みです：Connector.Driver.Device.Tag.Write(value)。」 | タグのアドレスではないものに対して `Write` を呼んだ。この一文は使われた動詞を名乗るので、`WriteAndWait` には、両方の箇所にその名前が入った同じ一文が出ます。 |
| 「WriteAndWait の待ち時間はミリ秒の数値にしてください：WriteAndWait(value, 2000)。」 | 2 つ目の引数が書かれていて、数値ではない。 |
| 「WriteAndWait の失敗時に続行するかは true か false にしてください：WriteAndWait(value, 2000, false)。」 | 3 つ目の引数が書かれていて、`true` でも `false` でもない。 |
| 「WriteAndWait の引数は最大三つです：値、ミリ秒の待ち時間、失敗時に続行するかどうか。」 | 4 つ目の引数がある。 |

`WriteAndWait` のこの 3 つのメッセージは、書かれた文字そのものを見て判断します。読むだけでは決められない引数（変数や、計算される式）は、その呼び出しが実際に行われるときに判断され、そのときは同じ一文がその行で実行を終わらせます。

JavaScript の局所変数（`let x = 1; x = x + 1;`）は、これらの規則に触れません。

## 実行 {#run}

**実行**は、開いているアクションの上のバーの先頭の動詞です。**Logic の操作**を持つロールにだけ描かれ、ステーションが読み取り専用の間は「ランタイム停止中」で灰色になります（エンジニアリングロックはこれを灰色にしません。実行することは操作であって、構成ではないからです）。そのロールが Logic の構成も持つときは、編集画面の下書きが先に保存され、保存された定義が実行されます。持たないときは、保存されている定義が実行されます（入力欄が読み取り専用なので、2 つが食い違うことはありません）。

実行はどれも、**アクションのレーン**の上で、新しいスクリプトのエンジンを使って走ります。ですから実行と実行の間に状態は漏れませんし、2 つのスクリプトが同時に走ることもありません。頼まれた順に、1 つずつ走ります。このレーンは 100 ms の周期ではありません。式、警報、確認、そしてほかから届く書き込みは、スクリプトが走っている間も進み続けるので、周期が 1 つのスクリプトの途中に入り込むことがあり、同じスクリプトの中の 2 つの読み取りが別の周期を見ることもあります。その代わり、機器を待って止まるアクションが遅らせるのは、その後ろに並んだアクションだけで、ステーションのほかの何も遅らせません。

実行は 500 ms（「スクリプトを中止しました：500 ms の制限時間を超えました（無限ループの可能性があります）。」）、100,000 文、4 MB のメモリを超えると中止され、そのたびに超えた上限を名指しするメッセージが出ます。この 500 ms が測るのはスクリプトが走っている時間です。`WriteAndWait` が機器を待っている間は数えるのを止め、あとから続きを数えるので、正しいスクリプトが遅い設備のせいで責められることはありません。無効なアクションは走らずに失敗します（「このアクションは無効です。」）。ランタイムが止まっているときは「ロジックのランタイムが動いていません。」と答えます。

操作ログ帯は、すべての書き込みが通ったときは「12 ms で正常に実行しました。」、スクリプト自身は終わって書き込みが通らなかったときは「12 ms で正常に実行しました。書き込み 1 件が失敗しました：Cmd：「Cmd」は書き込まれませんでした。機器が接続されていません。」（2 件目の失敗からは、その後ろが「書き込み 3 件が失敗しました。最初は：Cmd：「Cmd」は書き込まれませんでした。機器が接続されていません。」になります）、実行そのものが悪く終わったときは「実行に失敗しました：`<reason>`」と伝えます。

失敗した実行は[イベント](events)のコンソールに届き、そのアクションの名前と、それを呼んだもの（動詞なら手動、ボタンなら Dashboard、エージェントなら MCP、周期による実行ならそのトリガーの名前、Process の実行なら `Process · <unit> · <action>`）とともに記録されます。最初は警告として記録され、同じ原因で失敗し続けている間は要約され、回復したときにもう 1 行残ります。成功した実行がそこに書く記録は、その回復の 1 行だけです。拒まれた書き込みも同じやり方で、同じ名前の下でコンソールに届きます。そこで名指しされるのが実行ではなくアクションなのも同じ理由です。機器の答えは実行が終わって数秒あとに届くことがあり、そのときには失敗させるべき実行が残っていないからです。ただ成功した実行と、そこで書き込まれたすべての値は、[詳細記録](events-console#detailed-capture)にだけ書かれます。周期のたびに書き込むスクリプトは値を何千も生みますし、それで埋まったコンソールでは、誰もステーションの知らせを読めないからです。

最後の実行の帯は、そのアクションが最後に走った時刻（あるいは「まだ実行していません。」）、「所要時間：N ms」、誰が呼び出したか（下で説明します）、最後のエラーを注意の形で、その実行で失敗した書き込みをそれ専用の注意の形で（操作ログ帯が運んだのと同じ一文なので、成功しながら設定値をそのままにしてしまった実行は、ここでもそう述べます）、そして次の覚え書きを見せます。「実行はアクションのレーンで、一度に 1 つずつ処理されます。イベントのコンソールに届くのは失敗した実行だけで、成功した実行と、そこで書き込まれたすべての値は詳細記録のために保たれます。トリガーは、決めた周期でアクションを呼び出します。」この帯は動いているアクションのものです。自動保存でこれが空になることはありませんが、ランタイムの再起動では空になります。

### 呼び出し元 {#fired-by}

所要時間の下の「呼び出し元: …」の行は、最後にアクションを実行したものを言います。

| 実行したもの | 行の中身 |
| --- | --- |
| このページの**実行** | 「呼び出し元: このページからの実行」 |
| ダッシュボードのボタン | 「呼び出し元: ダッシュボードのボタン」 |
| エージェント（`action_run`） | 「呼び出し元: エージェント」 |
| トリガー | トリガーの名前です。「呼び出し元: Heater off when too hot」。 |
| Process の実行の Logic のアクションの指令 | ユニットとアクションのまとまりです。「呼び出し元: Process · Oven 1 · After soak」。 |

## 使っている場所 {#used-by}

**使っている場所**のカードは、そのアクションを実行する、ステーションの中のすべてを並べます（「このアクションを実行する、ステーションの中のすべてです。」）。アクションを開いたときに読まれ、それを呼び出す Process の指令、それに結び付いたトリガー、それを実行するダッシュボードのボタンが 1 つにつき 1 行です。

| 種類 | 場所 | 名前 |
| --- | --- | --- |
| **Process の指令** | モデルです。 | アクションのまとまりと時点です。`After soak · 完了`。 |
| **トリガー** | 空です。 | フォルダーの下のトリガーです。 |
| **ボタン** | ダッシュボードです。 | ボタンの表示文字です。 |

何もなければ「このアクションを実行するものは、ステーションの中にありません。」と出ます。参照を読めないときは「このアクションはまだ使われている可能性があります。削除や名前の変更をする前に、もう一度お試しください。」と出て、**もう一度試す**が付きます。カードは読むだけなので、どのロールにも見え、ステーションがエンジニアリングでロックされている間も見えます。アクションを無効にしたり削除したりする前に読んでください。アクションがなくなった Process の指令は何も実行せず、自分のページでそう言います。

## 呼び出した実行を読む {#reading-the-run-that-fired-it}

Process の実行が完了時に呼び出したアクションは、その実行をユニットの [LastRun](process-addresses#lastrun) の枝の下で読みます。アクションが走るときには、枝はもうその実行を持っています。開始時に呼び出したアクションは、始まる実行のメタデータを `Procedure.Metadata` の下で読みます。Process のポイントは、変数と違い、後ろに `.Value` を付けないそのままの値として読まれます。実行が持っていないポイントは `null` と読まれるので、確かめてから使います。

```js
if (Process.Oven_soak.Oven_1.LastRun.Verdict === "Ok") {
  Variables.Last_serial = Process.Oven_soak.Oven_1.LastRun.Metadata.Serial;
}
const peak = Process.Oven_soak.Oven_1.LastRun.Evaluations.Soak_check.Temperature_in_range.MaxValue;
if (peak !== null) {
  Variables.Last_peak = peak;
}
```

アドレスは、スクリプトのどのアドレスとも同じく、まるごと書きます。変数からアドレスを組み立てることはできません。

## コマンド {#commands}

| コマンド | 何をするか | 灰色になるとき | 描かれないとき |
| --- | --- | --- | --- |
| 実行 | 下書きを保存して（Logic の構成があるとき）、実行を 1 回キューに入れます。 | ランタイムが停止しているとき。 | ロールが Logic の操作を持たないとき。 |
| 有効化／無効化 | 有効の印を切り替えます。無効なアクションは実行を拒み、それに結び付いたトリガーをすべて何もしない状態にし、それを呼び出す Process の指令は毎回拒まれて要確認になります。 | ステーションがロックされているか読み取り専用のとき。 | ロールが Logic の構成を持たないとき。 |
| 複製 | そのアクションを、空いている名前で Actions の最上位に写し、その複製は開かずに最上位の表で強調表示します（[作成と複製](logic#creating-and-copying)を参照）。複製には実行の記録がなく、結び付いたトリガーもありません。 | ステーションがロックされているか読み取り専用のとき。 | ロールが Logic の構成を持たないとき。 |
| アクションを削除 | 「「X」を削除しますか？アクションが取り除かれます。 この操作は元に戻せません。」と尋ねます。それに結び付いていたトリガーは、削除されずに結び付きが外れます。それを実行する Process の指令は残り、何も実行せず、要確認になります。 | ステーションがロックされているか読み取り専用のとき。 | ロールが Logic の構成を持たないとき。 |

Actions の最上位のパネルは、すべてのアクションを、値の列にその状態とともに並べます。「有効 · 最後の実行 `<when>`」で、最後の実行が失敗していれば「· エラー」が、実行が成功してその書き込みが通らなかったときは「· 書き込み失敗」が付き、切られていれば「無効」です。

## アクションがしないこと {#what-actions-do-not-do}

Process の実行コマンドを送りませんし、手順を用意しませんし、実行のコメントも足しません（それらのポイントは、読み取り専用の準備状態と用意の値です）。警報を確認しません。並行して走りません。実行と実行の間に状態を残さず、関数も共有しません。そして、値の変化や条件で発火することはありません。実行、トリガーの周期、ダッシュボードのボタン、エージェント、Process の実行の指令だけがアクションを走らせます。Process の実行は、呼び出したアクションを決して待たず、アクションの結果が実行記録に書かれることもありません。
