글 / Modbus·OPC UA

Asyncua 값 읽기와 구독

한 번 읽을 때는 read_value를 사용하고, 변경될 때마다 수신하려면 구독을 생성하여 핸들러로 수신한다.

작성일 · 읽는 시간 2분 · Asyncua 2.0 기준

접속과 값 조회

asyncua는 asyncio 기반으로 동작하는 OPC UA 라이브러리이다. Client를 async with로 사용하면 블록에 진입할 때 접속하고 블록을 벗어날 때 접속을 종료한다.

pythonread.py
import asyncio

from asyncua import Client


async def main():
    async with Client("opc.tcp://192.0.2.20:4840") as client:
        idx = await client.get_namespace_index("urn:example:line1")
        node = client.get_node(f"ns={idx};s=Line1.Temperature")
        print(await node.read_value())


asyncio.run(main())

get_node는 서버에 요청하지 않고 노드 객체만 생성하므로 await가 없다. 서버와 통신하는 것은 read_value이며, 값을 Python 타입으로 변환하여 반환한다. 응답 대기 시간은 Client의 timeout 인자로 지정하며 기본값은 4초이다.

접속, 인덱스 조회, 노드 생성, 값 읽기, 종료의 순서

변경 알림 구독

read_value를 반복 호출하면 변경되지 않은 값도 매번 요청하게 된다. 구독(subscription)을 생성하면 서버가 값을 감시하다가 알림을 전송한다.

pythonsubscribe.py
import asyncio


class Handler:
    def datachange_notification(self, node, val, data):
        # 구독한 노드의 값이 바뀌면 불린다
        print(node, val)


async def watch(client, node):
    # 500은 알림을 보내는 주기이고 단위는 밀리초다
    sub = await client.create_subscription(500, Handler())
    await sub.subscribe_data_change(node)
    await asyncio.sleep(60)
    await sub.delete()

핸들러의 메서드 이름 datachange_notification은 고정되어 있다.

구독 생성부터 변경 알림 수신, 구독 삭제까지의 순서

핸들러 주의 사항

핸들러 메서드는 def로도 async def로도 정의할 수 있다. 어느 경우든 클라이언트와 같은 이벤트 루프에서 호출되므로, 핸들러 내부에서 time.sleep이나 긴 계산으로 루프를 점유하면 다음 알림과 다른 요청이 모두 지연된다. 수신한 값은 asyncio.Queue에 넣고 처리는 다른 태스크에 위임한다.

핸들러가 값을 큐에 넣고 다른 태스크가 처리하는 흐름