BACnet ToolsでBACnetデバイスと通信

## BACnetとは

BACnet（バックネット）とは、ASHRAE（アメリカ暖房冷凍空調学会;アシュレ）などで標準化されたビルオートメーション用通信プロトコルです。
空調や衛生（給水・排水）、エレベータ、電気、防犯、防災（消火設備）などの装置間の通信において、広く利用されています。

もし、これらビル用装置からデータを取得したり、制御したりする場合、BACnetの知識が役に立ちます。
本稿では、BACnet ToolsでBACnetデバイスに接続し、いろいろ遊んでみます。

## BACnet Toolsとは

オープンソースで開発されているBACnetプロトコル用ライブラリとして、BACnet Stackがあります。
そのライブラリをデモとしてCLIツールを実装したものが、BACnet Toolsです。
Source Forge上では、Windows向けにコンパイルされた実行ファイルが提供されており、これを用いて簡単にBACnetデバイスと通信ができます。

プロジェクトのウェブページ

https://bacnet.sourceforge.net/

Windows向け実行ファイルの配布先

https://sourceforge.net/projects/bacnet/files/bacnet-tools/

## BACnetデバイスを見つける

### bacwi

ネットワークに接続しているBACnetデバイスを、bacwiで探します。
bacwiは、Who-Isサービスをネットワーク上に送信し、応答のあったデバイスのリストを出力します。

以下に、私の環境で実行した結果を示します。

```bacwi実行例
> ./bacwi.exe
;Device   MAC (hex)            SNET  SADR (hex)           APDU
;-------- -------------------- ----- -------------------- ----
  6367    ■■:■■:■■:■■:■■:■■    0     00                   1476
;
; Total Devices: 1
```

※出力結果のMACアドレスは「■」に置き換えています。

もしデバイスが見つからない場合、ネットワークインターフェースが正しく選択できていない可能性があります。
BACnetデバイスを接続しているネットワーク上の、自分のIPアドレスを以下のように環境変数`BACNET_IFACE`に設定します。
コマンド上の`"192.168.0.184"` は、自分のIPアドレスです。
また、PowerShell上での、環境変数の設定コマンドです。
環境に合わせて変えていください。

```powershell
$Env:BACNET_IFACE = "192.168.0.184"
```

bacwiにより、ネットワーク上のBACnetデバイスを見つけることができました。
デバイスIDが6367のBACnetデバイスがいるようです。

他のbacwiの使い方は、以下のコマンドで出力できます。
```
> ./bacwi.exe --help
```

### bacwh

ある特定のオブジェクトを持っているデバイスを探したい場合があるかもしれません。
その場合、Who-Hasサービスでデバイスを探す、bacwhが適しています。
探したいオブジェクトの、「オブジェクトタイプ オブジェクトインスタンス」か「オブジェクト名」を指定します。

私の環境における実行結果を以下に示します。

AnalogOutputオブジェクトで、インスタンス番号0のオブジェクトを持っているデバイスを探索
```
> ./bacwh analog-output 0
I-Have: analog-output 0 from device 6367!
```

オブジェクト名が「myAnalogOutput」のオブジェクトを持っているデバイスを探索
```
> ./bacwh myAnalogOutput
I-Have: analog-output 0 from device 6367!
```
いずれも、デバイスインスタンス6367が、探しているオブジェクトを持っていると、BACnetデバイスがI-Haveサービスで応答しています。

## BACnetデバイスからプロパティを取得する

### bacrp

では、見つけたBACnetデバイスから、プロパティを取得します。
1つのプロパティを取得する場合、bacrpを使います。

例えば、AnalogOutput#0の現在値を取得してみます。

```:bacrpの実行例1
> ./bacrp.exe 6367 analog-output 0 present-value
0.000000
```

オブジェクトのIDやプロパティのIDを数値で指定することもできます。
例えば、IDはそれぞれanalog-outputは1で、present-valueは85なので、以下のようになります。
```:bacrpの実行例2
> ./bacrp.exe 6367 1 0 85
0.000000
```

### bacrpm

bacrpでプロパティを一つづつ取得できますが、手間なので一度にたくさんのプロパティを取得したいです。
その場合、bacrpmを使います。

引数は順に以下です。
1. デバイスID
2. オブジェクト型
3. オブジェクトインスタンス番号
4. プロパティ番号（`,`で複数を連結可能）

プロパティ番号は例えば、現在値を示すpresent-valueは85となります。
配列の一部を取得するには、`87[0]`のようにカッコ`[]`で囲んでインデックス番号を指定します。
配列のすべてを取得するには、カッコを囲わずに指定します。

```:bacrpmの実行例
./bacrpm.exe 6367 analog-output 0 85,87[0],87
analog-output #0
{
    present-value: 0.000000
    priority-array: [0]16
    priority-array: {Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null}
}
```

複数のオブジェクトのプロパティを取得するには、デバイスID～プロパティ番号の組を、複数回繰り返して引数に指定します。

```:bacrpmでanalog-output#0とanalog-input#0の現在値を取得
./bacrpm.exe 6367 analog-output 0 85 analog-input 0 85
analog-output #0
{
    present-value: 0.000000
}
analog-input #0
{
    present-value: 0.000000
}
```

すべてのプロパティを取得するには、プロパティ番号として8を指定します。

```:bacrpmでanalog-output#0のすべてのプロパティを取得した結果
> ./bacrpm.exe 6367 analog-output 0 8
analog-output #0
{
    object-identifier: (analog-output, 0)
    object-name: "myAnalogOutput"
    object-type: analog-output
    present-value: 0.000000
    status-flags: {false,false,false,false}
    event-state: normal
    out-of-service: FALSE
    units: percent
    priority-array: {Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null}
    relinquish-default: 0.000000
    description: "hello analog output"
    reliability: no-fault-detected
    cov-increment: 1.000000
}
```



他に、選択可プロパティをすべて取得するには、プロパティ番号を80にします。
```
> ./bacrpm.exe 6367 analog-output 0 80
analog-output #0
{
    description: "hello analog output"
    reliability: no-fault-detected
    cov-increment: 1.000000
}
```

必須プロパティをすべて取得するには、プロパティ番号を105にします。

```
> ./bacrpm.exe 6367 analog-output 0 105
analog-output #0
{
    object-identifier: (analog-output, 0)
    object-name: "myAnalogOutput"
    object-type: analog-output
    present-value: 0.000000
    status-flags: {false,false,false,false}
    event-state: normal
    out-of-service: FALSE
    units: percent
    priority-array: {Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null}
    relinquish-default: 0.000000
}
```

## BACnetデバイスのプロパティを設定する
### bacwp
BACnetデバイスのプロパティに値を設定してみましょう。

引数は順に次のとおりです。
1. device-instance
1. object-type 
1. object-instance
1. property
1. priority
1. index
1. tag
1. value

5番目の引数であるpriorityは、設定する値の優先度を1～16で指定します。
16が最も低い優先度です。
優先度を0とすると、優先度を指定しないことを意味しますが、16に自動的に割り当てられます。

6番目の引数であるtagは、設定する値の型を指定します。
present-valueの型は小数（REAL）であるため、4を指定します。

以下に、AnalogOutput#0の現在値present-valueに100.0を設定する例を示します。

```
> ./bacwp.exe 6367 analog-output 0 present-value 16 -1 4 100.0
```
設定後のAnalogOutput#0の必須プロパティを確認してみます。
```
> ./bacrpm.exe 6367 analog-output 0 105
analog-output #0
{
    object-identifier: (analog-output, 0)
    object-name: "myAnalogOutput"
    object-type: analog-output
    present-value: 100.000000
    status-flags: {false,false,false,false}
    event-state: normal
    out-of-service: FALSE
    units: percent
    priority-array:{Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, Null,
        Null, Null, Null, 100.000000}
    relinquish-default: 0.000000
}
```

無事、present-valueが100.0になっていました。
また、priorityが自動的に16に割り当てられたためか、priority-arrayの16番目も100.0となっています。

### bacwpm

複数のプロパティに値を設定するWritePropertyMultipleサービスを実行するコマンドです。
詳細の説明は割愛します。

以下のコマンドでヘルプを見るとができます。

```
> ./bacwpm --help
```

## BACnteデバイスのプロパティ変更通知COVを設定する

値が変化した場合に通知する仕組みをCOV通知（Change Of Value）といいます。

COV通知を購読するには、bacscovを使います。
引数は以下です。

1. device-id: デイバスID
1. object-type: オブジェクトタイプ（文字列または数値）
1. object-instance: オブジェクトインスタンス番号
1. process-id: COV通知を受け取るプロセスID
1. <[un]confirmed lifetime|cancel>: 何秒間購読するか数値で指定、「cancel」で購読をキャンセルする

実際にBACnetデバイスにCOV通知を購読した例を示します。
AnalogInput#0のCOV通知を60秒間購読しました。
BACnetデバイスで値を変更したところ、COV通知がbacscovに通知されました。

```:COV通知を購読し、COV通知を受信した例
> ./bacscov 6367 analog-input 0 1 confirmed 60
Sent SubscribeCOV request.  Waiting up to 60 seconds....
SubscribeCOV Acknowledged!
CCOV: Received Notification!          <-COV通知購読時に受け取った通知
CCOV: PID=1 instance=6367 analog-input 0 time remaining=60 seconds
CCOV: present-value
CCOV: status-flags
CCOV: Sending Simple Ack!
CCOV: Received Notification!          <-値の変更時に受け取った通知
CCOV: PID=1 instance=6367 analog-input 0 time remaining=50 seconds
CCOV: present-value
CCOV: status-flags
CCOV: Sending Simple Ack!
```

## 付録1：EPICSファイルを出力する

EPICSファイルというBACnetデバイスのオブジェクトの一覧を定義したファイルがあるそうです。
電気設備学会のガイドラインでは、CSVによりオブジェクト一覧を記すフォーマットがありますが、そのようなものだと思います。
とりあえず、オブジェクトの一覧を取得できるので、大変便利です。

以下は実行例です。（長いので一部抜粋）
```:bacepicsの実行例
> ./bacepics 6367
PICS 0
BACnet Protocol Implementation Conformance Statement
...
List of Objects in Test Device:
{
  {
    object-identifier: (device, 6367)
    object-name: "my-bacnet-device"
    object-type: device
...
    ip-subnet-mask: FFFFFF00
    ip-default-gateway: C0A80001
    ip-dns-server: { C0A80001,00000000,00000000 }
  }
}
End of BACnet Protocol Implementation Conformance Statement
```

## 付録2：デモサーバを起動

BACnet Toolsには、BACnet接続の練習用として、デモサーバであるbacservが同梱されています。

デモサーバ用の起動前に、ポート番号を指定しておきます。
同じパソコン上でデモサーバとクライアントを開こうとすると、デフォルトのポート番号である47808をお互いに取り合おうとするからです。
そのため、デモサーバのポート番号を47809に変更し、
デフォルトのポート番号47808はGUIツールであるYet Another Bacnet Explorer用に残しておきます。

PowerShellでは、以下のコマンドでポート番号を指定する環境変数を変更できます。

```powershell:ポート番号を指定
$ENV:BACNET_IP_PORT = 47809
```

ポート番号を指定後、デモサーバを起動します。

```:デモサーバの起動例
> .\bacserv.exe 123
BACnet Server Demo
BACnet Stack Version 1.3.4
BACnet Device ID: 123
Max APDU: 1476
BACnet Device Name: SimpleServer
```
Yet Another Bacnet Explorerでデモサーバに接続した例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105381/ffd06265-02fe-d4fc-770e-ec1d0fe0e302.png)