たけびしDxpSERVER向けOPC-UAクライアントを書いてみた

たけびしDxpSERVERのOPC-UAクライアントのサンプルを作ることになったので、手順を記録しておく。

# 1. 使用環境

* Windows10
* [たけびしデバイスエクスプローラ OPCサーバー バージョン6 (DxpSERVER)](https://www.faweb.net/product/opc/)

デモ版が上記サイトからダウンロード可。OPC-UAも利用可。

# 2. OPC-UAサーバ設定

## 2-1. 共通プロパティ

**ツール(T)>オプション(O)**で、共通プロパティタブを表示する。

![dxp6_property.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110430/c271f992-2bb7-841b-c923-dc17030d0a80.png)

`OPC UAサーバー設定`を有効にする。アプリケーションを再起動するように促されるので、再起動する。

## 2-2. UAサーバー設定

共通プロパティタブの`OPC UAサーバー設定`の右側に`...`が表示されているところをクリックすると、UAサーバー設定のダイアログが表示される。

![dxp6_uaprop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110430/dd1a3a2e-61e1-6873-7dd4-1874a2999a5b.png)

* UAサーバー使用
* Anonymous接続の許可
* セキュリティポリシー/セキュリティモード：None

にチェックが入っていること。

## 2-3. タグの追加

![dxp6_tag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110430/21c826a5-58e7-fdc3-b79c-2a6b8cd490c4.png)

プロジェクトエクスプローラからSYSTEMポートを選択して、タグを追加する。
タグの追加は **プロジェクト(P)>新規作成(N)>タグ(T)** で行う。

ここでは、以下の３つのタグを作っておく。

| No. | タグ名 | データ型 | サイズ |
|---:|---|---|---:|
| 1 | value_string | STRING | 32 |
| 2 | value_long | Long | 1 |
| 3 | value_float | Float | 1 |

注意事項：

* STRINGのサイズはデフォルトで1なので、そのままでは１文字しか保持できない
* 一度タグを設定してからサイズだけ変更すると、後続のタグのアドレス位置が移動いないので、領域を上書きされる場合がある（これでハマった）


## 2-4. プロジェクトの保存

**ファイル(F)>保存(S)**でプロジェクトを保存する。ここでは `OPCUA_sample`で保存する。

# 3. OPC-UAクライアント

## 3-1. OPC-UAサーバーの設定

DxpSERVER v6のデフォルト設定は以下の通り。

| No. | 項目名 | デフォルト値 | 内容 |
|---:|---|---|---|
| 1 | HOST | - | DxpSERVERのIPアドレス文字列。`localhost`も可。 |
| 2 | PORT | 52240 | ポート番号 |
| 3 | PATH | (空文字列） | end_pointのパス |
| 4 | URI | `urn:Takebishi:DxpOpcUaServer:customprovider` | 名前空間のURI |

上記の設定値から、OPC-UAのアクセス先を示すend_pointは以下の文字列で設定する。

```
end_point = f'opc.tcp://{HOST}:{PORT}{PATH}'
```

## 3-2. パッケージのインストール

asyncuaパッケージをインストールしておく。

```
$ pip install asyncua
```

## 3-3. サンプルプログラム

以下に示す。

```python:dxp6_client.py
import asyncio
from asyncua import Client, ua

# for v5
# URI = 'http://takebishi.co.jp/DeviceXPlorer5/UA'
# PORT = 52210

# for v6
URI = 'urn:Takebishi:DxpOpcUaServer:customprovider'
PORT = 52240

# local machine
HOST = 'localhost'
PATH = ''

OBJ = 'SYSTEM'  # オブジェクト名はプロジェクト直下のポート名と対応


async def main():
    end_point = f'opc.tcp://{HOST}:{PORT}{PATH}'

    try:
        async with Client(url=end_point) as client:
            """
            # URI一覧を表示
            print('[URI List]')
            for uri in await client.get_namespace_array():
                print(uri)
            print()
            """

            # ノードのread/write
            idx = await client.get_namespace_index(URI)
            node1 = client.get_node(f'ns={idx};s={OBJ}.value_string')
            node2 = client.get_node(f'ns={idx};s={OBJ}.value_long')
            node3 = client.get_node(f'ns={idx};s={OBJ}.value_float')

            print('[Read]')
            print(node1.nodeid, ':', await node1.read_value())
            print(node2.nodeid, ':', await node2.read_value())
            print(node3.nodeid, ':', await node3.read_value())
            print()

            # DxpSERVERだとこの形式が未サポート
            # await node1.write_value('HELLO')

            # Write
            # DataValueでset_valueを使用する
            print('[Write]')
            dv1 = ua.DataValue(ua.Variant(u'HELLO', ua.VariantType.String))
            await node1.set_value(dv1)
            dv2 = ua.DataValue(ua.Variant(123, ua.VariantType.Int32))
            await node2.set_value(dv2)
            dv3 = ua.DataValue(ua.Variant(9.8, ua.VariantType.Float))
            await node3.set_value(dv3)
            print()

            print('[Read]')
            print(node1.nodeid, ':', await node1.read_value())
            print(node2.nodeid, ':', await node2.read_value())
            print(node3.nodeid, ':', await node3.read_value())
            print()

    except Exception as e:
        print(e)
        exit()


if __name__ == '__main__':
    asyncio.run(main())
```

read/writeについては、ここで示した方法で動作することを確認した。

## 3-4. コードについて補足

コメントにも書いているので、察した方もいらっしゃるかも知れませんが補足しておきます。

* URIのリストはclient.get_namespace_array()で取得できる
* node.write_value()で書き込みができたOPC-UAサーバーもあるが、DxpSERVERではDataValueをnode.set_value()で設定する方法しか確認できなかった