302.ラズパイ無双[28 OPC-UA FreeOpcUa 3]
初回:2023/3/8
Raspberry Pi (ラズベリーパイ、通称"ラズパイ")で何か作ってみようという新シリーズです。これから数回に分けて、ラズパイ上にOPC-UAサーバーと、クライアントを入れて、通信させるところまで行ってみたいと思います。今回は、前回入れたサーバーと通信をするクライアントを実装してみました。実行結果も添付しますので、色々なデータの取得方法の確認もできると思います。
P子「とりあえず何となく動くところまで来たって感じね」※1
まだ、サンプルをゴネゴネしているだけなので、理解が進んでいません。
【目次】
1.参考資料
今回のOPC UAクライアントの改造の元ネタとなる記事です。
≪参考1≫
https://misoji-engineer.com/archives/python-opc-ua.html#toc11
PythonでOPC UAを実装!クライアントを作ってサーバーに接続
OPC UAのプログラム
参考資料は単純ですが、わかりやすくてよいと思います。とりあえず接続できれば、後はデータの取得方法さえわかれば、OKです。もちろん、データを取得するにはサーバー側のデータの設定方法やキーワードなどを知っておく必要がありますが、そのあたりを半自動的に処理できるようにしていきたいというのが、今回の調査の目的です。
そこで、色々なデータ取得方法を確認するために、改造してみたいと思います。
2.opcua_client.py
opcua_client.py
from opcua import Client ENDPOINT = 'opc.tcp://XXX.XXX.XXX.XXX:4840/freeopcua/server/' NAMESPACE = 'http://examples.freeopcua.github.io' def main() : client = Client(ENDPOINT) try: client.connect() root = client.get_root_node() print(f"Objects node is: {root} path={root.get_path(as_string=True)}") print("Children of root are: ", root.get_children()) idx = client.get_namespace_index(NAMESPACE) print(f'idx={idx}') myobj1 = client.get_objects_node() myvar1 = myobj1.get_child([f'{idx}:MyObject', f'{idx}:MyVariable']) # Now getting a variable node using its browse path myvar2 = root.get_child(['0:Objects', f'{idx}:MyObject', f'{idx}:MyVariable']) print("myobj1 is: ", myobj1) print("myvar1 is: ", myvar1) print("myvar2 is: ", myvar2) print('----------------------------') print('MyObject の一つ下のオブジェクトを取得') myobj2 = root.get_child(['0:Objects', f'{idx}:MyObject']) for vari in myobj2.get_children() : val = vari.get_value() key = vari.get_path(as_string=True)[-1] # 一番最後の値 print( f'①:{vari} {key} {val}' ) print('----------------------------') print('root から3階層分を順番にスキャンする') for ch1 in root.get_children() : print( f'②:{ch1} path={ch1.get_path(as_string=True)}') for ch2 in ch1.get_children() : print( f'③:{ch2} path={ch2.get_path(as_string=True)}') for vari in ch2.get_variables() : val = vari.get_value() key = vari.get_path(as_string=True)[-1] # 一番最後の値 print( f'④:{vari} {key} {val}' ) print('----------------------------') print('root から3階層目を直接取得する') cnt = root.get_child(['0:Objects', f'{idx}:MyObject', f'{idx}:MyCount']) clk = root.get_child(['0:Objects', f'{idx}:MyObject', f'{idx}:MyClock']) the = root.get_child(['0:Objects', f'{idx}:MyObject', f'{idx}:MyThermal']) print( f'⑤ {cnt.get_value()} {clk.get_value()} {the.get_value()} {myvar2.get_value()}' ) print('----------------------------') print('node 名称から、直接取得する') var1 = client.get_node('ns=2;i=2') var2 = client.get_node('ns=2;i=3') var3 = client.get_node('ns=2;i=4') var4 = client.get_node('ns=2;i=5') var1.set_value( 10 ) # 書き込んでみる。 print( f'⑥ {var1.get_value()} {var2.get_value()} {var3.get_value()} {var4.get_value()}' ) print('----------------------------') print('node 名称から直接取得した、1番ノードから、バリアブルを取得する') node2 = client.get_node(f'ns={idx};i=1') # 'ns=2;i=1' for nd in node2.get_variables() : val = nd.get_value() key = nd.get_path(as_string=True)[-1] # 一番最後の値 name = nd.get_display_name() # idx の無いキー値 print( f'⑦:{nd} {key} {val} {name.Text}' ) print('----------------------------') finally: client.disconnect() if __name__ == '__main__': main()
3.解説
同じパターンなので、ほとんど解説しなくてもわかりやすいと思います。
myobj1 = client.get_objects_node()
myvar1 = myobj1.get_child([f'{idx}:MyObject', f'{idx}:MyVariable'])
これは、MyObject や MyVariable というキーワードを直接指定してデータを取得しています。
myvar2 = root.get_child(['0:Objects', f'{idx}:MyObject', f'{idx}:MyVariable'])
は、root からの取得になっていますので、'0:Objects' が最初に来ます。
それ以降は、色々な取得方法をテストしています。ソースのコメントを見てください。
4.出力結果
このプログラムの実行結果を下記に示します。先のプログラムと実行結果を見比べることで、どのような動きをしているか理解できると思います。
$ python3 ./opcua_client.py Objects node is: i=84 path=['0:Root'] Children of root are: [Node(NumericNodeId(i=85)), Node(NumericNodeId(i=86)), Node(NumericNodeId(i=87))] idx=2 myobj1 is: i=85 myvar1 is: ns=2;i=2 myvar2 is: ns=2;i=2 ---------------------------- MyObject の一つ下のオブジェクトを取得 ①:ns=2;i=2 2:MyVariable 1493036 ①:ns=2;i=3 2:MyCount 1277401 ①:ns=2;i=4 2:MyClock frequency(48)=1500398464 ①:ns=2;i=5 2:MyThermal temp=71.5'C ---------------------------- root から3階層分を順番にスキャンする ②:i=85 path=['0:Root', '0:Objects'] ③:i=2253 path=['0:Root', '0:Objects', '0:Server'] ④:i=2256 0:ServerStatus ServerStatusDataType(StartTime:{self.StartTime}, CurrentTime:{self.CurrentTime}, State:{self.State}, BuildInfo:{self.BuildInfo}, SecondsTillShutdown:{self.SecondsTillShutdown}, ShutdownReason:{self.ShutdownReason}) ③:ns=2;i=1 path=['0:Root', '0:Objects', '2:MyObject'] ④:ns=2;i=2 2:MyVariable 1493039 ④:ns=2;i=3 2:MyCount 1277401 ④:ns=2;i=4 2:MyClock frequency(48)=1500398464 ④:ns=2;i=5 2:MyThermal temp=71.5'C ②:i=86 path=['0:Root', '0:Types'] ③:i=88 path=['0:Root', '0:Types', '0:ObjectTypes'] ③:i=89 path=['0:Root', '0:Types', '0:VariableTypes'] ③:i=90 path=['0:Root', '0:Types', '0:DataTypes'] ③:i=91 path=['0:Root', '0:Types', '0:ReferenceTypes'] ③:i=3048 path=['0:Root', '0:Types', '0:EventTypes'] ③:i=17708 path=['0:Root', '0:Types', '0:InterfaceTypes'] ②:i=87 path=['0:Root', '0:Views'] ---------------------------- root から3階層目を直接取得する ⑤ 1277402 frequency(48)=1500345728 temp=71.5'C 11367020 ---------------------------- node 名称から、直接取得する ⑥ 10 1277402 frequency(48)=1500345728 temp=71.5'C ---------------------------- node 名称から直接取得した、1番ノードから、バリアブルを取得する ⑦:ns=2;i=2 2:MyVariable 10 MyVariable ⑦:ns=2;i=3 2:MyCount 1277402 MyCount ⑦:ns=2;i=4 2:MyClock frequency(48)=1500345728 MyClock ⑦:ns=2;i=5 2:MyThermal temp=71.5'C MyThermal ----------------------------
5.まとめ
今回は、クライアントで色々な方法でデータを受けてみました。ここまで解説しておいて、実は python-opc-ua は、単体での動作を理解する上での良い参考プログラムですが、実際は、非同期サーバー/クライアントである opcua-asyncio の asyncua.Server や asyncua.Client を使用するのが良いそうです。次回はそちら側のプログラムの解説を行いたいと思います。
今後も、少し回数が増えるかもしれませんが、気長にお付き合いください。
ほな、さいなら
======= <<注釈>>=======
※1 P子「とりあえず何となく動くところまで来たって感じね」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。