OPC UAで最低限の読み書き

#概要
本記事ではOPC UAサーバー側のMyObject > MyVariableに対して、値を書き込み、読み出す例を示しながら、
OPC UAサーバーに対してデータを書き込み、読み出しするために最低限必要なOPC UAパケットのパラメータを示す。

Python用OPC UA Serverライブラリを使用したサーバーに対してUaExpertで接続、読み書きを行った時の通信パケットを取得する。
そのパケットを一部変更したものをサーバーに送信して、正常に通信させた。その際の備忘録を以下に示す。

**※注意：OPC UAの仕様を確認したわけではなく、個人的に解析しているため、本記事の内容がすべてのOPC UAサーバーに適用できるとは限りません。**

説明の流れは下記

- サーバー側準備

- セッション確立（クライアント側）

- 書き込み（クライアント側）

- 読み出し（クライアント側）

#環境
OPC UA Server : https://github.com/FreeOpcUa/python-opcua
Python3 : Python 3.6.9
OPC UAサーバー側IPアドレス : 192.168.1.35
OPC UAクライアント側IPアドレス : 192.168.1.104

#サーバー側準備

## サーバープログラム
以下の通りサーバープログラムを実行する。

    sango@example:~/opcua$ python3 serv.py
    Endpoints other than open requested but private key and certificate are not set.
    Listening on 0.0.0.0:4840

以下ソースコード。

    import sys
    sys.path.insert(0, "..")
    import time
     
    from opcua import ua, Server
     
    if __name__ == "__main__":
   
        # setup our server
        server = Server()
        server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
    
        # setup our own namespace, not really necessary but should as spec
        uri = "http://examples.freeopcua.github.io"
        idx = server.register_namespace(uri)
    
        # get Objects node, this is where we should put our nodes
        objects = server.get_objects_node()
    
        # populating our address space
        myobj = objects.add_object(idx, "MyObject")
        myvar = myobj.add_variable(idx, "MyVariable", 3535)
        myvar.set_writable()    # Set MyVariable to be writable by clients
    
        # start
        server.start()
    
        try:
            while True:
                time.sleep(1)
        finally:
            #close connection, remove subcsriptions, etc
            server.stop()

## (参考)本例のUaExpertでのサーバーの見え方
![0_UAX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/cc7fd8fc-cb3f-fe36-4568-d1f72225eead.png)

# セッション確立（クライアント側）
## Hello message & Ack
1. Hello message
![1_Hello.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/c58cc3b8-7c9a-7179-e666-ea430b8d3b21.png)

2. Acknowledge message
![2_Ack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/92a2a4fb-4979-9967-6912-2c82c5dfade7.png)

## Open Secure Channel Request & Response
1. Request
![3_OSCReq_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/0bc37604-7cf5-9420-3a23-75db5f3243b0.png)
**要求時にはSequenceNumberを設定し、以降要求の度に1ずつインクリメントする必要がある。**
**また、以降SequenceNumberはSecurity Sequence Numberと名称が変わる。**
この例では、SequenceNumber(Security Sequence Number) : 51としている。

2. Response
![4_OSCRes_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/3edcb31d-3388-1cac-5fde-74c1fdb7dd49.png)
SecureChannel IdとSecure Token Idをメモしておく。
この例では、SecureChannel Id: 7, Secure Token Id: 14となっている。
**以降、クライアント側から要求を送信する場合、これら2つののIdをヘッダ部に設定する必要がある。**

## Create Session Request & Response
1. Request
![5_CSReq_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/1cf50385-deda-5912-3712-7e3650242b5e.png)
この例では、先ほどメモしたSecureChannel Id: 7, Secure Token Id: 14を設定し要求を送信している。
この例では、Security Sequence Number : 52としている。

2. Response
![6_CSRes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/7045c5ff-4c6d-075c-1749-2773c2f6a698.png)

## Activate Session Request & Response
1. Request
![7_ASReq_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/c2b5ffdb-0aeb-dcf2-afc3-586b8a092d19.png)
この例では、先ほどメモしたSecureChannel Id: 7, Secure Token Id: 14を設定し要求を送信している。
この例では、Security Sequence Number : 53としている。

2. Response
![8_ASRes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/cc62a5a0-5673-6f6e-27b6-16cd7d2a3282.png)

# 書き込み（クライアント側）
1. Request
![9_WReq_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/3973248c-fb56-0036-984f-19b056a2c3d3.png)
上述の通りのOPC UAサーバー起動時にはMyObject > MyVariableに3535を格納している。
この例では、MyObject > MyVariableに対して4047(0x0fcf)を書き込んでいる。

**書き込みたいNamespace Index, Identifier Numericを指定する**
この例では、MyObject > MyVariableを指す、Namespace Index : 2, Identifier Numeric : 2を指定している。
(未確認)EncodingMaskにhas value(0x01)を指定する必要があるかもしれない。

**Message Sizeを正しく設定しないと、サーバー側でOPC UAパケットを正しく認識できない。**
**Message Sizeの範囲はWireShark上のOpcUa Binary Protocolを指す。**

2. Response
![10_WRes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/7a0904da-d351-21bd-b342-09914efcbd4b.png)

# 読み出し（クライアント側）
1. Request
![11_RReq_Mark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/67dbdbdf-b334-a76e-2701-e8ea7fc46d92.png)
**読み出したいNamespace Index, Identifier Numericを指定する**
この例では、MyObject > MyVariableを指す、Namespace Index : 2, Identifier Numeric : 2を指定している。
**読み出したいAttributeIdを指定する**
この例では、Value(0x0000000d)を指定している。

2. Response
![12_RRes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121683/59747e3b-297d-6c20-45ed-edf15607275f.png)
この例では、先ほど設定したMyObject > MyVariableから4047(0x0fcf)が読み出せている。

#まとめ
1. クライアント側要求時にはSequenceNumberを設定し、以降要求の度に1ずつインクリメントする必要がある。
2. セッション確立時に、サーバー側から指定されたSecureChannel Id, Secure Token Idを、クライアント側要求時にヘッダ部に設定する必要がある。
3. クライアント側の書き込み要求時に、書き込みたいNamespace Index, Identifier Numericを指定する必要がある。
4. クライアント側の読み出し要求時に、読み出したいNamespace Index, Identifier NumericとAttributeIdを指定する必要がある。