MQTT の配信はデバイスの実行ではない
何が、誰に、どのレイヤーで配信されたのかをシステムが説明しない限り、「配信済み」は曖昧なステータスです。
MQTT QoSは、プロトコルピア間のメッセージ配信を管理します。パブリッシャーからブローカーへのQoS 1 交換が成功しても、サブスクライブしているデバイスがコマンドを実行したことの証明にはなりません。ブローカーからデバイスへの配信は別の交換です。同様に、QoS 2 でも、物理的なアクションが正確に1回だけ実行されるビジネストランザクションになるわけではありません。OASIS MQTT 仕様のセクション 4.3(https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901234). を参照してください
コマンドは、複数の異なるチェックポイントを通過する場合があります。
- バックエンドがリクエストを記録する。
- パブリッシャーがメッセージを送信する。
- ブローカーがパブリケーションを処理する。
- デバイスアプリケーションがコマンドを検証する。
- デバイスが操作を試行する。
- デバイスが結果を検証して報告する。
インターフェースには、利用可能な証拠によって裏付けられる最も確度の高い結論のみを表示する必要があります。バックエンドがメッセージをパブリッシュしたことしか把握していない場合、「完了」よりも「コマンド送信済み」の方が正確です。
また、使用している MQTT ライブラリにおいて、パブリッシュ成功のコールバックが何を意味するのかを確認してください。その意味は、クライアントの実装と選択したQoSによって異なる場合があります。
成功と見なす条件を定義する
確認応答メッセージを設計する前に、各コマンドの完了条件を定義してください。
設定更新の場合、デバイスが新しい設定を永続化し、それを読み戻したことを成功と見なせます。モーターコマンドの場合は、位置フィードバックが必要になることがあります。印刷リクエストの場合、プリンターへのバイト送信によって送信済みであることは確認できても、用紙に印刷されたことまでは証明できません。
これらは異なる製品保証です。
有用なコマンド仕様では、次の3つの質問に答えます。
- リクエストを受理する前に、デバイスは何を検証する必要があるか?
- どのような証拠によって完了が確認されるか?
- その証拠を取得できない場合、システムは何を報告すべきか?
ハードウェアが物理的な結果を検証できない場合は、確認できる範囲に限定した結果を報告してください。たとえば、「印刷済み」ではなく「プリンターに送信済み」とします。
すべての論理コマンドに安定した識別子を付与する
すべてのコマンドには、再試行しても変わらないアプリケーションレベルの識別子を含める必要があります。
次のリクエスト例を考えてみましょう。
{
"schemaVersion": 1,
"commandId": "cmd_7f93a2",
"deviceId": "controller-042",
"type": "set_output",
"parameters": {
"channel": 1,
"enabled": true
},
"issuedAt": "2026-09-11T08:00:00Z",
"expiresAt": "2026-09-11T08:00:30Z"
}
各フィールドには、それぞれ異なる役割があります。
schemaVersionはペイロード契約を識別します。commandIdはリクエスト、再試行、応答、ログを関連付けます。deviceIdは対象となるターゲットを識別します。typeとparametersは操作を記述します。issuedAtはトレーサビリティを支えます。expiresAtは、この例で操作を開始できる最終時刻を定義します。
同じ論理リクエストを再試行する場合は、その commandId を再利用する必要があります。新しいユーザー操作には、新しい識別子を付与する必要があります。
パラメーターを変更して同じ識別子を再利用しないでください。コマンドレコードとともにペイロードのフィンガープリントを保存し、内容が異なる再利用を拒否してください。
有効期限には、明示的なクロックポリシーも必要です。デバイスが自身の実時間時計を信頼できない場合は、時刻を確立する方法、または時間制約のあるコマンドを拒否する方法を定義してください。この契約では、有効期限は遅延した開始を防ぎますが、すでに進行中の操作を自動的にキャンセルするものではありません。
受理と完了を分ける
簡潔なアプリケーションレベルのライフサイクルでは、次の状態を使用できます。
accepted:検証に合格し、デバイスが処理対象としてコマンドを記録した。executing:操作が開始された。succeeded:定義された完了条件が検証された。rejected:検証に失敗し、実行は開始されなかった。failed:実行を試行したが、完了条件を満たさなかった。
これらの名称は、提案するアプリケーション契約であり、MQTT プロトコルのステータスではありません。
再起動後の復旧が必要な操作では、コマンドが永続的に記録された後にのみ accepted を送信してください。
受理応答は、次のようになります。
{
"schemaVersion": 1,
"commandId": "cmd_7f93a2",
"deviceId": "controller-042",
"status": "accepted",
"statusVersion": 1
}
完了応答には、操作に適した証拠を含める必要があります。
{
"schemaVersion": 1,
"commandId": "cmd_7f93a2",
"deviceId": "controller-042",
"status": "succeeded",
"statusVersion": 3,
"result": {
"channel": 1,
"enabled": true,
"verification": "output_register_readback"
}
}
ここでの成功とは、出力レジスターの読み戻しに成功したことを意味します。接続された機械が動作したことや、意図したタスクを実行したことを独立して証明するものではありません。
コマンドごとに単調増加する statusVersion を使用し、バックエンドが重複または古い更新を無視できるようにしてください。再起動後もコマンドが保持される場合は、復旧後もこのシーケンスを維持してください。
コマンドを送信する前に応答経路を設計する
シンプルなトピック構造は次のとおりです。
devices/controller-042/commands
devices/controller-042/command-results
デバイスは自身のコマンドトピックをサブスクライブし、ステータス更新を自身の結果トピックにパブリッシュします。バックエンドは commandId を使用して応答を関連付けます。
コマンドをパブリッシュする前に、バックエンドはそのレコードを永続化し、応答のサブスクリプションを確立する必要があります。そうしないと、システムが処理できる状態になる前に、迅速な応答が到着する可能性があります。
トピックへのアクセスを制限し、デバイスが許可されたコマンドのみを受信し、自身の結果のみをパブリッシュできるようにしてください。認証されたパブリッシャー、トピック、ペイロード内のIDの関係を検証してください。
MQTT 5 は、リクエスト・レスポンスパターン向けにResponse TopicおよびCorrelation Dataプロパティを提供します。これらはルーティング情報と相関情報を伝達できますが、結果ペイロードと成功条件は引き続きアプリケーションが定義します。MQTT 5 のセクション 4.10(https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html). を参照してください
MQTT 3.1.1 のデプロイメントでは、上記の明示的なトピックとペイロードの規約が、アプリケーションレベルの代替手段となります。
タイムアウトを結果不明として扱う
応答がないからといって、何も起きなかったことが確定するわけではありません。
コマンドがデバイスに届かなかった可能性があります。デバイスがまだ実行中である可能性もあります。または、接続が失われる直前に操作が完了していた可能性もあります。
期待する事象ごとに、個別の期限を使用してください。
- 受理期限: デバイスがリクエストを受理または拒否するまで待機する時間。
- 完了期限: 受理後に操作に許容される時間。
- コマンドの有効期限: デバイスが操作を開始できる最終時点。
これらの値は、測定したデバイスの動作と製品要件に基づいて選択してください。
十分な証拠がないまま期限を過ぎた場合は、次のような観測結果を記録してください:outcome_unknown。デバイスが報告していない失敗を作り出してはいけません。
インターフェースには次のように表示できます。
確認できません。デバイスの状態を確認しています。
タイムアウトはキャンセルではありません。キャンセルが必要な場合は、独自の受理ルールと結果を持つ別のリクエストとして設計してください。
タイムアウトしたコマンドは、照合できるように保持してください。後から有効な結果が届いた場合は、確認が遅れて到着したという事実を保持しつつ、レコードを更新する必要があります。
再試行を有効にする前に安全性を確保する
デバイスが元のコマンドを認識しない場合、アプリケーションによる再試行で物理的なアクションが繰り返される可能性があります。
デバイス側のコマンドジャーナルには、コマンドID、ペイロードのフィンガープリント、実行状態、最終結果を記録する必要があります。重複したコマンドが届いた場合:
- コマンドが完了している場合は、保存済みの結果を返す。
- まだ実行中の場合は、現在の状態を返す。
- 同じ識別子に異なる内容が含まれている場合は、リクエストを拒否する。
処理を直列化するか、コマンドIDをアトミックに予約し、同時に2件配信されても両方が実行を開始しないようにしてください。
重複排除レコードは、少なくともサポート対象の再試行期間と遅延配信期間を合わせた期間、保持してください。
実用的な場合は、明示的な目標状態を指定するコマンドを優先してください。「出力を有効に設定」は「出力を切り替え」よりも照合しやすく、繰り返しても意図的に状態が反転することはありません。
ただし、ジャーナルだけでは、物理的な実行が正確に1回だけ行われることを保証できません。デバイスは、アクションを実行した後、結果を保存する前に電源を失う可能性があります。払い出し、印刷、または同様の操作では、復旧にハードウェアトランザクション識別子、センサーによる証拠、またはオペレーターによる確認が必要になる場合があります。
結果が曖昧で、重複が問題になる場合は、再試行する前に照合してください。
古いコマンドが後から実行されるのを防ぐ
一度限りのアクションは、通常、保持せずにパブリッシュする必要があります。保持された MQTT メッセージは、後から接続したサブスクライバーに配信される可能性があるため、古いアクションリクエストは特に問題になります。MQTT 5 Message Expiry Intervalは、メッセージングシステム内でのメッセージの有効期間を制限しますが、デバイス側の実行ルールに代わるものではありません。MQTT 5 仕様(https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html). を参照してください
再接続後や再起動後を含め、キューに入ったコマンドを開始する前に有効期限を確認してください。
一度限りのアクションと、望ましい状態の同期を分けてください。保存された望ましい設定は意図的な場合もありますが、それには独自のバージョン管理と照合ルールが必要です。
同様に、ハートビートはコマンド完了の証拠ではありません。アクチュエーターが動作不能であったり、コマンドプロセッサーが利用できなかったりしても、デバイスはオンラインである可能性があります。
誤った成功を生む障害をテストする
バックエンド、ブローカー、ファームウェア、ハードウェアの境界をまたいでコマンド契約をテストしてください。
次のシナリオを含めてください。
- 配信前にデバイスが切断: インターフェースに未検証の成功が表示されない。
- 無効なパラメーター: デバイスはハードウェアを動作させずに
rejectedを返す。 - 実行中にコマンドが重複: 1回の実行のみが開始される。
- 同じ識別子でパラメーターが変更: デバイスが競合を拒否する。
- 完了応答が失われる: アクションを繰り返すことなく、照合によって結果を復元する。
- 受理後に再起動: デバイスがポリシーに従って記録済みのコマンドを復旧する。
- 物理的なアクション後に電源喪失: 証拠によって解決されるまで、システムは不確実性を保持する。
- 再接続後に期限切れのコマンド: デバイスは古い操作を開始しない。
- 古いステータスが遅れて到着: バックエンドは完了から受理へ状態を後退させない。
- 別のデバイスのIDで結果がパブリッシュされる: アクセス制御または検証によって拒否される。
各テストでは、ソフトウェアのステータスと実際のハードウェアの結果の両方を確認してください。ダッシュボードだけでは、物理的な実行の重複が防止されたことを証明できません。
リクエストから結果まで追跡可能な経路を構築する
信頼性の高いコマンドワークフローには、誰が操作をリクエストしたか、どのデバイスがそれを受理したか、実行中に何が起きたか、結果がどのように検証されたかを示す明示的な証拠の連鎖が必要です。
まず1種類のコマンドから始めてください。その完了条件を定義し、安定した識別子を追加し、受理メッセージと結果メッセージを実装し、確認が失われた場合の動作をテストします。再試行と復旧の動作を理解してから、このパターンを拡張してください。
YUNJI の MQTT Cloud Integrationサービス(https://yunji-node.com/solutions/mqtt-cloud-integration) は、コネクテッド製品システム全体のデバイスID、コマンドワークフロー、状態同期、障害処理を対象としています。統合レビューを準備する際は、トピック構造、サンプルペイロード、ファームウェアの動作、結果が不明確だったコマンドのログを用意してください。



