PythonでOPC UAの変数ノードを監視する

PythonでOPC UAの変数ノードを監視する

opcua-asyncioを使ってOPC UAの変数ノードの変更を購読し、ポーリングではなくサーバーからのプッシュ更新にクライアントが反応するようにします。

Takahiro Iwasa
3 min read

必要要件

以下のコマンドでopcua-asyncioをインストールします。

Terminal window
pip install asyncua

Pythonスクリプト

server.py
import asyncio
import random
from asyncua import Server
ENDPOINT = 'opc.tcp://localhost:4840'
NAMESPACE = 'http://examples.freeopcua.github.io'
async def main() -> None:
# Start a server.
server = Server()
await server.init()
server.set_endpoint(ENDPOINT)
idx = await server.register_namespace(NAMESPACE)
await server.start()
print(f'Server started: {server}')
# Create a node.
myobj = await server.get_objects_node().add_object(idx, 'MyObject')
myvar = await myobj.add_variable(idx, 'MyVariable', 1)
await myvar.set_writable()
# Write a new value every second.
while True:
await myvar.write_value(random.randint(1, 100))
await asyncio.sleep(1)
if __name__ == '__main__':
asyncio.run(main())
client.py
import asyncio
from asyncua import Client, Node
from asyncua.common.subscription import DataChangeNotif, SubHandler
ENDPOINT = 'opc.tcp://localhost:4840'
NAMESPACE = 'http://examples.freeopcua.github.io'
class MyHandler(SubHandler):
def __init__(self):
self._queue = asyncio.Queue()
def datachange_notification(self, node: Node, value, data: DataChangeNotif) -> None:
self._queue.put_nowait([node, value, data])
print(f'Data change notification was received and queued.')
async def process(self) -> None:
try:
while True:
# Get data in a queue.
[node, value, data] = self._queue.get_nowait()
path = await node.get_path(as_string=True)
# *** Write your processing code ***
print(f'New value {value} of "{path}" was processed.')
except asyncio.QueueEmpty:
pass
async def main() -> None:
async with Client(url=ENDPOINT) as client:
# Get a variable node.
idx = await client.get_namespace_index(NAMESPACE)
node = await client.get_objects_node().get_child([f'{idx}:MyObject', f'{idx}:MyVariable'])
# Subscribe data change.
handler = MyHandler()
subscription = await client.create_subscription(period=0, handler=handler)
await subscription.subscribe_data_change(node)
# Process data change every 100ms
while True:
await handler.process()
await asyncio.sleep(0.1)
if __name__ == '__main__':
asyncio.run(main())

テスト

以下のコマンドでサーバーを起動します。

Terminal window
$ python server.py
Server started: OPC UA Server(opc.tcp://localhost:4840)

以下のコマンドでクライアントを起動します。

Terminal window
$ python client.py
Data change notification was received and queued.
New value 4 of "['0:Root', '0:Objects', '2:MyObject', '2:MyVariable']" was processed.
Data change notification was received and queued.
New value 79 of "['0:Root', '0:Objects', '2:MyObject', '2:MyVariable']" was processed.
Data change notification was received and queued.
New value 75 of "['0:Root', '0:Objects', '2:MyObject', '2:MyVariable']" was processed.
...

まとめ

opcua-asyncioを使ってOPC UAの変数ノードを購読することで、値の変更をポーリングするのではなく、サーバーからプッシュされた変更にクライアントが反応できるようになりました。このプッシュ型パターンを支えているのはcreate_subscriptionsubscribe_data_changeです。クライアント側が値をポーリングして比較する必要はなく、MyVariableが変化するたびにサーバー側が自身のスケジュールでdatachange_notificationを呼び出します。そのコールバックをdatachange_notification内で直接処理するのではなくasyncio.Queueを経由させることで、同期的な通知ハンドラを高速に保ちつつ、100msごとにキューをポーリングする非同期のprocess()ループから切り離しています。この分離によって、このパターンは再利用しやすくなっています。各監視タスクに必要なロジックは、通知の受け取り方に手を加えることなく、「write your processing code」のブロック内に記述するだけで済みます。

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.