Getting Started with OPC UA In Docker

Simulating OPC environment in Docker

Muhammad Faiz Noh
3 min readMay 29, 2023

In this article, we will explore Open Platform Communication (OPC) used in industrial automation using Python.

We will learn how to set up a mock server to generate 2 server nodes, pressure & temperature.

Setup

Let’s assume we have PLC sensors that send data to the OPC server and we wanted to grab the data for analysis using a data visualization tool. The end goal of the architecture will be shown below:

Prerequisite

  • Python
  • Docker
  • OPC UA

Im using Docker Windows for this setup.

FreeOpcUa with docker-compose

services:
data_generator:
build:
dockerfile: ./container/datagenerator/Dockerfile
container_name: opc_data_generator
restart: on-failure
ports:
- "4840:4840"

Here we define a service that builds the docker file and named the container opc_data_generator. Next, the service will be restarted automatically on failure and map the host machine port with 4840 with container port 4840.

Create Python Application

The code below is from FreeOpcUa/opcua-asyncio examples in git. We are using examples/server-minimal.py as an example with some modifications.

import asyncio
import logging
import random
from asyncua import Server, ua


async def main():
_logger = logging.getLogger("asyncua")
# setup our server
server = Server()
await server.init()
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 = await server.register_namespace(uri)
ns = "ns=2;s=freeopcua.Tags.pressure"
ns2 = "ns=2;s=freeopcua.Tags.temperature"

min_val = -0.5
max_val = 0.6

# populating our address space
# server.nodes, contains links to very common nodes like objects and root
myobj = await server.nodes.objects.add_object(idx, "MyObject")
pressure = await myobj.add_variable(ns, "MyVariable", 10.5)
temperature = await myobj.add_variable(ns2, "MyVariable", 26.7)
# Set MyVariable to be writable by clients
await pressure.set_writable()
await temperature.set_writable()
opcs = [ pressure, temperature ]

_logger.info("Starting server!")
async with server:
while True:
await asyncio.sleep(1)
random_counter = random.uniform(min_val, max_val)
for opc in opcs:
new_val = await opc.get_value() + random_counter
if(new_val>100.0):
new_val=100.0
elif(new_val<0.0):
new_val=0.0
_logger.info("Set value of %s to %.1f", opc, new_val)
await opc.write_value(new_val)


if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
asyncio.run(main(), debug=True)

Create Dockerfile

Based on the above directory, we will create Dockerfile as per below:

Directory of our Dockerfile
FROM python:3.7-slim

RUN pip install \
asyncua==1.0.2

WORKDIR /opt/opc_mockup/datagenerator

COPY ../../datagenerator /opt/opc_mockup/datagenerator

CMD [ "python" ,"./opc_data_generator.py"]

We are using images from Python 3.7 slim version to reduce the size of our Docker image. Next line we install FreeOpcUa using pip to be able to use their library.

Lastly, we will run our Python file by setting the working directory and copying it to that directory.

Start OPC Server using Docker Compose

docker compose up --build -d

We can check our application logs in Docker Desktop

This is only the beginning part of the project. The next part will include the data streaming using kafka.

References link

Tap the 👏 button if you found this article useful!

--

--