# MQTT

> 外部の MQTT ブローカーを通じて通常の MQTT か Sparkplug B で発行する機器を扱い、ブローカーをその下のすべての機器が共有するノードとして持ちます。ステーション自身のデータをブローカーに発行することもできます。

MQTT ドライバーは、**外部の MQTT ブローカー**を通じて発行する装置のタグを読み書きします。ゲートウェイ、無線センサー、Node-RED のフロー、Sparkplug B を話すエッジノードなどです。ステーションはそのブローカーのクライアントで、タグが読むトピックを購読し、あなたが行った書き込みを発行します。また、どのブローカーも、その[発行のカード](#the-publish-card)に載せたタグ、Logic 変数、警報というステーション自身のデータを、JSON または Sparkplug B で発行できます。ステーションはクライアントでブローカーではないので、装置とステーションはどちらもブローカーに接続します。ブローカーがないときは、ステーション自身の [MQTT ブローカー](connector-mqtt-broker) をそのブローカーにでき、オンにするとこのドライバーにブローカー **このステーション** が加わります。別のブローカーを用意するしかたは[ブローカーをインストールする](#installing-a-broker)にあります。

ブローカーの後ろにある機器はすべて同じ接続を使うため、構成は **ドライバー → ブローカー → 機器 → タグ** です。**ブローカー**は応答する場所とステーションのサインイン方法を一度だけ持ち、その下の各**機器**は発行のしかた（**通常の MQTT** か **Sparkplug B**）と自分のタグだけを持ちます。2 つのプロトコルは 1 つのブローカーの下に並んで共存します。値は定周期読み出しではなく購読で届くため、MQTT の機器にもタグにも読み出し周期はありません。

## ドライバーのページ {#the-drivers-page}

MQTT ドライバーのページは、機器ではなくブローカーを並べます。ヘッダーの下の文は「各ブローカーは外部の MQTT ブローカーへの接続 1 本です。応答する場所とステーションのサインイン方法を設定してから、そのブローカーを通じて発行する機器を追加します。」です。**MQTT ブローカー**の表は、タイトルの右に**ブローカーを追加**を持ち、ブローカーごとに 1 行を並べます。**名前**、**アドレス**（`mqtt://host:port`、TLS では `mqtts://host:port`。ブローカーにホストがない間は空）、**機器**、**状態**（点と、ブローカーの状態に機器の様子を添えたもの。たとえば「オンライン · 機器 2 台中 2 台がオンライン」）、続いて**開く**と、Connector の構成を持つロールには**有効化**、**無効化**、**削除**です。空の表には「ブローカーはまだありません。」と出ます。ステーションを拒んだブローカーは、名前のあとにツリーと同じ赤い印が付き、ページの見出しの下にはエラーのあるブローカーが名前で挙がります（「エラーのあるブローカー 1 台：Plant」）。

ツリーでは、ドライバーの行が 2 つの階層を数え（「ブローカー 1 台 · 機器 2 台」）、ページのヘッダーはブローカーの数をかっこに入れて示します。欄の新規…メニューでは、MQTT の項目に**新しいブローカー**があり、そのあとに各ブローカーが、それぞれの**新しい機器**と機器を持って並びます。ドライバーの行の右クリックメニューには**ブローカーを追加**があります。ドライバーのバーに機器検出はありません。スキャンは 1 つのブローカーで受信するので、ブローカーのページにあります。追加したドライバーは、ブローカーがあってもなくても、ツリーのドライバーのフォルダーの下に並びます。

## ブローカー {#the-broker}

**ブローカーを追加**、新規…メニューの**新しいブローカー**、ドライバーの行の右クリックメニューは、「新しいブローカー」（次は「新しいブローカー 2」）という名前のブローカーを無効の状態で作ります。ステーションが接続する前に接続先を入力できるようにするためです。操作ログ帯もそう伝えます：「無効のブローカー「新しいブローカー」を追加しました。応答する場所を入力してから、有効にしてください。」ブローカーは開きません。ドライバーのページの MQTT ブローカーの表で強調表示され、新規…や右クリックからはそのページに画面が移ります。行の**開く**かツリーのその行で開くブローカーのページは、「1 つの MQTT ブローカーです。応答する場所と、ステーションがサインインする方法を設定します。その下のすべての機器がこの接続を共有します。」という文と、ヘッダーに状態、その点、アドレスを示します。見出しの下には、自分のエラーがあるブローカーの機器が名前で挙がります（「エラーのある機器 1 台：Pump」）。ブローカー自体にエラーがあるあいだは、機器はブローカーを指します。機器のページは「この機器はブローカー Plant を経由しますが、そのブローカーにエラーがあります。理由はブローカーのページにあります。」と示します。

### ブローカーへの接続のカード {#the-broker-connection-card}

| 項目 | 意味 | 既定値 |
| --- | --- | --- |
| **名前** | ツリーと、その下の各機器に表示されるブローカーの名前です。アドレスには決して入りません。空にすると拒否されます：「ブローカーの名前は必須です。」 | 「新しいブローカー」 |
| **ホスト** | ブローカーのホスト名か IP アドレスです。アドレス全体を貼り付けられます。前に `mqtt://` か `tcp://` があれば通信方式は平文、`mqtts://`、`ssl://`、`tls://` なら TLS になり、ホストの後ろのポートは**ポート**に入り、ホストはそれだけで保存されます。ほかのスキーム、パス、サインイン情報がアドレスに書かれていると枠の下で拒否され、各項目は元の値を保ちます：「「http://192.168.0.20」はブローカーのアドレスではありません。ブローカーのホスト名か IP アドレスを、必要なら mqtt://host:port や mqtts://host:port の形で書いてください。」 | 空 |
| **ポート** | ブローカーが応答する TCP ポートで、1 から 65535 までです。 | 1883 |
| **通信方式** | チェックボックスの **TLS を使う**です。オンにすると接続は暗号化され、ブローカーの証明書が Windows の証明書ストア、または[現場設備へのアクセス](#equipment-access)で承認された証明書で確かめられます：「暗号化されます。ブローカーの証明書を Windows が信頼しているか、MQTT ドライバーのページの「現場設備へのアクセス」で承認されている必要があります。」オフにすると注記が「暗号化されません。値とパスワードは平文で送られます。」と警告します。切り替えると、1883 のままのポートは 8883 に、その逆にも移ります。ほかのポートはそのままです。 | オフ |
| **ユーザー名**、**パスワード** | ステーションがブローカーにサインインするアカウントです。ユーザー名が空なら匿名でサインインします。パスワードは機器の資格情報と同じく保存時に保護され、アドレス、診断ログ、エージェントの応答には決して現れません。 | 空 |
| **クライアント ID** | ステーションのセッションがブローカー上で持つ名前で、制御文字を含まない 1 から 128 文字です。ブローカーはクライアント ID ごとに 1 つのセッションを保ち、2 つあれば古い方を切るため、クライアント ID はこのステーションのブローカー 1 つだけのものです：「クライアント ID「GanterLab01」はすでにブローカー「Plant」のものです。同じクライアント ID の 2 つのセッションは互いをブローカーから追い出すため、別の ID を選んでください。」2 台のステーションが 1 つのブローカーを共有するときは、それぞれが自分の ID を持ちます。 | `GanterLab` と 16 進数 14 桁（自動生成） |
| **キープアライブ (s)** | ほかに何も流れていないとき、セッションが生きていることを示す間隔です：「キープアライブは 5 から 3600 秒の間にしてください。」 | 60 |

項目の下の文は、ブローカーとその機器が何をしているか、ブローカーにまだ何が足りないかを伝え、「このブローカーの下のすべての機器と、ステーションがこのブローカーに発行するものがこの接続を共有します。最初の有効な機器か発行する行が必要としたときにセッションが開きます。」と続きます。ブローカーが有効な間も、どの項目も編集できます。応答する場所、通信方式、サインイン、クライアント ID、キープアライブの変更を保存すると、ブローカーの下のすべての機器が共有する 1 つのセッションがつなぎ直されます。

### コマンドバー {#the-command-bar}

| コマンド | 何をするか | 灰色になるとき（状況） | 描かれないとき（ロール） |
| --- | --- | --- | --- |
| **有効化** | ブローカーの下の機器にそのセッションを使わせます。ホストのないブローカーは拒否されます：「ブローカーを有効にする前に、そのホスト名か IP アドレスを入力してください。」 | すでに有効なとき、接続がそろっていないとき（「このブローカーの接続設定は、まだ揃っていません。」）、スキャンが動いているとき、またはステーションが変更を拒否しているとき。 | Connector の構成がないとき。 |
| **無効化** | その下のすべての機器についてセッションを閉じます。各機器は自分の有効の選択を保ち、ブローカーを再び有効にすると元の状態に戻ります。 | すでに無効なとき、スキャンが動いているとき、またはステーションが変更を拒否しているとき。 | Connector の構成がないとき。 |
| **ブローカーを削除**（危険色、右側） | 先に確認します。機器のないブローカーでは「このブローカーに機器はありません。」、機器のあるブローカーでは「ブローカーの機器 2 台と、それらのタグもすべて削除されます。」。そのあと MQTT ドライバーが選ばれ、操作ログ帯に「ブローカー「Plant」と、その機器 2 台を削除しました。」と出ます。 | スキャンが動いているとき（「機器検出のスキャンがこのブローカーを使っています。終わるまでお待ちください。」）、またはステーションが変更を拒否しているとき。 | Connector の構成がないとき。 |

ブローカーの行を右クリックすると、**機器を追加**、**有効化**、**無効化**、**削除**が出ます。

### ブローカーの状態 {#broker-states}

ブローカーは自分のセッションを機器とは別に、ツリーの行、ドライバーのブローカーの表、自分のヘッダーで報告します。

| 状態 | 点 | 意味 |
| --- | --- | --- |
| **オフ** | 灰色 | ブローカーが無効です。 |
| **セッションなし** | 灰色 | 有効ですが、それを使う有効な機器がなく、発行のカードも何も発行していないため、セッションは開かれていません。 |
| **接続中…** | 琥珀色 | 機器が有効か、ブローカーが発行していて、セッションが接続中、または切れたあとに再接続中です。 |
| **オンライン** | 緑 | セッションがつながっています。 |
| **接続できません** | 赤 | ブローカーに届かないか、ブローカーがステーションを拒否しました。原因はヘッダーの下にステーションの言語で赤字で、またドライバーの言葉のまま行のエラー表示のツールチップとして、直すべき点の言葉で書かれます。そのポートで何も応答しない、その名前がこのネットワークで知られていない、ユーザー名かパスワードが拒否された、証明書が信頼されていないか[現場設備へのアクセス](#equipment-access)で承認を待っている、別のクライアントが同じクライアント ID でセッションを奪った、などです。ポートを拒否するブローカーや、ユーザー名のないクライアントを拒否するブローカーは、代わりにその設定で示されます ([ブローカーの準備](#prepare-the-broker) を参照)。 |

ツリーの行は状態と機器の数を示し（「オンライン · 機器 2 台」）、カードの下の要約は機器の様子を加えます：「オンライン · 機器 2 台中 2 台がオンライン」「オンライン · オンライン 1 台 · 問題あり 1 台」「セッションなし · 有効な機器なし」。死亡を告げた Sparkplug B の機器は自分だけを通信から外し、ブローカーはオンラインのままです。セッションは MQTT 5 で話し、話せないブローカーには MQTT 3.1.1 に下げます。クリーンな状態で始まり、自分でつなぎ直し、ブローカーが戻らない間は試行の間隔を延ばしていきます。

### 機器を検出のカード {#the-discover-devices-card}

Connector の構成を持つロールにだけ描かれ、「このブローカーでしばらく受信します。名乗り出た Sparkplug B のエッジノードと機器、そしてフィルターに合うトピックを集めます。」という文の下にあります。

| 項目 | 意味 | 既定値 |
| --- | --- | --- |
| **受信時間 (s)** | スキャンが受信する長さで、3 から 60 秒です。 | 10 |
| **トピックフィルター** | 1 台の機器のタグとして並べる通常のトピックです。ワイルドカードの `+`（1 階層）と `#`（その階層と下のすべて。最後にだけ）が使えるので、「例: plant/#」となります。空なら Sparkplug B の機器だけを並べます。 | 空 |

**機器検出**はスキャンを始め、動いている間は**キャンセル**に変わります。項目の下の文はスキャンが何をするかを伝え（「192.168.0.20:1883 を 10 s 受信します。」）、進行の文は時間枠を数えます（「192.168.0.20:1883 を受信しています · 10 s 中 4 s…」）。スキャンにはブローカーに保存されたホストが必要で、それまでカードは「機器検出の前に、ブローカーのホストを入力して編集が保存されるのを待ってください。」と伝えます。ブローカーの保留中の編集は先に保存され、保存できなければ何も始まりません：「ブローカーの編集を保存できなかったため、機器検出は始まりませんでした。」

スキャンがブローカーで行うこと：

- 自分専用のクライアント ID で接続するため、機器のセッションをブローカーから追い出すことはなく、ブローカーが有効で使われている間でも動かせます。
- すべての Sparkplug B のトピック（`spBv1.0/#`）とトピックフィルターを購読します。Sparkplug B の誕生証明書は保持されないので、それなしで聞こえたエッジノードには名乗り直しを頼みます（`Node Control/Rebirth` のコマンドで、スキャンが発行する唯一のメッセージです）。動いているエッジノードは時間枠の中で応えます。
- 名乗り出た各エッジノードとその後ろの各機器（NBIRTH、DBIRTH）は、メトリックをタグとして持つ候補になり、型は誕生の宣言に従います。エッジノード自身の仕組み（`bdSeq` と `Node Control/…`、`Device Control/…` のメトリック）は除かれ、どのタグも読まないメトリック（配列、データセット、テンプレート、プロパティセット、バイト列、ファイル）も理由ごとに数えて除かれます。聞こえたものの時間枠の中で誕生しなかったエッジノードは、タグなしで示されます。
- トピックフィルターがあると、その下で聞こえた通常のトピックが別の候補 1 つになります。各トピックが 1 つのタグになり、JSON のペイロードではその中の各値が値のパスで 1 つのタグになります（トピックごとに最大 32 個、深さ 4 階層まで、配列は先頭の 8 要素）。数値は Double、`true` か `false` は Boolean、それ以外は String として示されます。一覧は 500 トピックで止まります。発行元が最後のメッセージを保持しているトピックはすぐに聞こえ、それ以外は時間枠の中で発行したときだけ聞こえます。
- 候補を追加するまで何も保存されません。キャンセルすると時間枠はすぐ終わります。

結果のカード**検出した機器**は、**発行元**（`group/edge node`、エッジノードの後ろの機器なら `/device` 付き、または「通常のトピック」）、**機器**（追加するときの名前で編集できます。機器 ID かエッジノード ID、またはフィルターの最初のワイルドカードより前の階層）、**結果**（「タグ 12 件中 12 件を選択中」）、そして**機器を追加**を並べます。**機器を追加**はこのブローカーの下にそれらのタグを持つ機器を作ります。ブローカーにすでに設定されている Sparkplug B の識別は「設定済み」と表示され、代わりに**開く**を出します。何も聞こえなかったスキャンは「スキャンの受信中、このブローカーで名乗り出たものはありませんでした。」と伝えます。

すでにある機器は、まだ読んでいないものを自分のページから見つけます。タグのカードの見出しにある**タグ検出**は、ブローカーで 10 秒間聞きます。Sparkplug B の機器は自分のエッジノードだけを聞き、そのノードには誕生を求め、ほかのエッジノードには何も求めません。プレーンの機器は、タグが共有する階層の下のトピック（`plant/oven/temperature` と `plant/oven/count` なら `plant/oven/#`）を聞き、共有する階層がなければブローカーのすべてのトピックを聞きます。出てくるのは、まだタグがない値だけです（[機器のタグを検出する](connector-discovery#discovering-a-devices-tags)を参照）。

### 機器のカード {#the-devices-card}

「各機器は通常のトピック、または Sparkplug B のエッジノードか機器 1 つを読み取ります。接続はブローカーから受け継ぐため、ここでは変更できません。」**機器を追加**は、このブローカーの下に「新しい機器」を有効、通常の MQTT で作り、この表で強調表示して見える位置までスクロールします：「「Plant」に機器「新しい機器」を追加しました。」表は**機器**、**発行元**、**タグ**、**稼働状態**を並べ、各行に開く、有効化、無効化、削除があります。空のときは「このブローカーに設定された機器はありません。」と出ます。自分のエラーがある機器は、名前のあとにツリーと同じ赤い印が付きます。

### 発行のカード {#the-publish-card}

最後のカードは、ステーション自身のデータをこのブローカーに発行します。カードには「値またはその品質が変わるたびに、ステーションは各行を JSON としてこのブローカーの行のトピックに発行し、接続が開くたびにすべての行をもう一度発行します。ブローカーは各トピックの最後のメッセージを、あとから購読する相手のために保持します。ステーションが購読するのは、読み書きにした各行の書き込みトピックだけです。」と書かれています。Connector を閲覧できる人はカードを読め、構成できる人は変更できます。ステーション自身の [MQTT ブローカー](connector-mqtt-broker)のためのブローカー **このステーション** にも同じカードがあります。

| 項目 | 意味 | 既定値 |
| --- | --- | --- |
| **発行の形式** | 行の出し方です。各行のトピックに **JSON** で出す（[発行](#publishing)）か、ステーションをブローカーのエッジノードにして **Sparkplug B** で出します（[Sparkplug B での発行](#publishing-in-sparkplug-b)）。ツールチップは「行の出し方です。各行のトピックに JSON で出すか、Sparkplug B で出します。Sparkplug B ではこのステーションがエッジノードになり、Ignition などの Sparkplug B コンシューマーが自分で見つけます。」です。 | JSON |
| **トピックの接頭辞** | JSON のときだけです。新しい行のトピックを提案するときの上位の階層です。空のときは既定値が表示され、それが使われます。「トピックの接頭辞には + や # を含められず、$ で始めることも、トピックに使えない文字を含めることもできません。」変更しても、一覧にあるトピックはそのままです。 | `ganterlab/` とステーション名 |
| **グループ ID** | Sparkplug B のときだけ、接頭辞の代わりに出ます。このブローカーでのステーションのエッジノードの Sparkplug B のグループで、空のときは `GanterLab` です。`/`、`+`、`#` を含むグループは項目の下で拒否され、直すまでブローカーは保存されません。 | `GanterLab` |

Sparkplug B のとき、タイトルの下の文は代わりに「このブローカーでは、ステーションが Sparkplug B のエッジノードです。Logic の変数と警報はエッジノード自身のメトリックになり、行のある機器はそれぞれ Sparkplug B の機器になります。接続が開いたときと一覧が変わったときにすべてのメトリックを告げ、その後は変わった値と品質を発行します。コマンドから値を受け付けるのは、読み書きにした行だけです。」となり、項目の下の **エッジノード ID（ステーションの名前）** に、発行に使うエッジノードが出ます。

表には **信号源**、**トピック**、**アクセス** がトピック順に並びます。信号源をまだ選んでいない行は「信号源はまだありません」です。行がなければ「このブローカーにはまだ何も発行していません。新しい行で追加してください。」と出ます。ブローカーが無効で一覧に行があるときは「ブローカーが無効のため、有効にするまで何も発行しません。」と注記が出ます。ブローカーの行のどれかが読み書きのときは、「読み書きにした行は、書き込みトピックに発行された値を受け付けます。このブローカーに発行できる人は誰でもそれらの信号源を変更できるため、それらを守るのはブローカーのユーザー名とパスワードだけです。」という 2 つ目の注記が出ます。Sparkplug B のときは「読み書きにした行は、Sparkplug B のコマンド（NCMD、DCMD）から値を受け付けます。このブローカーに発行できる人は誰でもそれらの信号源を変更できるため、それらを守るのはブローカーのユーザー名とパスワードだけです。」と出ます。Sparkplug B のとき、**トピック** の列は **メトリック** になります。タグは機器の後に（`Oven · Temperature`）、変数と警報はエッジノード上の名前で（`Logic/Oven/Setpoint`、`Alarms/High`）表示されます。

| コマンド | 何をするか | 灰色になるとき（状況） | 描かれないとき（ロール） |
| --- | --- | --- | --- |
| **新しい行** | まだ何も発行しない行を追加して選びます。「ブローカーの発行の一覧に行を追加しました。信号源を選んでください。」と出ます。 | 実行記録がステーションを押さえている、またはランタイムが止まっているとき。 | Connector の構成 がないとき。 |
| **行を削除**（危険色） | 選んだ行をすぐに削除します。ツールチップには、その行が発行しているものが出ます。ステーションはその発行をやめますが、ブローカーがそのトピックに保持したメッセージは残ります。 | 行が選ばれていない（「削除する行を一覧で選んでください。」）、実行記録がステーションを押さえている、またはランタイムが止まっているとき。 | Connector の構成 がないとき。 |

行の項目は変わった時点で保存されます。

| 項目 | すること |
| --- | --- |
| **信号源** | 行が発行するものです。「（なし）」に続いて、有効な機器の有効なタグ（「タグ · Simulated · Oven · Temperature」）、値を持つ Logic 変数、警報がフォルダー付きの名前で並びます。機器のコマンドは何も発行しないので出ません。行は信号源を識別で持つので、名前を変えても行はその信号源のままで、信号源の[使用箇所](connector-tags#the-used-by-card)のカードにこの行が並びます。 |
| **トピック** | JSON のときだけです。行が発行する先です。信号源を選ぶと、その名前からトピックが提案されます。タグなら `<prefix>/<device>/<tag>`、変数なら `<prefix>/logic/<folders>/<variable>`、警報なら `<prefix>/alarms/<folders>/<alarm>` で、名前はそれぞれトピックの 1 階層になります（空白、スラッシュ、ワイルドカードは `_` になります）。自由に変更できます。提案されたままのトピックはあとで選んだ信号源に合わせて変わり、自分で書いたトピックはそのまま残り、信号源の名前を変えても変わりません。 |
| **アクセス** | **読み取りのみ** はどの行も最初の状態で、ステーションは行を発行し、そのために何も購読しません。**読み書き** にすると、ステーションは行の書き込みトピック（行のトピックに `/set` を続けたもの）に発行された値も受け付け、信号源に書き込みます（[書き戻し](#writing-back)）。選べるのは、書き込むドライバー上の書き込みアクセスのあるタグか、内部値の変数だけです。それ以外では一覧に灰色で残り、「この信号源は書き込みを受けません。受けるのは書き込みアクセスのあるタグか内部値の変数だけです。」と出ます。警報を選ぶと、行は読み取りのみに戻ります。Sparkplug B のとき、読み書きは書き込みトピックの代わりに、Sparkplug B のコマンドが行のメトリックに送った値を受け付けます。 |

項目の下の文は、行が何を発行するかを伝えます。

| カードの表示 | 出るとき |
| --- | --- |
| 「この行はまだ何も発行していません。信号源を選んでください。」 | 信号源が選ばれていないとき。 |
| 「値またはその品質が変わるたびに、値、品質、時刻を JSON で発行します。」 | 読み取りのみのタグか変数のとき。 |
| 「値またはその品質が変わるたびに値、品質、時刻を JSON で発行し、書き込みトピックに発行された値を信号源に書き込みます。書き込みはブローカーの名前とともに診断ログに記録されます。」 | 読み書きのタグか変数のとき。 |
| 「警報が発生中か、確認済みか、状態、優先度、メッセージ、時刻のいずれかが変わるたびに、それらを JSON で発行します。」 | 警報のとき。 |
| 「この行の信号源はもうないため、この行ではもう何も発行しません。ブローカーは最後に保持したメッセージを残します。」 | タグ、変数、警報が削除されたとき。 |
| 「値またはその品質が変わるたびに、値と品質を Sparkplug B で発行します。」 | Sparkplug B で、読み取りのみのタグか変数のとき。 |
| 「値またはその品質が変わるたびに値と品質を Sparkplug B で発行し、Sparkplug B のコマンドがそのメトリックに送った値を信号源に書き込みます。書き込みはブローカーの名前とともに診断ログに記録されます。」 | Sparkplug B で、読み書きのタグか変数のとき。 |
| 「警報が発生中か、確認済みか、状態、優先度、メッセージのいずれかが変わるたびに、それらを Sparkplug B で発行します。」 | Sparkplug B で、警報のとき。 |
| 「この行の信号源はもうないため、エッジノードはそれを除いて名乗り直します。」 | Sparkplug B で、タグ、変数、警報が削除されたとき。 |

その文の下に、読み書きの行は **書き込みトピック**（行のトピックに `/set` を続けたもの）を表示します。Sparkplug B には書き込みトピックがありません。代わりに 2 つ目の文が、行がどのメトリックとして発行されるかを伝えます。タグなら Sparkplug B の機器のメトリック、変数ならステーションのエッジノードのメトリック、警報ならその名前の下の 5 つのメトリック（Active、Acked、State、Priority、Message）です。

一覧が受け付けない変更は保存されません。理由は行の項目の上に赤字で出て、直せるように変更は画面に残ります。

| カードの表示 | 出るとき |
| --- | --- |
| 「この信号源はすでにこのブローカーに発行しています。発行している行を編集してください。」 | このブローカーの別の行がその信号源を発行しているとき。 |
| 「このブローカーの別の行がすでにこのトピックに発行しています。この行には別のトピックを付けてください。」 | 1 つのブローカーの 2 つの行が同じトピックに発行することになるとき。 |
| 「フィルターには何も発行できないため、トピックに + や # は使えません。」 | トピックにワイルドカードがあるとき。 |
| 「$ で始まるトピックはブローカーのものです。別のトピックを書いてください。」 | トピックが `$` で始まるとき。 |
| 「このトピックは送れません。トピックに使えない文字を含むか、長すぎます。」 | 制御文字があるとき、または MQTT のトピックで送れる長さを超えるとき。 |
| 「書き込みを受ける行には、書き込み先のタグまたは変数が必要です。」 | 信号源より先に読み書きを選んだとき。 |
| 「この信号源には書き込めません。書き込みアクセスのあるタグか内部値の変数を選ぶか、行を読み取り専用のままにしてください。」 | 警報、式の変数、読み取り専用のタグ、何も書き込まないドライバーのタグで読み書きを選んだとき。 |
| 「このトピックとこのブローカーの別の行のトピックは、行が書き込みを受ける /set だけが違います。この行には別のトピックを付けてください。」 | ある行のトピックが、別の行のトピックに `/set` を続けたものであるとき。 |
| 「一覧のこの行はもうありません。読み込み直してからやり直してください。」 | 編集している間に、その行がほかの場所で削除されたとき。 |

何をいつ発行するかは[発行](#publishing)にあります。

## 機器 {#device}

| 項目 | 意味 | 既定値 |
| --- | --- | --- |
| **ブローカー · 固定** | ブローカーの名前とその下のアドレスで、読み取り専用です。機器はブローカーの下に作られ、そこにとどまります。アドレスの位置には「ブローカーのホストはまだ入力されていません」が出ることがあります。「ブローカーがありません」は、機器がブローカーを失って保存できないことを示します：「MQTT ブローカーが必要です。」 | 追加したブローカー |
| **プロトコル** | 機器の発行のしかたです。**通常の MQTT**：「各タグは読み取るトピックを名指しし、JSON のペイロードなら値までのパスも名指しします。」**Sparkplug B**：1 つのエッジノード、またはその後ろの機器 1 つです：「各タグは、エッジノードの誕生証明書（birth）が宣言するとおりにメトリックを名指しします。型はメトリックと一緒に届きます。」切り替えると、アドレスがトピックかメトリックとして読まれるようになるため、各タグを確かめ直します。 | 通常の MQTT |
| **メッセージがないまま許す最長時間（秒）** | 通常の MQTT のみで、0 から 86400、0 は期限なしです：「この時間トピックにメッセージが届かないと、そのタグは読み取り値として扱われなくなります。0 は期限なしで、値が変わったときだけ発行されるトピック向けです。数秒ごとに報告するセンサーのように一定の間隔で発行する機器では値を入れてください。」この範囲の外では機器は保存されません：「メッセージがないまま許す最長時間は 0 から 86400 秒の間にしてください。」Sparkplug B の機器にはありません。死亡証明書が、いなくなったことをすでに伝えるからです。 | 0 |
| **グループ ID** | Sparkplug B のみ。エッジノードが発行するグループで、トピックの `spBv1.0` の次の階層です。「Sparkplug B の機器にはグループ ID が必要です。」 | 空 |
| **エッジノード ID** | Sparkplug B のみ。メトリックを発行するエッジノードで、NBIRTH が名指しするとおりです。「Sparkplug B の機器にはエッジノード ID が必要です。」 | 空 |
| **機器 ID** | Sparkplug B のみ。エッジノードの後ろの機器で、DBIRTH が名指しするとおりです。空のとき（「エッジノード自身」と表示）は、エッジノード自身のメトリックを読みます。 | 空 |

機器は自分のホスト、ポート、資格情報、読み出し周期を持ちません。それらはすべてブローカーのものです。Sparkplug B の各 ID はトピックの 1 階層です：「Sparkplug B の ID には '/'、'+'、'#' を含められません。」ブローカーは Sparkplug B のエッジノードや機器を一度だけ名指しするので、同じブローカーで同じグループ ID、エッジノード ID、機器 ID を持つ 2 台目の機器は拒否されます：「ブローカー「Plant」の Sparkplug B 機器 Plant/Line1/Press1 は、すでに機器「Press」のものです。」通常の MQTT の機器はブローカー上で自分のものを何も名指ししないので、2 台が同じトピックを読んでもかまいません。

ヘッダーが示す基準 URI は実行時のアドレスです：`mqtt://<broker id>?host=…&port=…&tls=0&keepAlive=60&clientId=…&mode=plain`（機器がメッセージがないまま許す最長時間を設定していれば `&silence=…` が付きます）、Sparkplug B の機器では `mode=sparkplug&group=…&node=…` で、エッジノードの後ろの機器には `&device=…` が付きます。ブローカーは表示名ではなく安定した識別で名指しされ、ユーザー名やパスワードは決して含まれません。

MQTT の機器のコピーは同じブローカーにとどまります。Sparkplug B のコピーは、そのブローカーのどの機器も持たない最初の識別を取ります。いちばん内側の ID（機器 ID、エッジノード自身のメトリックならエッジノード ID）に、コピーの名前と同じように番号が付きます（「Press1 (2)」）。

## タグのアドレス {#tag-addressing}

アドレスの項目は、通常の MQTT の機器では**トピック**、Sparkplug B の機器では**メトリック**で、必須です（「このドライバーでは トピック が必須です。」）。通常のタグにはさらに 3 つの項目があります。Sparkplug B のメトリックにはありません。型を自分で持ち、プロトコル自身のコマンドで書き込まれるからです。

| 項目 | 意味 | 既定値 |
| --- | --- | --- |
| **トピック** | タグが読む 1 つのトピックです。例: `plant/line1/temperature`。「タグが読むトピックは 1 つなので、ワイルドカードの + や # は使えません。」 | 必須 |
| **値のパス** | JSON のペイロードの中で値がある位置です。ドットで区切ったメンバーと角かっこの配列インデックスで書きます。例: `sensors[0].value`。名前にドットや角かっこを含むメンバーは、角かっこの中に引用符で書き（`['flow.rate']`）、先頭の `$.` も受け付けます。空のとき（「ペイロード全体」と表示）はペイロード全体を値として読みます。形の崩れたパス：「値のパスは、ドットで区切ったメンバーと角かっこの配列インデックスで書いてください。例: sensors[0].value」 | 空 |
| **コマンドトピック** | 書き込みを発行する場所です。空ならタグは読み取り専用のままで、アクセスが書き込みを含むタグには必要です（「書き込みできるタグには、書き込みを発行するトピックが必要です。」）。ワイルドカードは使えません（「フィルターには何も発行できないため、コマンドトピックに + や # は使えません。」）。装置が報告するトピックで指示を待つことはまれなので、これは別の項目になっています。 | 空 |
| **ペイロードのテンプレート** | 書き込みが発行するものです。書き込む値の位置に `{value}` を引用符の外でちょうど 1 回置いた JSON で、例: `{"setpoint": {value}}`。空のとき（「値をテキストのまま」と表示）は値をテキストで発行します。「ペイロードのテンプレートには、書き込む値の位置に {value} をちょうど 1 回入れてください。」／「ペイロードのテンプレートは JSON で書き、JSON の値が入る位置に {value} を引用符なしで置いてください。」 | 空 |
| **メトリック** | Sparkplug B。誕生証明書が宣言するとおりのメトリック名です。例: `Inputs/Temperature`。 | 必須 |

データ型は Boolean、Int16、Int32、Int64、Float、Double、String、DateTime です。読み取り専用のワイヤーアドレスのプレビューは、トピックに `path`、`command`、`payload` のうち当てはまるものを続けたもの（`plant/line1/temperature?path=sensors[0].value&command=plant/line1/setpoint`）か、メトリック名です。部分を終わらせてしまう文字（`%`、`?`、`&`、`=`、`#`、`+`、空白）は `%XX` と書かれます。

## 読み取り {#reading}

セッションは購読したトピックごとに最後のメッセージを保ち、タグはそこから読みます。

| 状況 | 通常の MQTT のタグ | Sparkplug B のタグ |
| --- | --- | --- |
| 値 | トピックの最後のメッセージです。ペイロード全体をテキストとして、または値のパスにある JSON の値を、タグの型に変換します。 | エッジノードの最後の誕生メッセージかデータメッセージにあるメトリックの値です。 |
| タイムスタンプ | メッセージが届いた時刻です。発行元が保持したメッセージは、ステーションが購読した時点で最初の値になります。 | メトリック自身のタイムスタンプです。 |
| まだ何も届いていない | タグは待ちます。値もエラーもなく、どのドライバーでも最初の応答の前と同じです。発行元が保持しないトピックは、次に発行されるまで値がありません。**メッセージがないまま許す最長時間（秒）**があれば、待つのはステーションが聞き始めてからその時間までで、そのあとタグは読み取りに失敗します。 | 誕生証明書が届くまで同じです。ステーションはエッジノードに名乗り直しを頼みます。 |
| 発行元が黙った、ブローカーはつながっている | **メッセージがないまま許す最長時間（秒）**があれば、トピックにそれより長くメッセージがないタグは読み取りに失敗し、[応答しなかった機器](connector-value-pipeline#the-device-did-not-answer)についてのタグの方針が表示を決めます。次のメッセージで戻ります。時間は最後のメッセージ（保持メッセージを含む）の到着から数えます。0 なら、タグは最後に受け取った値を持ち続けます。 | 当てはまりません。死亡証明書が伝えます。 |
| タグが読めないメッセージ | そのタグの読み取り失敗で、理由が付きます。空のメッセージ、値のパスがあるのに JSON でないペイロード、パスの位置に何もない、型に収まらない値などです。 | そのタグの読み取り失敗です。誕生証明書が宣言していないメトリック、または型に収まらない値です。 |
| ブローカーに接続できない | ブローカーのすべての機器が切断になり、そのタグはどの機器とも同じように失敗します。 | 同じです。 |
| 死亡証明書（NDEATH、DDEATH） | 当てはまりません。 | エッジノードとその機器、またはその機器 1 台だけが、再び誕生するまで値を持ちません。ブローカーのほかの機器はそのまま続きます。 |

Boolean のタグは `true` か `false`、`1` か `0`、`on` か `off` を受け付けます。整数のタグは小数を丸めて受け付け、範囲外の数は拒否します。DateTime のタグは ISO 8601 のテキストか Unix のミリ秒を受け付けます。String のタグはテキストをそのまま、JSON のオブジェクトや配列は生の JSON として受け付けます。符号なしの型の Sparkplug B メトリックは、範囲全体を読めるよう、1 段広い符号付きの型で読みます（UInt8 は Int16、UInt16 は Int32、UInt32 と UInt64 は Int64）。

## 書き込み {#writing}

書き込みは、ほかのどのドライバーとも同じ経路を通ります。書き込み欄は工業値を受け取り、生の値を発行する前に変換段が逆にたどられます（[Connector](connector) を参照）。

| タグ | 発行されるもの | QoS | 書き込みが報告すること |
| --- | --- | --- | --- |
| 通常の MQTT | タグの**コマンドトピック**に値を、テキストのまま（`21.5`、`true`、テキストそのもの、ISO 8601 の日付）、または**ペイロードのテンプレート**に JSON の値として入れて（数値は数値、真偽値は `true` か `false`、テキストは引用符で囲みエスケープして）発行します。 | 1、保持なし | ブローカーが受領を返したら完了。MQTT 5 のブローカーが断ったら拒否で、ブローカーの理由が付きます。受領が時間内に来なければ応答なしです。 |
| Sparkplug B | NCMD（エッジノードのメトリック）か DCMD（その後ろの機器のメトリック）でメトリックを運びます。誕生で与えられたエイリアスと、誕生が宣言した型を使います。 | 0、保持なし。Sparkplug B の仕様がコマンドに求めるとおりです | 送った時点で完了です。エッジノードは新しい値を発行して応え、タグは次にそれを読みます。 |

ブローカーに接続できない間、またはエッジノードや機器が死亡を告げている間は、書き込みは切断として拒否され、何も送られません。誕生証明書が宣言していないメトリックへの Sparkplug B の書き込みは、エッジノードが受け取らないため拒否され、メトリックの型が運べない値も同じです。書き込みは決して保持されません。保持された設定値は、あとから接続するすべての購読者にもう一度適用されてしまうからです。

## 発行 {#publishing}

この節は **発行の形式** が **JSON** のときの説明です。Sparkplug B は次の節にあります。

ブローカーの[発行のカード](#the-publish-card)の各行は、そのトピックに保持メッセージとして QoS 1 で発行されます。使うのはブローカーの機器が使っているのと同じ接続で、ステーションが 1 つのブローカーに 2 本目の接続を開くことはありません。発行のためだけに使うブローカーには機器がいりません。ブローカーは MQTT ドライバーの下に残り、その状態（接続中…、オンライン、接続できません）はこの接続の状態です。有効で一覧に行がある間は発行し、専用のスイッチはありません。

| 信号源 | メッセージ（JSON） |
| --- | --- |
| タグ | `{"value": 21.5, "quality": "good", "timestamp": "2026-10-04T12:30:15.250Z"}`：タグが示す工学値です。`quality` は届いた読み取りでは `good`、届かなかった読み取りでは `bad` で `value` は null、代わりの値、保持した最後の値、届かなくなった読み取りでは `uncertain` です（最後に発行した値が残ります）。時刻は読み取りのタイムスタンプで、UTC です。 |
| Logic 変数 | 同じメンバーです。変数が良好な間は値と `good`、そうでなければ `null` と `bad`。時刻は最後の書き込みか計算のタイムスタンプです。 |
| 警報 | `{"active": true, "acked": false, "state": 1, "priority": "High", "message": "Oven above 80 °C", "timestamp": "2026-10-04T12:30:15.250Z"}`：発生中か、確認済みか、`state` を数値で（0 正常、1 発生中で未確認、2 発生中で確認済み、3 復帰して未確認）、優先度を名前で（Critical、High、Medium、Low）、メッセージ（なければ空）、最後の遷移のタイムスタンプです。[OPC UA サーバー](connector-opcua-served-nodes#where-a-client-finds-each-source)が提供するものと同じです。 |

値はその JSON の型で送られます。数値、`true` か `false`、テキスト、日時は ISO 8601 のテキストです。JSON で表せない数値（NaN、無限大）は `null` で送られます。無効な機器のタグは `null` と `bad` を発行します。

| 状況 | 発行されるもの |
| --- | --- |
| 値か品質が変わる | その行です。1 秒に 5 回確かめます。同じ値をもう一度読んだタグは発行し直しません。タイムスタンプだけでは変化になりません。 |
| 接続が開く、または開き直す | すべての行を、いまの状態で発行します。 |
| ブローカーが止まっている | 何も発行せず、キューで待つものもありません。ブローカーが戻ると、すべての行をその時点の状態で発行します。 |
| ブローカーがメッセージを拒否する、または確認しない | 30 秒後にやり直します。診断ログの Connector の領域、コンポーネント`MQTT publication`に、最初に 1 回、続く間は数分ごとにまとめて記録されます。 |
| 信号源が削除される | それ以上何も発行しません。ブローカーは最後に保持したメッセージを残します。 |
| 行が削除される | それ以上何も発行しません。保持メッセージは、ほかの何かが発行されるまでトピックに残ります。 |

ステーションは発行するものを読むだけで、そのために何かを計算することはありません。タグの最後の読み取り、変数の値、警報の状態です。読み取りのみの行のトピックは購読しないので、ほかのシステムがそこに発行したメッセージがステーションに届くことはありません。読み書きの行は、書き込みトピックで値を受け付けます（[書き戻し](#writing-back)）。エージェントは `mqtt_publish_list` で一覧を読み、`mqtt_publish_upsert` と `mqtt_publish_delete` で変更します（[ツール](agent-tools#connector--configure)）。

```text
mqtt_publish_upsert(brokerId: "<id from mqtt_broker_list>", source: "Connector.Simulated.Oven.Temperature")
mqtt_publish_upsert(brokerId: "<id from mqtt_broker_list>", source: "Alarms.Oven.HighTemperature", topic: "plant/oven/alarms/high")
mqtt_publish_upsert(id: "<id from mqtt_publish_list>", access: "ReadAndWrite")
mqtt_publish_list()
```

## Sparkplug B での発行 {#publishing-in-sparkplug-b}

**発行の形式** を **Sparkplug B** にすると、ブローカーは同じ一覧を Sparkplug B の仕様どおりに、ステーションをエッジノードとして発行します。Ignition の MQTT Engine などの Sparkplug B ホストは、ステーションをそのグループとエッジノードの下に、各機器をその下に、各メトリックを型付きで並べます。手で作るマッピングはいりません。接続は JSON のときと同じく、ブローカーの機器が使っている接続です。

| 名前 | 何か |
| --- | --- |
| グループ | **グループ ID** です。空の間は `GanterLab` です。 |
| エッジノード | ステーションの名前です。`/`、`+`、`#`（Sparkplug B の ID に使えない 3 文字）はそれぞれ `_` になります。 |
| 機器 | タグの行がある Connector の各機器で、同じ規則で名前が付きます。 |
| タグのメトリック | タグの名前で、その機器の上にあります。 |
| 変数のメトリック | `Logic/<folders>/<variable>` で、エッジノードの上にあります。 |
| 警報のメトリック | エッジノードの上の `Alarms/<folders>/<alarm>/` の下に 5 つです。`Active` と `Acked`（Boolean）、`State`（数値で 0 正常、1 発生中で未確認、2 発生中で確認済み、3 復帰して未確認）、`Priority` と `Message`（テキスト）です。 |

名前の中の `/` は `_` になるので、階層になるのはフォルダーだけです。名前が同じになる 2 つの機器や 2 つのメトリックには、元の名前の順に `_2`、`_3` が付きます。行はトピックを持ち続けます。Sparkplug B ではそのトピックには何も発行せず、ブローカーを JSON に戻すと一覧はまたそこに発行します。

| メッセージ | いつ |
| --- | --- |
| NDEATH（エッジノードの死亡） | 接続が開くたびに接続の遺言としてブローカーに渡し、ステーションが落ちたときにブローカーが発行します。ステーション自身も、発行をやめる前に発行します。ブローカーを JSON に戻したとき、無効にしたとき、削除したとき、一覧を空にしたとき、グループやステーションの名前を変えたとき、ランタイムを止めたときです。 |
| NBIRTH（エッジノードの誕生） | 接続が開くたび、ホストが名乗り直しを求めたとき（`Node Control/Rebirth`）、変数や警報が加わった、外れた、名前が変わったときです。エッジノードのすべてのメトリックを今の値で、`bdSeq` と `Node Control/Rebirth` とともに載せます。 |
| DBIRTH（機器の誕生） | 各 NBIRTH の後にすべての機器について、また一覧に加わった機器について送ります。機器のすべてのタグを今の値で載せます。 |
| NDATA、DDATA | 1 秒に 5 回、値か品質が変わったメトリックを送ります。エッジノードに 1 つ、機器ごとに 1 つのメッセージです。 |
| DDEATH（機器の死亡） | 機器の最後の行が一覧から外れたとき、またタグが変わった機器が誕生し直す前に送ります。 |

NDEATH 以外のメッセージはすべて QoS 0 で、保持はしません。シーケンス番号 `seq` は NBIRTH のたびに 0 から始まり、その後のメッセージごとに 255 まで数えて、また最初に戻ります。`bdSeq` は接続を数えるので、1 つの接続の NBIRTH と NDEATH は同じ番号を持ちます。ブローカーが止まっている間、何もキューにためません。

| 信号源の型 | Sparkplug B の型 |
| --- | --- |
| `Boolean` | `Boolean` |
| `SByte`, `Byte` | `Int8`, `UInt8` |
| `Int16`, `UInt16` | `Int16`, `UInt16` |
| `Int32`, `UInt32` | `Int32`, `UInt32` |
| `Int64`, `UInt64` | `Int64`, `UInt64` |
| `Float`, `Double` | `Float`, `Double` |
| `String` | `String` |
| `DateTime` | `DateTime` |
| `Guid`, `ByteString` | `UUID`, `Bytes` |

タグはタグ自身の型で、変数は [OPC UA サーバー](connector-opcua-served-nodes#where-a-client-finds-each-source)が提供する型で発行します（整数は Int64、実数は Double、表示用の値はテキスト）。各メトリックは品質をプロパティ `Quality` に載せます。Sparkplug B ホストが読む慣習で、192 が良好、64 が不確か（読み取りが届かなくなったときは最後に発行した値）、0 が不良（このときメトリックは値を持ちません）です。

| コマンド | ステーションがすること |
| --- | --- |
| NCMD `Node Control/Rebirth` | NBIRTH とすべての DBIRTH を今の値でもう一度発行します。 |
| 読み書きの行のメトリックを指す NCMD か DCMD | [書き戻し](#writing-back)と同じく値を信号源に書き込みます。コマンドが届いた順に、発信元 `MQTT <broker name>` で診断ログに記録されます。 |
| それ以外のメトリック、値のないコマンド、ブローカーが保持して渡したコマンド | 何も書き込みません。診断ログの Connector の領域、コンポーネント`MQTT publication`に記録されます。 |

ステーションは、ホストがいつでも名乗り直しを求められるように、エッジノードへのコマンドは常に聞き、機器へのコマンドはその機器の行のどれかが読み書きの間だけ聞きます。ブローカーを Sparkplug B に切り替えると、ブローカーがエッジノードの遺言を持てるように、その接続は一度開き直します。ブローカーの機器は、ほかの再接続と同じようにこれを乗り越えます。ステーションはエッジノードで、プライマリホストアプリケーションではありません。誕生の前にホストの STATE メッセージを待ちません。

```text
mqtt_broker_upsert(id: "<id from mqtt_broker_list>", publishFormat: "SparkplugB", sparkplugGroupId: "Plant1")
mqtt_publish_list()
```

## 書き戻し {#writing-back}

[発行のカード](#the-publish-card)の行を **読み書き** にすると、その行は書き込みトピック（行のトピックに `/set` を続けたもの）で値を受け付けます。たとえば `plant/oven/setpoint` を読むクラウドのダッシュボードは、新しい設定値を `plant/oven/setpoint/set` に送ります。ステーションはそれを、[Modbus サーバー](connector-modbus-server)や [OPC UA サーバー](connector-opcua-served-nodes)のクライアントが読み書きの行に書き込むのと同じように書き込みます。

| 状況 | どうなるか |
| --- | --- |
| 行を読み書きにした | ステーションは、ブローカーの機器が使っている接続で、その書き込みトピックを QoS 1 で購読し、その接続が開くたびに購読し直します。読み取りのみの行は購読しないので、その書き込みトピックへのメッセージはステーションに届きません。 |
| 書き込みトピックにメッセージが届いた | `{"value": 65}` または値そのもの（`65`、`true`、`レシピ A`）を、届いた順に信号源へ書き込みます。タグにはオペレーターの書き込みと同じ経路で、内部値の変数にはそのまま書き込みます。JSON の数値、`true` か `false`、JSON のテキストは、それが表す値として届きます。JSON でないものはテキストとして読みます。値はほかの書き込みと同じく信号源の型に変換され、その型で表せない値は拒否されます。 |
| メッセージに値がない | 何も書き込みません。空のメッセージ、`null`、`value` のない JSON オブジェクト、配列です。 |
| ブローカーが保持していたメッセージを渡した | 何も書き込みません。ブローカーはステーションが購読したときにそれを渡しますが、1 時間前に置かれた設定値は、いま誰かが送った値ではありません。ステーションが聞いている間に発行された値は、保持メッセージかどうかにかかわらず書き込みます。 |
| 信号源が書き込みを受けなくなった | 何も書き込みません。読み取りのみに戻したタグや、削除した変数です。 |
| 行を読み取りのみに戻した、トピックを変えた、削除した、またはブローカーを無効にした | ステーションはすぐにその書き込みトピックを聞くのをやめ、その間に届いたメッセージは何も書き込みません。 |

診断ログは、オペレーターの書き込みと同じように、各書き込みを Connector の領域に `MQTT <ブローカー名>` という発生元で記録します。書き込まれなかったメッセージは診断ログの Connector の領域、コンポーネント`MQTT publication`に、最初に 1 回、繰り返す間は数分ごとにまとめて記録されます。

守りはブローカーのユーザー名とパスワードだけです。ブローカーが書き込みトピックへの発行を許した相手は、その信号源を変更できます。ほかの人と共有するクラウドのブローカーでは、それはプラントの外の人も含みます。ブローカー上のダッシュボードのアカウントには、必要なトピックだけを許可してください。Sparkplug B のブローカーでは、同じ行が書き込みトピックの代わりに Sparkplug B のコマンド（NCMD、DCMD）を受け付けます（[Sparkplug B での発行](#publishing-in-sparkplug-b)）。

```text
"C:\Program Files\mosquitto\mosquitto_pub" -h localhost -u dashboard -P <password> -t plant/oven/setpoint/set -m 65
```

## セキュリティ {#security}

**TLS を使う**では、ブローカーの証明書は 2 つのどちらかの方法で受け入れられます。証明書チェーンとホスト名が、このコンピューターのどの HTTPS 接続とも同じく Windows の証明書ストアで検証されるか、このドライバーのページの[現場設備へのアクセス](#equipment-access)でその証明書そのものを承認したかです。自前の認証局が発行した証明書を持つブローカーは、その認証局を Windows のストアに入れれば信頼されます。自己署名の証明書を持つブローカーは、証明書を承認すれば何もインストールする必要はありません。失効は確認しません。見ていない証明書を受け入れるものは何もなく、知らない証明書は拒否され、ブローカーは理由付きで「接続できません」になり、その機器は承認を待ちます。パスワードは TLS の接続の中を通ります。TLS なしではカードの警告どおり平文で送られます。

## 現場設備へのアクセス {#equipment-access}

自己署名の証明書や自前の認証局が発行した証明書を持つブローカーは、Windows にインストールされたどのルートも保証しない証明書を出します。そのルートをこのコンピューターにインストールする必要はありません。**TLS を使う**では、最初の接続は拒否され、ブローカーは理由付きで「接続できません」になり、その機器はツリーとそれぞれのページで**証明書の承認を待っています**と表示し、そのページの**証明書を確認する**のボタンがこのカードをその証明書の位置で開きます。証明書は、MQTT のドライバーのページの最後にある**現場設備へのアクセス**で、判断を待ちます。OPC UA のドライバーのページにあるのと同じカードです。

| グループ | 並ぶもの | できること |
|---|---|---|
| 承認待ち | 拒否された証明書の 1 つ 1 つが、証明書が名乗る名前の下に並びます。SHA-256 のフィンガープリント、それに出会ったブローカーのアドレス、最後に試した日時と回数、詳細（主体、発行者、ドメイン名、有効期間）が出ます。機器の**証明書を確認する**で開いた証明書には「確認のために選択中」の印が付きます。 | **承認する**と、セッションの次の接続の試みからその証明書を受け入れます。**遮断する**と、ここにふたたび並べることなく拒否し続けます。**承認待ちの証明書をすべて破棄**は、何も承認も遮断もせずに一覧を空にします。 |
| 承認した現場設備 | 承認した証明書と取り込んだ証明書が、誰がいつ判断したかと、それぞれに最後に出会ったブローカーのアドレスとともに並びます。 | **承認を取り下げる**と、ブローカーのセッションを閉じ、その証明書を一覧に戻します。**証明書を取り込む**と、手元にある公開証明書のファイル（.der、.cer、.crt、.pem）を最初の接続より前に承認します。 |
| 遮断した現場設備 | 遮断した証明書が並びます。遮断した証明書は、Windows が信頼していても拒否されます。 | **遮断を解除**すると、承認はせずに、その証明書を一覧に戻します。 |

承認する前に、ブローカーを管理している担当者とフィンガープリントを照合してください。承認で受け入れるのはその証明書だけで、しかも有効期間の内側だけです。承認は、Windows に足りないルートと、証明書に書かれた名前の代わりになります。そのため、IP アドレスで接続するブローカーも、証明書に別のホストの名前が書かれていてもつながります。証明書のそのほかの点は何もゆるめません。承認した証明書のアドレスでブローカーが別の証明書を出すと、その機器は読み取りを止め、新しい証明書がここで待ち、ステーションが**確認する**のボタン付きで「証明書が変わりました：`<名前>`」と知らせます。「機器を検出」のカードからのブローカーの検出も、同じ判断にしたがいます。Windows がすでに信頼している証明書はこれまでどおり受け入れられ、ここには出ません。何かが自動で承認されることはありません。

ブローカーの原因は、名前のない TLS のエラーではなく理由を述べます：「`<ホスト>:<ポート>` のブローカーの証明書（フィンガープリントの先頭 16 文字）は、MQTT ドライバーのページの「現場設備へのアクセス」であなたの承認を待っています。承認すると読み取りが始まります。」遮断した証明書なら「…はこのステーションで遮断されています。MQTT ドライバーのページの「現場設備へのアクセス」で確認してください。」となります。有効期間の外にある証明書、失効した証明書、署名の壊れた証明書は、その理由で拒否され、一覧には並びません。承認しても健全にはならないからです。

判断はステーションのデータフォルダーの `ua\pki\mqtt` に保存され、再起動しても残ります。操作が描かれるのは Connector を構成できるロールだけで、操作が効くのはステーション自身のウィンドウの中だけです。別の機器のブラウザーはカードを読むだけで変えられず、エージェントのツールが証明書を承認することもありません（`connector_browse` はブローカーの機器を `awaitingCertificate` で示します）。一覧に並ぶ承認待ちの証明書は 100 件までです。いっぱいになると、新しい証明書は引き続き拒否されますが一覧には並ばず、カードがそのことを伝えます。

## ブローカーの準備 {#prepare-the-broker}

ブローカーは、ステーションのコンピューターからの接続を受け付け、ステーションのサインインを受け付ける必要があります。最も一般的なブローカーである Eclipse Mosquitto 2 は、工場出荷時にはどちらもしません。mosquitto.conf に listener がなければ自分のコンピューターでしか待ち受けず、listener を持つとユーザー名のないクライアントを拒否します。MQTT はどのブローカーが応答したかを伝えないため、ステーションは Mosquitto をよくある例として挙げるだけで、断定はしません。

| ブローカーの応答 | ブローカーのページが示すこと | 変える方法 |
| --- | --- | --- |
| そのコンピューターがポートを拒否する | ブローカーは自分のコンピューターからの接続しか受け付けません | mosquitto.conf に listener とポートの行 (listener 1883) を加えてブローカーを再起動し、そのコンピューターのファイアウォールでポートを許可します |
| ユーザー名のないクライアントを拒否する | ブローカーはユーザー名のないクライアントを受け付けません | ブローカーへの接続カードのユーザー名とパスワードにブローカーのアカウントを入れます。Mosquitto 2 ではそのアカウントを mosquitto_passwd で作り、mosquitto.conf の password_file で指定します。または allow_anonymous true で匿名のクライアントを通します |
| ユーザー名またはパスワードを拒否する | ブローカー自身の拒否で、ブローカーへの接続カードのユーザー名とパスワードを指し示します | ブローカー側かカード側のアカウント |

最初の 2 つは、ブローカーのページで英語のエラーの代わりに、この手順とポートとともに示されます。その配下の機器は引き続きブローカーを指し示します。セッションが接続すれば、この通知はひとりでに消えます。ツリーのマークとジャーナルには英語の原因が残り、その最後に同じ Mosquitto の手順が付きます。

## ブローカーをインストールする {#installing-a-broker}

ステーションには接続先のブローカーが必要で、装置にも必要です。ない場合、Eclipse Mosquitto は Windows のサービスとして動く小さなブローカーです。

1. mosquitto.org のダウンロードページから Windows 用のインストーラーを入手して実行します。`C:\Program Files\mosquitto` に**Mosquitto Broker**というサービスが入ります。
2. 初期状態の Mosquitto は、動いているコンピューターの中でしか応答しません。ネットワーク上の装置を受け入れるには、そのフォルダーの `mosquitto.conf` にリスナーとパスワードファイルを加えます。

   ```
   listener 1883
   password_file C:\Program Files\mosquitto\passwd
   ```

   ステーションと装置が使うアカウントでファイルを作ります（コマンドがパスワードを尋ねます）。

   ```
   "C:\Program Files\mosquitto\mosquitto_passwd" -c "C:\Program Files\mosquitto\passwd" station
   ```

3. 管理者のコマンドプロンプトでサービスを再起動し、Windows Defender ファイアウォールで装置のネットワークからの TCP ポート 1883 の受信を許可します。

   ```
   net stop mosquitto
   net start mosquitto
   ```

4. ブローカーのページで、ブローカーがステーションと同じコンピューターで動いているなら**ホスト**に `localhost` を、そうでなければそのコンピューターのアドレスを入れ、**ポート**に 1883 と作ったアカウントを入れてから**有効化**します。

装置をつなぐ前に通常のタグを試すには、同じフォルダーから保持付きのテスト値を発行し、タグの**トピック**をそこに向けます。

```
"C:\Program Files\mosquitto\mosquitto_pub" -h localhost -u station -P <password> -t plant/line1/temperature -m 21.5 -r
```

## 知っておきたい制限 {#limits-worth-knowing}

- MQTT ドライバーはクライアントで、ブローカーではありません。装置とステーションはブローカーに接続します。ステーション自身の [MQTT ブローカー](connector-mqtt-broker) でも、別のブローカーでもかまいません。
- ブローカーの発行のカードは JSON か Sparkplug B のどちらかで出て、両方を同時には出しません。Sparkplug B のとき、ステーションはエッジノードでプライマリホストアプリケーションではなく、エイリアス、テンプレート、履歴値は使いません。
- ステーションは Sparkplug B のホストアプリケーションではなく、STATE メッセージを発行しません。発行の前にプライマリホストを待つよう設定されたエッジノードは沈黙したままです。
- 通常の MQTT は、黙ってしまった発行元について何も伝えません。通常のタグは、ブローカーとの接続が続く間、最後に受け取った値を到着時刻付きで持ち続けます。ただし機器に**メッセージがないまま許す最長時間（秒）**が設定されていれば別です。値が変わったときだけ発行されるトピックでは、黙った発行元と変わらない値を見分けられないので、その機器は 0 のままにします。Sparkplug B は死亡証明書でそれを伝えます。
- セッションはクリーンな状態で始まります。ステーションが接続していない間に発行されたものは再送されず、タグに値を戻すのはトピックの保持メッセージです。
- タグが読むのは 1 つのトピックとその中の 1 つの値で、フィルターは読みません。配列、データセット、テンプレート、プロパティセットである Sparkplug B のメトリックは読みません。
- TCP のみで、TLS の有無は選べます。WebSocket 上の MQTT、クライアント証明書、MQTT 3.1.1 より古いブローカーには対応しません。
- 機器を別のブローカーに移すことはできません。別のブローカーの下に作り、古い機器を削除してください。
