オフィスビルの天井裏には、部屋ごとにファンコイルユニット(FCU)が入っている。冷温水を通したコイルにファンで風を当てて室内を冷暖房する、空調設備のいちばん末端の機器だ。そしてその FCU 1 台ごとに、発停・風量切替・バルブ開度を受け持つ小さな専用コントローラが付いている。
このコントローラが何を喋っているかというと、多くの場合 BACnet MS/TP である。ビル管理システム(BAS)の世界では標準的なオープンプロトコルで、物理層は RS-485。つまり「既設の空調設備から値を読みたい」という IoT 案件は、かなりの確率で RS-485 の差動 2 線と BACnet のオブジェクトモデルを相手にすることになる。
以前まとめた IoTセンサーの種類と選び方 では、信号インターフェースの 1 つとして「産業用シリアル(RS-485 / Modbus RTU)」を挙げ、既設設備から値を読むならここは避けて通れないと書いた。この記事はその続きで、ビル空調側の実物 — FCU コントローラと BACnet MS/TP — に降りていく。扱うのは次の 4 点だ。
- FCU コントローラの実仕様(アズビル 形WJ-1202)と、機能を決める形番の読み方
- BACnet MS/TP の中身 — トークンパッシング、Max_Master、120 Ω 終端、台数設計
- IoT の接続点を BACnet/IP 側に置く理由と、BAC0(Python)での読み出し
- 繋がらないときの切り分け — mstpcap + Wireshark
全体像 — FCU コントローラから BACnet/IP までの 3 層構成
先に構成を押さえておく。FCU コントローラ単体を見ても、IoT からどう触るかは見えてこない。効いてくるのは「現場側の入出力」「RS-485 幹線」「BACnet/IP 側」という 3 層の関係だ。
- 現場側 — FCU 本体、温度センサ、居住者が触る設定器、汎用の接点入出力。ここは BACnet ではなく、センサ線や専用シリアルの世界。
- RS-485 幹線 — FCU コントローラや VAV(Variable Air Volume、可変風量)コントローラが 1 本の線にマルチドロップで接続される。ここが BACnet MS/TP。
- BACnet/IP 側 — 幹線を束ねる上位コントローラが MS/TP と IP を橋渡しし、中央監視装置や IoT ゲートウェイが乗る。
IoT でデータを取りに行く場合、現実的な接続点は 3 層目の BACnet/IP になる。理由は後述する。
FCU コントローラの実仕様 — アズビル 形WJ-1202
抽象論だと掴みづらいので、実際の製品仕様を見る。アズビルの FCU コントローラ、形番 WJ-1202 の仕様書(AI-7637)から主要なところを抜き出すと次のようになる。
| 項目 | 仕様 |
|---|---|
| 入力電圧 | 形WJ-1202A: AC100 V(AC90〜110 V)/ 形WJ-1202W: AC100〜240 V(AC85〜264 V) |
| 消費電力 | 6 VA 以下(形WJ-1202W1*01・WJ-1202W4*01)/ それ以外 7 VA 以下 |
| CPU | 32 bit |
| 通信方式 | BACnet MS/TP(SmartScreen2 システム接続時は専用通信) |
| 通信速度 | 9.6 / 19.2 / 38.4 / 76.8 kbps(初期値 76.8 kbps) |
| 通信距離 | 1000 m 以下 |
| 温度入力 | 測温抵抗体 Pt100(形WJ-1202**0*1)/ Pt1000(形WJ-1202**K*1) |
| 周囲温度(動作) | 0〜50 ℃ |
| 質量 | 0.23 kg または 0.52 kg(形番による) |
制御としてやることは、室内温度を設定値に保つようにバルブを開閉すること。バルブ制御には ON/OFF 制御タイプと比例制御タイプがあり、比例タイプにはさらに「還温度制御機能」付きの形番がある。これはファンコイルの還水温度(コイルから戻る水の温度)を測り、還水温度を設定値以上に保ちながら室温を維持するという制御で、狙いは過流量の防止と熱源側の搬送動力の削減にある。空調の省エネは末端の流量制御でかなり決まるので、この手の機能が形番のバリエーションとして用意されている。ビル設備の IoT を用途別に整理した 建築設備メンテナンス視点での分類 でいえば、ここはエネルギー最適化・BEMS 連携のレイヤに直結する部分だ。
仕様表で目を引くのは 通信距離 1000 m と 温度入力が測温抵抗体という点だ。前者は RS-485 の素性そのままで、後者は「センサは 4〜20 mA でも I2C でもなく Pt100/Pt1000」というビル設備の伝統的な流儀を示している。IoT 側の感覚では「センサを足せばいい」と考えがちだが、実際には形番ごとに入力の種類が固定されている。
形番が仕様を決める
WJ-1202 のようなビル設備機器で厄介なのは、形番の各桁が機能を表すことだ。仕様書には次のような但し書きが並ぶ。
- バルブ ON/OFF タイプは外部接点なしのみ(形WJ-1202W1*01・WJ-1202W4*01)
- スタンドアロン仕様の比例制御・還温度制御付き比例制御は外部接点ありのみ(形WJ-1202*5*11・WJ-1202*6*11)
- 還水温度入力コネクタがあるのは形WJ-1202*3**1・WJ-1202*6*11
- デジタル出力/デジタル入力端子台があるのは形WJ-1202***11
つまり「この現場の FCU コントローラから DI の状態も取れるか」は、製品名ではなく銘板の形番を読まないと分からない。既設設備の調査で最初にやるべきは、盤を開けて形番を全部控えることになる。
BACnet MS/TP — RS-485 の上を走るデータリンク層
BACnet MS/TP(Master-Slave/Token-Passing)とは、BACnet を RS-485 の上で走らせるためのデータリンク層である。ここからはそのプロトコルの中身を見ていく。
トークンパッシング — MS/TP が衝突を避ける仕組み
イーサネットのような衝突検出ではなく、トークンを回して送信権を渡す方式をとる。トークンを持っているデバイスだけが送信でき、用が済んだら次の MAC アドレスへ渡す。半二重の 2 線式でぶつからずに複数機器が喋るための、素直な解決策である。
代償として、帯域のかなりの部分がプロトコルのオーバーヘッドに消える。76.8 kbps という数字を見て「1 秒に何点読めるか」を単純計算しても、その数字はまず当たらない。実効スループットはトークンの周回時間に支配され、それは台数と各機器の応答時間で決まる。
Max_Master の効き方
MS/TP の設計で最も効くパラメータが Max_Master だ。これは「ピアを探すときに、どの MAC アドレスまで探しにいくか」の上限を指す。マスタの MAC アドレスは 0〜127 の範囲で、Max_Master の既定値は 127 になっていることが多い。
問題は、アドレス空間の「穴」を定期的に探しにいく動作にある。各マスタは自分の次のマスタまでの空きアドレスに対して Poll For Master(PFM)フレームを投げ、応答がなければタイムアウトを待つ。この PFM サイクルはトークンが 50 回渡るごとに開始され、穴の中のアドレスが順に確認される。MAC 1〜10 の 10 台しか繋がっていない幹線を考えてみる。Max_Master を 127 のままにしておくと、11〜127 という 117 個ぶんの空振りとタイムアウトを定期的に払い続けることになる。Max_Master を実際の最大 MAC に合わせて下げると、この分の無駄が消える。
逆方向の副作用もある。Max_Master を下げすぎると、それより大きい MAC の機器を発見できなくなる。あとから MAC 100 の機器を増設したのに Max_Master が 64 のままなら、その機器は永久にトークンを受け取れない。増設の余地を残した値にしておくのが無難だ。
もう 1 つ、Max_Master は 全機器で同じ値に揃える必要がある。1 台だけ 127 のまま残っていると、その機器が上位アドレスを探し続けて効果が消える。ボーレートについても同様で、幹線上の全機器が同一速度でなければ通信は成立しない。
RS-485 の物理配線 — 分岐不可、両端に 120 Ω 終端抵抗
RS-485 は差動 2 線のマルチドロップだが、スター配線・分岐はできない。デイジーチェーンで数珠つなぎにし、幹線の両端に終端抵抗を入れる。WJ-1202 の仕様書でも、終端に 120 Ω を接続することと、専用の終端抵抗部品(形番 83162637-005 / 006)を使うことが明記されている。分岐が必要な場合は RS-485 T 分岐ユニット(形番 84507910-001)を使えとあり、任意の場所で線を割いてよいわけではない。
なおコネクタは RJ-45 モジュラで、LAN ケーブルで配線できるようになっている。見た目はイーサネットだが中身は RS-485 なので、間違ってスイッチングハブに挿さないよう注意がいる — 現場では実際に起きる事故である。
台数設計 — MS/TP 1 幹線に何台つなげるか
「1 本に何台つなげるか」は、RS-485 の電気的な上限と、BACnet MS/TP の帯域と、機器ごとのオブジェクト数の 3 つで決まる。アズビルの仕様書は上位のジェネラルコントローラ側の条件として次のように書いている。
- 自社デバイスのみ(VAV コントローラ、FCU コントローラ、小型リモート I/O モジュール、熱量演算器など): 50 台/CH
- ただしジェネラルコントローラ 1 台あたりセカンダリデバイス 70 台、自社 VAV + 自社 FCU コントローラ 50 台という制約がある
- 他社デバイスのみ(伝送速度 76.8 kbps、オブジェクト数 30 点/1 デバイスの場合): 31 台/CH
- Modbus の場合(同条件): 31 台/CH
自社機器 50 台に対して他社機器 31 台という差は、台数上限が電気的制約ではなく帯域で決まることを示している。「オブジェクト数 30 点/1 デバイスの場合」という条件が付いているのがその証拠で、1 台あたりの点数が増えれば収容台数は減る。マルチベンダーの幹線を設計するときは、カタログの最大台数ではなく総オブジェクト数で見積もる必要がある。
ちなみにジェネラルコントローラの RS-485 幹線は 2 CH あり、CH ごとに BACnet MS/TP / Modbus RTU / Modbus ASCII を選べる。同一 CH に自社デバイスと他社デバイスを混在させると台数条件が変わるので、可能なら CH を分けたほうが設計は楽になる。
IoT から読む — なぜ BACnet/IP 側に寄せるのか
さて本題。この FCU コントローラ群から値を取ってクラウドに上げたい、というときにどこへ繋ぐか。
選択肢は 2 つある。RS-485 幹線に直接ぶら下がって MS/TP を喋るか、上位の BACnet/IP に乗るか。実務ではほぼ後者になる。
理由は 2 つある。
1 つは実装コスト。Python の BACnet ライブラリとして定番の BAC0(および基盤の BACpypes)は BACnet/IP を前提にしており、MS/TP を直接喋る機能は持っていない。MS/TP を扱いたければシリアルスタックを自前で用意するか、MS/TP ⇔ BACnet/IP のルータを噛ませることになる。ルータは市販品があり、後述するようにビル側にもともと上位コントローラが入っていることが多い。
もう 1 つは運用リスク。稼働中の MS/TP 幹線に機器を追加するのは、単に線を割いてぶら下げれば済む話ではない。MAC アドレスの重複、Max_Master の不整合、終端抵抗の位置、トークン周回時間の悪化 — どれも空調そのものを止めうる。すでに BAS が動いている幹線に IoT 機器を割り込ませるのは、原則として避ける。
構成図の右側で IoT ゲートウェイを BACnet/IP 側に置いているのは、この 2 点による。なおゲートウェイから上位(クラウド)への区間は別の問題系で、機械室や地下ピットに置いた機器の回線が切れる話は SORACOM のセッションが Deleted → Created を繰り返す にまとめてある。
BAC0 でルータ配下の MS/TP 機器を読む
BAC0 は近年 async に移行したので、コードは async 前提で書く。ドキュメントの記法に沿うと、起動は async コンテキストマネージャを使う。
import asyncio
import BAC0
async def main():
async with BAC0.start(ip="192.168.1.10/24") as bacnet:
# MS/TP ⇔ BACnet/IP ルータを登録する。
# タプルの 2 番目が、そのルータ配下の MS/TP ネットワーク番号。
await bacnet.use_router(("192.168.1.2:47808", 303))
# 登録済みネットワークに Who-Is をかけてデバイスを探す
# (`_discover` は先頭アンダースコアだが、await して使うのはこちら)
await bacnet._discover(networks="known")
asyncio.run(main())
ここで出てくる ネットワーク番号が MS/TP を扱ううえでの肝になる。BACnet/IP のセグメントと MS/TP 幹線はルータで分けられた別ネットワークなので、MS/TP 側の機器を指すには「ネットワーク番号 : MAC アドレス」で書く。ネットワーク番号はルータ側の設定値なので、機器を探す前にルータの設定を確認する必要がある。
プロパティの読み出しは、アドレス・オブジェクト種別・インスタンス番号・プロパティ名をスペース区切りの 1 文字列で渡す。
# いずれも上の main() の `async with` ブロック内での続き
# BACnet/IP 上の機器を直接読む
value = await bacnet.read("192.168.1.100 analogValue 4410 presentValue")
# -> 24.5
# ネットワーク番号 303 の MS/TP 幹線の、MAC 9 番の機器を読む
room_temp = await bacnet.read("303:9 analogInput 1 presentValue")
# -> 26.3
# 複数プロパティをまとめて読む(プロパティの並び順に値が返る)
props = await bacnet.readMultiple("303:9 analogValue 1 presentValue units description")
# -> [24.0, 'degreesCelsius', 'Room Temp Setpoint']
FCU コントローラなら、室温は analogInput か analogValue、温度設定値は analogValue、発停や風量段は binaryValue / multiStateValue に割り当てられているのが典型的な構成だ。ただしどのオブジェクトの何番が何なのかは機器固有で、標準では決まっていない。機器の BACnet 通信仕様書 — PICS(Protocol Implementation Conformance Statement)やオブジェクト一覧 — を入手するのが正攻法である。それが手に入らない現場では、_discover() の結果からオブジェクトを列挙し、description や objectName を読んで当たりを付けることになる。
ポーリングか COV(Change of Value)か
値の取り方には、周期的に ReadProperty を投げるポーリングと、値が変化したときだけ通知を受ける COV(Change of Value)購読の 2 通りがある。
MS/TP の帯域を考えると COV のほうが筋がいい。室温のような遅い物理量を数秒周期でポーリングするのは、トークン周回時間を食いつぶすだけで得るものが少ない。ただし COV は機器側の対応が必須で、購読数にも上限がある。「COV に対応しているか、何点まで購読できるか」は、これも機器の仕様書マターになる。
ポーリングにするなら、周期は分オーダーから始めるべきだ。LTE-M 機の死活監視の話でも書いたとおり、監視の分解能はサンプリング周期で決まる。逆に言えば「何分の遅れまで許容できるか」を先に決めれば、必要な周期は自ずと決まる。空調の室温なら、多くの用途で 1〜5 分で足りる。
現場で切り分ける — mstpcap と Wireshark
繋がらないときに手が止まるのが MS/TP の辛いところで、イーサネットのように気軽にパケットを見られない。ここで使えるのが Steve Karg の bacnet-stack に含まれる mstpcap だ。
Wireshark 2.0 で入った extcap の仕組みを使う。Wireshark の「Help > About」で extcap フォルダの場所を確認し、そこに mstpcap を置いて再起動する。SourceForge で配布されているのは Windows 向けの mstpcap.exe だ。するとキャプチャ対象のインターフェース一覧に、シリアルポートごとの「BACnet MS/TP」が現れる。
あとは USB-RS485 変換アダプタを(受信専用で)幹線に接続し、ボーレートを合わせてキャプチャを開始すれば、トークンの流れも ReadProperty のやりとりも Wireshark で見える。
これが見えると、切り分けが一気に楽になる。
- トークンがそもそも回っていない → 配線・終端抵抗・ボーレート不一致を疑う
- Poll For Master ばかり流れている → Max_Master の設定を疑う
- 特定の MAC だけ応答しない → その機器のアドレス設定・電源を疑う
- 周回はしているが遅い → 台数とオブジェクト数の見積もりを疑う
「通信できません」を「トークンが回っていません」まで具体化できれば、原因の候補は数個に絞られる。
マルチベンダーの現実 — Johnson Controls / Metasys
BACnet がオープンプロトコルであることの意味は、別ベンダーの機器を同じ幹線に載せられるという点にある。実際、アズビルのシステム構成図にも「他社 BACnet MS/TP デバイス」という枠がはっきり描かれている。
たとえば Johnson Controls の Metasys 系では、FEC(Field Equipment Controller)や VMA、FAC といったフィールドコントローラが N2(Johnson Controls 独自のフィールドバス)と BACnet MS/TP を切り替えて使えるようになっている。FEC 系はファンコイルや熱源のような単純な制御から、より高度なプラント管理までをカバーする製品群だ。
ここで注意しておきたいのが、レガシー機器の製造終了という時間軸だ。Johnson Controls は 2019 年からの CG / CV シリーズ投入に伴い、旧来の VMA・FAC・FEC・IOM・FCU 系モデルの製造終了計画を告知している。ビル設備は 10 年、20 年と使われるので、既設調査で出てくる型番が現行品とは限らない。
マルチベンダー環境で実際に効いてくるのは、次の点だ。
- オブジェクトの意味づけはベンダー任せ — プロトコルが同じでも、室温がどのオブジェクトに入るかは揃わない。上位で正規化するレイヤが要る。
- 同一幹線混在で台数条件が変わる — 前述のとおり自社 50 台/他社 31 台のような差が出る。混ぜるなら総オブジェクト数で見積もる。
- ボーレートと Max_Master を全機器で合わせる — ベンダーごとに既定値が違うので、混在時に最初に踏む地雷になりやすい。
まとめ
FCU コントローラは、ビル空調の末端にいる地味な機器だが、IoT から既設設備を触ろうとしたときの入口になる。押さえておくべき点を整理する。
- 形番がすべて — アズビル WJ-1202 のような機器は、バルブ制御方式も DI/DO の有無も温度入力の種類も形番で決まる。現地調査は銘板の形番を控えるところから。
- MS/TP は帯域とトークンで考える — 76.8 kbps という数字ではなく、台数 × オブジェクト数とトークン周回時間で設計する。Max_Master とボーレートは全機器で揃える。
- 配線は分岐不可、両端に 120 Ω — RJ-45 コネクタでも中身は RS-485。デイジーチェーンと終端抵抗の原則は変わらない。
- IoT の接続点は BACnet/IP 側に置く — BAC0 / BACpypes は BACnet/IP 前提。稼働中の MS/TP 幹線に割り込むのは空調を止めるリスクがある。ルータ配下のネットワーク番号を指定して読む。
- 切り分けは mstpcap + Wireshark — MS/TP が見えないまま推測で触ると時間が溶ける。
- オブジェクトの意味づけは機器固有 — 標準で決まっているのはオブジェクト種別とプロパティ名までで、「何番が室温か」は仕様書を取るしかない。
空調設備の世界は、IoT の感覚からすると保守的に見える。だが 1000 m の配線、0〜50 ℃ の動作環境、20 年の運用寿命という要求を満たしたうえでオープンプロトコルに乗っているという意味では、むしろ相当よくできた仕組みだと思う。値を取りに行く側は、その前提に合わせて設計するほうが早い。
