CoolMasterのBACnetデータポイントをPythonを通して操作してみた

はじめに

CoolMasterにはBACnetを通したコントロールが可能という機能が備わっています。ネットワーク経由でBACnetのデータポイントを操作をすることで、エアコンの温度を変更するといった操作などが可能です。

全体の流れとしては、YABEを用いてデータポイントの確認→Pythonでデータポイントを操作してみるという流れです。

YABEを使用してデータポイントを確認してみる

YABEというBACnetのシュミレーターツールを使用することで、どのようなデータポイントが存在しているのか確認することが可能なので、まずはそこから始めてみます。

YABEを起動して[Add device]をクリック

Portに[BAC0]を指定、[Local endpoint]にYABEを起動しているPCのIPアドレスを入力します。入力したら[Start]をクリック。

※通信に使用しているポートが先に使われていると表示させたいデバイスが表示されないことがあります。その場合は、先にポートを占有しているアプリなどのタスクキルをしてみてください。

CoolMasterのデバイスが表示されました。

様々なデータポイントが確認できますが、このままだとそのデータポイントが何を示しているのか名称から判断できないので、一度カーソルを触れさせてあげる必要があります。一番上のCoolMaster-64をクリックした状態にします。

一番上のCoolMaster-64をクリックして選択された状態からカーソルキーの↓を長押しすると、なぞられたものから名称が変化して正式な名称に変化します。

温度設定などを確認してみたいので、[L1.101 set_temp (AV:256)]を右クリックして[Subscribe]を選択。

Valueに27と数値が入っているのが見えます。いま、事務所のエアコンを27度でONしている状態なので温度設定(Set Point)としての数値がYABEから確認することができました。

YABEからBACnet通信で温度変更をさせる場合は、[Properties]から[Present Value]の値を変更してEnterを押してみてください。こちらの値を変更してエアコンの温度設定を変更することができれば、BACnet通信が問題なく動作しているということになります。

Pythonから操作してみる

YABEから行っていた動作のように、Visual Studio CodeのPythonからプログラムを実行することでSet Pointの確認と変更をやってみます。

環境構築として、[BACpypes]というプラグインがインストールされている必要がありますので、[pip install bacpypes]のようなコマンドからインストールします。

[coolmaster_bacnet.py]というファイルを生成し、下記のようなソースコードを用意します。

ソースコード:modbus_control.py

import threading
from bacpypes.app import BIPSimpleApplication
from bacpypes.object import AnalogValueObject
from bacpypes.apdu import ReadPropertyRequest, WritePropertyRequest
from bacpypes.primitivedata import ObjectIdentifier, Real
from bacpypes.constructeddata import Any
from bacpypes.pdu import Address
from bacpypes.core import run, enable_sleeping, stop
from bacpypes.iocb import IOCB
import time

# BACnet 設定
BACNET_DEVICE_IP = "192.168.1.100"  # CoolMaster の IP
DEVICE_ID = 64  # CoolMaster の BACnet Device ID
SETPOINT_OBJECT_ID = "analogValue:256"  # YABE で確認したオブジェクトID

# PC 側のローカル BACnet アドレス(YABEと同じものにする)
LOCAL_BACNET_IP = "192.168.1.4"  # PC のローカルIP

# BACnet クライアント
class BACnetClient(BIPSimpleApplication):
    def __init__(self, local_address):
        super().__init__(None, local_address)

    def read_setpoint(self):
        """セットポイントを取得"""
        print("[INFO] Setpoint を取得中...", flush=True)
        request = ReadPropertyRequest(
            objectIdentifier=ObjectIdentifier(SETPOINT_OBJECT_ID),
            propertyIdentifier="presentValue"
        )
        request.pduDestination = Address(BACNET_DEVICE_IP)

        iocb = IOCB(request)
        self.request_io(iocb)  # 修正: `self.request()` → `self.request_io()`
        
        def on_response(iocb):
            if iocb.ioError:
                print(f"[ERROR] 読み取りエラー: {iocb.ioError}", flush=True)
            else:
                value = iocb.ioResponse.propertyValue.cast_out(Real)
                print(f"[INFO] 現在のセットポイント: {value}°C", flush=True)

        iocb.add_callback(on_response)  # 修正: 非同期で処理

    def write_setpoint(self, new_value):
        """セットポイントを変更"""
        print(f"[INFO] Setpoint を {new_value}°C に変更中...", flush=True)
        request = WritePropertyRequest(
            objectIdentifier=ObjectIdentifier(SETPOINT_OBJECT_ID),
            propertyIdentifier="presentValue",
            propertyValue=Any(Real(new_value))
        )
        request.pduDestination = Address(BACNET_DEVICE_IP)

        iocb = IOCB(request)
        self.request_io(iocb)  # 修正: `self.request()` → `self.request_io()`

        def on_response(iocb):
            if iocb.ioError:
                print(f"[ERROR] 書き込みエラー: {iocb.ioError}", flush=True)
            else:
                print(f"[INFO] Setpoint を {new_value}°C に変更しました", flush=True)

        iocb.add_callback(on_response)  # 修正: 非同期で処理

# BACnet クライアントの作成
client = BACnetClient(Address(LOCAL_BACNET_IP))

# BACnet イベントループを別スレッドで実行
def start_bacnet_loop():
    enable_sleeping()
    print("[INFO] BACnet イベントループ開始...", flush=True)
    run()  # イベントループの開始

# バックグラウンドスレッドで `run()` を実行
thread = threading.Thread(target=start_bacnet_loop, daemon=True)
thread.start()

# メイン処理(セットポイントの取得 & 変更)
try:
    time.sleep(2)  # BACnet の準備を待つ
    client.read_setpoint()  # 現在のセットポイントを取得
    time.sleep(2)
    client.write_setpoint(27.0)  # 設定したい温度を入力
    time.sleep(2)
    client.read_setpoint()  # 変更後のセットポイントを取得

    while True:
        time.sleep(1)  # メインスレッドを維持(Ctrl+Cで抜けられるようにする)

except KeyboardInterrupt:
    print("\n[INFO] ユーザーが停止しました。BACnetを終了します。", flush=True)
    stop()  # BACnet を安全に停止

実行結果

プログラムを実行すると、「現在のセットポイントの数値取得して表示→プログラム内の数値にSet Pointを変更」という流れで動作します。

実際に動作させたい場合は、データポイントはIPアドレスなどの設定を各環境に合わせて編集してみてください。

まとめ

Pythonを通してBACnetからエアコンの温度変更をさせるということをやってみました。YABEで通信が通っていれば、あとはプログラムが動作するように設定を頑張るといった具合でした。

ここでのポイントとしては、デバイスとしてのIDやデータポイントの数値などを確認することが重要だと思いました。動作させたいデータポイントを調べるために、YABEを使用したというわけです。

「万能」とまではいかないのかもしれませんが、基本的な調べ方と動作のさせ方としてはこちらのやり方で問題なさそうです。