【第1回】PLCの10秒周期データをRustで集約し、ZabbixとWebに二重配信する
三菱 MELSEC Q から 10 秒ごとに全センサーを Rust で一括取得し、Zabbix には蓄積用に送り込み、Web には DB を経由せず JSON を直送する。この二重配信アーキテクチャで、現地管理者には Zabbix の GUI を、利用者には React ダッシュボードを、それぞれ最適な画面として提供しています。本記事ではその全体像と、Rust によるデータ収集層を解説します。
前提環境
| 項目 | 内容 |
|---|---|
| PLC | 三菱電機 MELSEC Q シリーズ(CPU: Q06UDV、QJ71MB91、QX42等) |
| Rust | 1.75+(Tokio 1.x、serde、axum、MC Protocol 3E 通信は自前実装) |
| Zabbix | 6.0 LTS |
| フロントエンド | React 18 + Tailwind CSS 3.x + Highcharts / ApexCharts |
| 1 拠点あたりのセンサー数 | 数十〜約 200 ポイント(電力 + ping + HTTP) |
| ポーリング周期 | 10 秒固定 |
具体的な設置場所や拠点数は非公開です。また、本番環境は複数のマイクロサービスで構成されていますが、本記事では設計思想を伝えるために概念的に整理・簡略化して記述しています。コード例も実際に使用したものを参考にしていますが、構造を示すための擬似的なものです。
はじめに
施設の電力監視システムを構築するにあたり、最終的に採用したアーキテクチャは以下の形になりました。
- MELSEC Q(PLC) が電力量センサーの信号を集約
- Rust モジュール が 10 秒周期で PLC + ping + HTTP を一括取得し、全データを 1 つの JSON に統合
- その JSON を Zabbix(トラッパー経由で蓄積・アラート)と React ダッシュボード(WebSocket 直送で DB なしリアルタイム表示)に二重配信
リアルタイム表示のパスに DB が一切介在しないため、閲覧者が何人いても DB がボトルネックになりません。一方、Zabbix は長期データの蓄積とアラートに徹しつつ、現地管理者が SQL を書かずにセンサーの生値やグラフを GUI だけで確認できる「計器盤」 としても機能します(この点は第2回で詳しく論じます)。
本シリーズでは、この設計判断の背景と実装を3回に分けて解説します。
| 回 | テーマ |
|---|---|
| 第1回(本記事) | 全体アーキテクチャと Rust によるマルチプロトコルデータ収集 |
| 第2回 | Zabbix を現地管理者の「計器盤」にする — 時系列 DB だけでは足りなかった理由 |
| 第3回 | React + Tailwind で「Zabbix にログインさせない」監視画面を作る |
システムのデモ版について
こちらから本システムのデモ版にアクセスいただけます。ぜひご覧ください。
以下のユーザー名/パスワードで閲覧できます。
- ユーザー名:demo01
- パスワード:ResnBNVvrHObS8GD
なぜ「監視システムのUI」が課題になるのか
Zabbix は IT インフラ監視の世界で圧倒的な実績を持つ OSS です。
ネットワーク機器やサーバーの監視には最適ですが、施設の電力監視 という文脈でそのまま使おうとすると、壁にぶつかります。
管理者にとっては完璧、しかし利用者には複雑すぎる。
Zabbix のダッシュボードは柔軟で強力です。実際、現地の設備管理者にとっては、Zabbix は少しの学習でセンサーの生値やグラフを確認できる 非常に優秀なインターフェース でもあります(この点は第2回で詳しく論じます)。
しかし、その先の「利用者」──たとえば施設管理部門の上層や、電力量レポートを見たいだけの担当者──にとっては、ホスト・テンプレート・トリガーといった概念が前提知識として求められる Zabbix は明らかにオーバースペックです。
つまり問題は 「Zabbix が良くない」のではなく、「Zabbix が適切な人と、もっとシンプルな画面が必要な人が混在している」 ことにあります。
さらに、Zabbix 標準のポーリング間隔は最短 1 秒設定が可能とはいえ、数十〜数百のセンサーを 10 秒ごとに一括取得して即座に Web 画面に反映する ようなリアルタイム性を求めると、Zabbix 単体ではアーキテクチャが苦しくなります。
そこで本システムでは、2 つの設計判断を行いました。
- 画面を「3 層」に分ける ── 開発者(Rust / API 保守)、現地管理者(Zabbix UI でセンサー確認・設定)、利用者(React で結果だけ見る)
- PLC からのリアルタイム取得は専用の Rust モジュールに任せ、Zabbix と Web の両方にデータを供給する
システム全体像 ── 5 層アーキテクチャ
本システムは以下の 5 層で構成されています。Rust モジュールが中央に位置し、データの「心臓」 として機能します。
┌─────────────────────────────────────────────────────────────────┐
│ 利用者(ブラウザ) │
│ React + Tailwind CSS Dashboard │
│ リアルタイムグラフ / 統計テーブル / 拠点マップ │
└──────────────────────────┬──────────────────────────────────────┘
│ WebSocket / SSE (JSON)
┌──────────────────────────▼──────────────────────────────────────┐
│ 中間 API サーバー │
│ Rust リアルタイムデータ受信 / Zabbix API 長期データ取得 │
│ 拠点メタ情報管理 / JSON 変換 │
└───────────┬──────────────────────────────────┬──────────────────┘
│ Zabbix API (JSON-RPC) │ WebSocket
│ ※長期トレンド・アラート参照 │ ※リアルタイム受信
┌───────────▼──────────────┐ ┌──────────────▼──────────────────┐
│ Zabbix サーバー │ │ Rust データ収集モジュール │
│ ヒストリ・トレンド蓄積 │◀───│ 10秒周期で全センサー一括取得 │
│ トリガー・アラート管理 │ │ PLC ↔ MC Protocol 3E 通信 │
│ 管理者は直接この UI 操作 │ │ Zabbix + Web 双方へデータ供給 │
└──────────────────────────┘ └──────────────┬──────────────────┘
│ MC Protocol 3E (10秒周期)
┌───────────────▼──────────────────┐
│ PLC(三菱 MELSEC Q シリーズ) │
│ 電力量センサー/CT/パルスカウンタ │
│ モジュール構成で柔軟にI/F拡張 │
└──────────────────────────────────┘
従来の構成との最大の違い は、PLC と Zabbix の間に Rust モジュール が入っている点です。Rust モジュールは 10 秒ごとに PLC の全レジスタを一括読み出しし、取得したデータを 2 つの経路に同時に流します。Rust モジュールも 10秒毎の定時実行を非常に安定して実行してくれます。
-
Zabbix 方向:
zabbix_senderプロトコル経由で Zabbix サーバーへ送信(蓄積・アラート用) - Web 方向:バックエンドサービスを経由し、WebSocket で React フロントエンドへリアルタイム配信
この「1 回の取得、2 方向の配信」が、リアルタイム表示と長期蓄積を両立する鍵です。
DB を経由しないリアルタイムパス
ここで強調したいのは、Web 方向のリアルタイム表示パスには DB が一切介在しない という点です。
[リアルタイム表示パス ── DB なし]
PLC → Rust → JSON (メモリ上) → WebSocket → React
│
└→ 全センサーの現在値が 1 つの JSON オブジェクト
DB への読み書きゼロ。純粋なメモリ→ネットワーク配信。
[長期データパス ── DB あり(グラフ・統計用のみ)]
PLC → Rust → Zabbix (DB書込) → Zabbix API (DB読出) → 中間API → React
Rust モジュールが PLC から読み出した全センサーの値は、SensorSnapshot という 1 つの JSON 構造体としてメモリ上に保持され、そのまま WebSocket で配信されます。DB へのクエリも、DB への書き込み待ちもありません。
この構造がもたらすメリットは 2 つあります。
リアルタイム性: DB のクエリレイテンシが構造上ゼロです。当社構成では、PLC からの読み出し完了からブラウザ到達まで数百ミリ秒程度に収まっています。Zabbix 経由で同じことをやろうとすると、「Zabbix に書き込む → Zabbix API で読み出す」という DB の往復が入り、体感で数秒以上のもたつきが生じます。
スケーラビリティ: 閲覧者が 10 人でも 100 人でも、DB への負荷は増えません。Rust が生成した JSON を WebSocket で fan-out するだけなので、閲覧者数に比例するのはネットワーク帯域だけです。従来の「DB にデータを入れて、画面表示のたびに DB から読み出す」構成では、閲覧者が増えるほど DB が SELECT クエリで圧迫されますが、本構成ではリアルタイム表示に関して DB がボトルネックになることは構造上ありえません。
DB が介在するのは、過去のトレンドグラフや月次統計など 「蓄積されたデータを遡って見る」 場面だけです。日常の監視──「今の電力はいくらか」「各区画の状態はどうか」──は、すべて DB を経由しない JSON 直送パスで完結します。
本番環境の現実 ── PLC だけでは足りない
ここまで PLC(MELSEC Q)からの通信を中心に説明してきましたが、本番環境で実際に扱っているデータソースは PLC だけではありません。
現場のインフラを統合的に監視するためには、電力量センサー以外にも多様なプロトコルでデータを取得する必要があります。
| データソース | プロトコル | PLC 単体で対応可能か |
|---|---|---|
| 電力量センサー | MC Protocol 3E(PLC 経由) | ○ |
| サーバー・機器の死活監視 | ping (ICMP) | ✕ |
| Web サービスの応答確認 | curl(HTTP/HTTPS) | ✕ |
| 環境センサー(温湿度・CO2) | MC Protocol / HTTP API | △ |
ping で通信機器の生存確認をしたり、curl で外部 API や Web サービスのレスポンスコードを取得したり──こうした PLC だけでは対応が難しい領域 を、Rust モジュールがネイティブに担います。
Rust の非同期ランタイム(Tokio)が真価を発揮するのはまさにここで、10 秒の 1 サイクル内に MC Protocol 読み出し・ping 送信・HTTP リクエストを並行実行 し、すべての結果を 1 つの SensorSnapshot JSON に統合して出力します。
[10秒の1サイクルで起きること]
tokio::join!(
mc_protocol_poll(&plc), // MELSEC Q から電力データ取得(MC Protocol 3E)
ping_check(&switches), // ネットワーク機器の死活確認
http_check(&endpoints), // Web サービスの応答確認
)
↓
全結果を SensorSnapshot に統合
↓
1つの JSON として WebSocket 配信 + Zabbix 送信
MELSEC Q の 「一度動けば止まらない」信頼性 と、Rust の 「何でも並行に取りに行ける」柔軟性。この 2 つが組み合わさることで、PLC 単体では到達できなかった幅広いセンサーデータの統合が実現しました。
きっちり 10 秒毎に更新される JSON、DB 経由では味わえないキビキビ感
こうして生成される JSON ファイルは、きっちり 10 秒毎に更新されます。そしてフロントエンドの React は、この JSON を忠実かつ高速に描画に反映します。
DB を経由する構成では、「書き込み → インデックス更新 → クエリ → レスポンス整形」という多段のレイテンシが積み重なり、画面の更新にどうしても「もたつき」が生じます。一方、本構成では JSON がそのまま画面になる ──間に何も挟まない── ため、数値の変化が画面に現れるまでのタイムラグが体感的にほぼゼロです。
ダッシュボードを開いた瞬間、10 秒ごとにサマリーカードの数値がスッと切り替わり、フロアマップの色がなめらかに遷移し、折れ線グラフに新しい点が追加されていく。この 「キビキビ感」 は、DB 経由の監視画面ではなかなか味わえないものです。
設計者として正直に言えば、このリアルタイム表示が初めて本番環境で動いたとき、ひそかに喜びを感じていました。技術的に正しい設計が、ユーザーの体験として「気持ちいい」に直結する瞬間は、エンジニアにとって何にも代えがたいものです。
まだまだ語りきれないノウハウはありますが、MELSEC Q × Rust × 全データ JSON というこの構造が、本システムのリアルタイム性とスケーラビリティの根幹を支えています。
なぜ Rust なのか ─ リアルタイムデータ収集に求められる要件
PLC の選定 ── 三菱電機 MELSEC Q シリーズ
本システムの最下層で全センサーデータを集約する PLC には、三菱電機製 MELSEC Q シリーズ を採用しています。
MELSEC Q シリーズを選定した最大の理由は、モジュールベースのアーキテクチャ にあります。ベースユニットにCPUモジュールを搭載し、必要に応じて以下のようなモジュールを差し込んでいくことでインターフェースを柔軟に拡張できます。
- Ethernet 通信モジュール:Rust モジュールとの MC Protocol 3E 接続に使用
- A/D 変換モジュール:アナログ出力のセンサーからの信号取り込み
- 高速カウンタモジュール:パルス出力型電力量センサーの積算値を正確にカウント
- デジタル入出力モジュール:接点信号(ブレーカーの ON/OFF 状態など)の取り込み
計測対象が増えた場合も、ベースユニットにモジュールを追加するだけで物理的なインターフェースを拡張できます。ソフトウェア側(Rust の TOML 設定)と同じ発想──コード変更なしに設定追加で拡張──がハードウェア側でも成立するのは、モジュール型 PLC ならではの強みです。
Rust からの通信には、三菱電機独自の MC Protocol(MELSEC Communication Protocol)3E フレーム を採用しています。MELSEC Q は Modbus/TCP にも対応していますが、MC Protocol はデバイスメモリ(D レジスタ、M コイル等)への直接アクセスが可能で、Modbus のようなアドレス変換が不要なため、MELSEC Q のネイティブプロトコルとしてより効率的に通信できます。Rust 側のクライアントは tokio の非同期 TCP ソケット上に自前実装しています。
そして何より、MELSEC Q シリーズは ひとたび動き出せば抜群の信頼性と安定感で動き続けてくれます。日本の製造業や社会インフラの現場で数十年にわたって鍛え上げられてきた産業用コントローラは、24 時間 365 日の連続運転においてPC ベースのデータ収集とは次元の異なる堅牢性を持っています。電力監視システムの最下層──最もダウンが許されないデータ収集の起点──に、この信頼性は不可欠でした。
Rust に求めた要件
PLC との MC Protocol 3E 通信に加え、ping・HTTP など 複数プロトコルを並行処理 するモジュールには、以下の要件がありました。
| 要件 | 内容 |
|---|---|
| 確実な周期実行 | 10 秒ごとのポーリングを長期間ドリフトなく維持 |
| 低レイテンシ | MC Protocol + ping + HTTP を 10 秒以内に完了 |
| 高信頼性 | 24/365 運転でメモリリーク・クラッシュが許されない |
| 並行処理 | マルチプロトコル収集・Zabbix 送信・Web 配信を同時実行 |
| 省リソース | 現場の小型 Linux ボックスや産業用 PC で動作 |
Python や Node.js でも実現は可能ですが、GC による一時停止がポーリング周期の安定性を損なうリスク があります。特に数百センサーの一括取得では、GC ポーズが 10 秒の周期境界と重なると、1 サイクル分のデータが欠損する可能性があります。
Rust は以下の特性でこれらの要件に応えます。
- ゼロコスト抽象化 + GC なし:確定的なメモリ管理でポーリング周期が安定
- async/await(Tokio):MC Protocol 読み出し、Zabbix 送信、WebSocket 配信を非同期に並行処理
- 所有権システム:データ競合をコンパイル時に排除し、長期運転の信頼性を担保
- クロスコンパイル:x86_64 で開発し、ARM ベースの産業用 PC にもデプロイ可能
-
シングルバイナリ:依存ランタイム不要で、現場へのデプロイが
scp1 発
Rust モジュールの設計 ── 10 秒周期の全センサー一括取得
アーキテクチャ概要
┌──────────────────────────────────┐
│ Rust Data Collector │
│ │
[MELSEC Q]─MC 3E─▶ │ ┌──────────┐ ┌──────────┐ │──▶ [Zabbix Server]
[機器]─────ping──▶ │ │ Poller │ │ Sender │ │ (zabbix_sender)
[Web]──────curl──▶ │ │ (10sec) │──▶│ Fanout │ │
│ │ 全プロトコル│ │ │ │──▶ [バックエンドサービス群]
│ │ 並行実行 │ │ │ │ (WebSocket 配信)
│ └──────────┘ └──────────┘ │
└──────────────────────────────────┘
Rust モジュール内部は大きく 2 つの責務に分かれます。
- Poller:10 秒タイマーで起動し、MC Protocol 3E / ping / HTTP を並行実行 して全データを一括取得
- Fanout(分配器):取得した全結果を 1 つの JSON に統合し、Zabbix と Web の 2 方向に同時送信
コア実装
use tokio::time::{interval, Duration};
use tokio::sync::broadcast;
// MC Protocol 3E の通信クライアント(自前実装)
use crate::plc_client::McProtocolClient;
/// 全センサーの読み取り結果を保持する構造体
#[derive(Clone, Debug, serde::Serialize)]
struct SensorSnapshot {
timestamp: chrono::DateTime<chrono::Utc>,
readings: Vec<SensorReading>,
}
#[derive(Clone, Debug, serde::Serialize)]
struct SensorReading {
zone_id: String,
item_key: String, // Zabbix アイテムキーに対応
value: f64,
unit: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// ── 設定読み込み ──
let config = load_config("config.toml")?;
// ── broadcast チャネル:取得データを複数の送信先に分配 ──
let (tx, _) = broadcast::channel::<SensorSnapshot>(64);
// ── Zabbix 送信タスク ──
let tx_zabbix = tx.subscribe();
tokio::spawn(zabbix_sender_task(tx_zabbix, config.zabbix.clone()));
// ── Web リアルタイム配信タスク ──
let tx_web = tx.subscribe();
tokio::spawn(web_publisher_task(tx_web, config.web.clone()));
// ── メインループ:10秒周期でPLC全レジスタ読み出し ──
let mut ticker = interval(Duration::from_secs(10));
let plc_addr = config.plc.address.parse()?;
loop {
ticker.tick().await;
match poll_all_sensors(&plc_addr, &config.plc.registers).await {
Ok(snapshot) => {
if tx.send(snapshot).is_err() {
tracing::warn!("No active receivers");
}
}
Err(e) => {
tracing::error!("PLC polling failed: {e}");
}
}
}
}
PLC 一括読み出し(MC Protocol 3E)
async fn poll_all_sensors(
addr: &std::net::SocketAddr,
registers: &[RegisterConfig],
) -> anyhow::Result<SensorSnapshot> {
// MC Protocol 3E でPLCに接続(自前実装のクライアント)
let mut client = McProtocolClient::connect(*addr).await?;
let mut readings = Vec::with_capacity(registers.len());
for reg in registers {
// デバイスメモリの一括読み出し(D レジスタ等)
let data = client.batch_read(reg.device, reg.start, reg.count).await?;
// レジスタ値を物理量に変換
let value = convert_registers(&data, ®.data_type, reg.scale);
readings.push(SensorReading {
zone_id: reg.zone_id.clone(),
item_key: reg.zabbix_item_key.clone(),
value,
unit: reg.unit.clone(),
});
}
Ok(SensorSnapshot {
timestamp: chrono::Utc::now(),
readings,
})
}
ポイント:
-
tokio::time::intervalは前回の実行完了からではなく 固定周期 でティックするため、処理時間の揺らぎに関わらず 10 秒間隔が維持される -
broadcast::channelにより、Poller は送信先の数を意識せずにデータを流せる。将来的にログ記録や別システム連携を追加する場合も、受信側をsubscribe()するだけ - PLC との MC Protocol 3E 接続は自前実装の非同期クライアントで処理。複数 PLC がある場合は
tokio::join!で並行読み出しも可能
副産物 ── 「きっちり 10 秒刻み」のきれいなデータ
Rust モジュールを導入したことで、当初は想定していなかった嬉しい副産物がありました。Zabbix に蓄積されるデータが、きっちり 10 秒単位で等間隔に並ぶ ようになったことです。
Zabbix ネイティブのポーリングでは、これが意外と実現できません。
[Zabbix ネイティブポーリングの場合]
10:00:00.000 ← 設定上は10秒間隔
10:00:10.347 ← サーバー負荷で 0.3 秒ずれ
10:00:20.012
10:00:30.891 ← 別ホストのポーリングと競合
10:00:40.234
10:00:51.567 ← 1秒以上のドリフト
...タイムスタンプが微妙にずれ続ける
[Rust モジュール経由の場合]
10:00:00
10:00:10
10:00:20
10:00:30
10:00:40
10:00:50
...きれいに10秒刻みで並ぶ
Zabbix のポーラープロセスは、数百〜数千のアイテムを限られたプロセス/スレッド数で順番にさばいています。監視対象が増えるほどポーリングキューが混雑し、各アイテムの取得タイミングにばらつきが生じます。10 秒間隔を設定しても、実際には 9.8 秒〜11.2 秒のようにジッターが乗るのが現実です。
Rust モジュールでは tokio::time::interval が OS のタイマー機構に基づいた固定周期を刻み、1 回のティックで全センサーを一括読み出し します。Zabbix 側はトラッパーとして受け取るだけなので、ポーラーの混雑とは無縁です。結果として、Zabbix のヒストリテーブルには 全アイテムが同一タイムスタンプで、きれいに 10 秒刻みのデータ が並びます。
この「きれいなデータ」は、見た目の気持ちよさだけでなく実用上のメリットもあります。
- 区画間の比較が正確になる:全ゾーンが同じ瞬間のスナップショットなので、「A 区画と B 区画の同時刻の値」を比較する際にタイムスタンプ補間が不要
- 集計・分析が単純になる:等間隔データは移動平均やピーク検出のアルゴリズムがシンプルになる(不等間隔の補正ロジックが不要)
- CSV エクスポートがそのまま使える:タイムスタンプが揃っているため、Excel 等で複数アイテムを横に並べたときに行がきれいに対応する
Zabbix 方向への送信
Rust が取得したデータは、パイプライン上の別サービスを経由して最終的に zabbix_sender プロトコルで Zabbix のトラッパーアイテムに送り込まれます。
Rust (SensorSnapshot)
→ 中間データストアに書き込み
→ バックエンドサービスが zabbix_sender CLI で Zabbix へ送信
本記事では概念的に「Rust → Zabbix」と表現していますが、本番環境では間にデータストアやバックエンドサービスが介在しています。設計上重要なのは Zabbix 自身はポーリングせず、外部からデータを受け取る(トラッパー)構成 であるという点です。
Zabbix 側では トラッパーアイテム として登録しておくことで、Zabbix のポーラープロセスを使わずにデータを受動的に受け取れます。
Web リアルタイム配信
リアルタイム表示用のデータは、Rust が生成した JSON をバックエンドサービス群が中継し、最終的に WebSocket でブラウザに配信します。
Rust (SensorSnapshot → JSON)
→ バックエンドサービス群(WebSocket サーバーを含む)
→ ブラウザ(React)
こちらも本番環境では複数のサービスが協調して動作しています。記事冒頭の注記の通り、本番は複数のマイクロサービスで構成されていますが、設計思想を伝えるために簡略化して記述しています。
このタスクにより、当社構成では PLC からのデータ取得完了から数百ミリ秒程度で React のダッシュボードに反映 されています。Zabbix のポーリング間隔に依存しないため、Web 画面のリアルタイム性は Rust モジュールの 10 秒周期がそのまま保証されます。
設定ファイルの設計 ── TOML による宣言的管理
データソースの追加・変更をコード修正なしで行えるよう、設定ファイルで宣言的に管理しています。
以下は設計思想を示すための簡略化した設定例です。本番環境の設定ファイルはサービスごとに分割されており、構造も異なります。
# config.toml(記事用の簡略例)
# ── PLC(MELSEC Q)との MC Protocol 3E 接続 ──
[plc]
address = "192.168.1.100:502"
[[plc.registers]]
zone_id = "1f-east"
zabbix_item_key = "power.kwh[panel-1f-east]"
start = 0x0000
count = 2
data_type = "uint32"
scale = 0.01
unit = "kWh"
[[plc.registers]]
zone_id = "1f-east"
zabbix_item_key = "power.demand[panel-1f-east]"
start = 0x0002
count = 2
data_type = "float32"
scale = 1.0
unit = "kW"
# ... 全区画分のレジスタ定義が続く
# ── ping による死活監視 ──
[[ping.targets]]
zone_id = "infra"
zabbix_item_key = "ping.rtt[gw-main]"
host = "192.168.1.1"
timeout_ms = 2000
[[ping.targets]]
zone_id = "infra"
zabbix_item_key = "ping.rtt[server-zabbix]"
host = "10.0.0.50"
timeout_ms = 2000
# ── HTTP/HTTPS によるコンテンツ取得・応答確認 ──
[[http.targets]]
zone_id = "service"
zabbix_item_key = "http.status[dashboard]"
url = "https://monitoring.example.com/health"
method = "GET"
timeout_ms = 5000
expect_status = 200
[zabbix]
server = "10.0.0.50:10051"
host_name = "plc-building-a"
[web]
port = 8080
PLC のレジスタも、ネットワーク機器の ping 先も、Web サービスの URL も──すべて同じ TOML ファイルに並列に定義されます。新しいデータソースが増えた場合、この TOML にエントリを追記してプロセスを再起動するだけで、次の 10 秒周期から全データ JSON に組み込まれます。
ハードウェア据付 ── 意外と語られない「物理層」の話
Qiita の記事では見落とされがちですが、電力監視システムの品質を大きく左右するのは 物理層の設計と施工 です。
MELSEC Q シリーズの現場構成例
実際の現場では、MELSEC Q シリーズを以下のようなモジュール構成で盤内に据え付けています。
[ベースユニット]
├── CPU モジュール (Q06UDV 等)
├── Ethernet モジュール (QJ71E71-100) ── Rust との MC Protocol 3E 通信
├── A/D 変換モジュール (Q64AD) ── アナログセンサー入力
├── 高速カウンタモジュール (QD62) ── パルス出力型電力量センサー
├── デジタル入力モジュール (QX42) ── ブレーカー状態等の接点入力
└── (空きスロット) ── 将来の増設用
この構成の利点は、計測対象が増えたときにモジュールを差すだけで対応できる ことです。たとえば新しいフロアの電力監視を追加する場合、A/D モジュールを 1 枚追加し、PLC のラダープログラムにレジスタ割り当てを追記し、Rust の TOML にレジスタ定義を追加するだけです。
施工品質がデータ品質を決める
- CT のクランプ方向を間違えると電力値が負になる(位相反転)
- センサーから PLC までのケーブル長がノイズ耐性に影響する
- 分電盤内のスペース制約により、CT のサイズ選定が限られる
- MELSEC のモジュール間配線やターミナルブロックの結線品質が、長期運転時のデータ安定性に直結する
当社ではソフトウェア開発だけでなく、電気通信工事の資格を持つエンジニアが現地据付まで一貫対応 しています。PLC の選定・モジュール構成設計・盤内配線・CT 取付・ケーブル敷設・そして Rust / React のソフトウェア開発──OT と IT の両方を一社で完結できることが、システム全体の整合性を担保しています。
第1回まとめ
本記事では、電力監視システムの全体アーキテクチャと、Rust によるリアルタイムデータ収集の設計を解説しました。
ここまでのキーポイント:
- 管理者向け(Zabbix UI)と利用者向け(React Dashboard)の画面分離が設計の出発点
- 最下層に三菱電機 MELSEC Q シリーズを採用。モジュール構成による柔軟な拡張性と、産業用コントローラとしての圧倒的な信頼性がデータ収集の起点
- Rust モジュールが 10 秒周期で PLC の全センサーを一括読み出しし、Zabbix と Web の両方にデータを分配 する「心臓部」
- GC なし・非同期ランタイム(Tokio)・broadcast チャネルにより、確実な周期実行と低レイテンシ配信を両立
- 副産物として、Zabbix ネイティブポーリングでは難しかった きっちり 10 秒刻みの等間隔データ が得られ、区画間比較や集計の精度が向上
- TOML による宣言的な設定管理で、センサー追加がコード修正不要
- OT(PLC 選定・モジュール構成・盤内配線・CT 取付)と IT(Rust / Web 開発)の一気通貫対応
次回は、Rust から送り込まれたデータを蓄積する Zabbix 側の設計──トラッパーアイテムの活用、ヒストリ/トレンドの使い分け、そして拠点情報の JSON 管理について掘り下げます。
→ 次回:【第2回】Zabbix を現地管理者の「計器盤」にする — 時系列 DB だけでは足りなかった理由
本シリーズは、筆者が所属するクイックイタレート株式会社での電力監視システム開発の知見をもとに執筆しています。関連する公開事例はこちら。
