杰瑞科技汇

如何用Python实现OPC服务器?

Of course! Creating an OPC (OLE for Process Control) server in Python is a common requirement for industrial automation, simulation, and data integration. Python is excellent for this due to its simplicity and the availability of robust libraries.

如何用Python实现OPC服务器?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering the concepts, libraries, and a complete, step-by-step example.

Core Concepts

  1. OPC UA (Unified Architecture): When people talk about OPC today, they almost always mean OPC UA. It's the modern, platform-independent, service-oriented successor to the older classic OPC (DA, AE). It uses a client-server model over TCP/IP and is much more secure and feature-rich. We will focus on OPC UA.

  2. Server vs. Client:

    • An OPC UA Server exposes data and functionality (called "Nodes") on the network. Other applications (clients) can connect to read this data or call methods.
    • An OPC UA Client connects to a server to read, write, monitor, or call methods on the nodes exposed by the server.
  3. Address Space: The "address space" is the hierarchical tree of all the nodes (variables, methods, objects) that the server exposes. This is the core of the server's functionality.

    如何用Python实现OPC服务器?-图2
    (图片来源网络,侵删)

Popular Python Libraries for OPC UA Servers

There are two main, well-maintained libraries for creating OPC UA servers in Python:

  1. asyncua: This is arguably the most popular and powerful library. It's built on top of asyncio, making it very efficient for handling many concurrent client connections. It's feature-rich and well-documented.
  2. opcua-asyncio: A more direct Python wrapper around the open62541 C library. It's also asyncio-based and very performant. The API is slightly different from asyncua.

For this guide, we'll use asyncua because its API is very intuitive for Python developers.


Step-by-Step Guide: Creating an OPC UA Server with asyncua

This example will create a simple server that exposes:

  • A variable that counts up every second.
  • A variable with a static string.
  • A method that you can call from a client to reset the counter.

Step 1: Installation

First, you need to install the asyncua library. It's recommended to do this in a virtual environment.

pip install asyncua

Step 2: The Basic Server Code

Let's create a file named my_opc_server.py.

# my_opc_server.py
import asyncio
import time
from asyncua import Server, ua
# --- 1. Server Configuration ---
# Create an instance of the server
server = Server()
# Set the endpoint URL for the server
# This is the address clients will use to connect
endpoint = "opc.tcp://0.0.0.0:4840/freeopcua/server/"
server.set_endpoint(endpoint)
# Set a server name
server.set_server_name("Python OPC UA Server")
# --- 2. Setup the Server's Address Space ---
# The address space is the hierarchy of nodes (variables, objects, etc.)
# Get the root node of the server
uri = "http://example.org"
idx = server.register_namespace(uri)
# Get the Objects folder
objects_node = server.get_objects_node()
# Add a new object to our address space
myobj = await objects_node.add_object(idx, "MyObject")
# Add a variable to our new object
# The variable will have a data type of Int32 and an initial value of 0
myvar = await myobj.add_variable(idx, "MyVariable", 0)
# Set the variable to be writable by clients
await myvar.set_writable()
# Add another variable for a string
my_string_var = await myobj.add_variable(idx, "MyStringVariable", "Hello from Python!")
await my_string_var.set_writable()
# --- 3. Define a Method ---
async def reset_counter(parent_node):
    """A method that resets the counter variable to 0."""
    print("Method 'ResetCounter' called!")
    # Find the variable to reset (it's a child of the parent node)
    var_to_reset = await parent_node.get_child(f"{idx}:MyVariable")
    await var_to_reset.write_value(0)
    print("Counter has been reset to 0.")
    return ua.StatusCode.GOOD
# Add the method to our object
# The parent for the method is our 'MyObject'
await myobj.add_method(idx, "ResetCounter", reset_counter, [ua.VariantType.Boolean])
# --- 4. Main Server Logic ---
async def main():
    # Start the server
    async with server:
        print(f"Server started at {endpoint}")
        print("Waiting for clients...")
        # This is where you would add your application logic
        # For example, updating a variable periodically
        counter = 0
        while True:
            # Update the value of our variable
            counter += 1
            await myvar.write_value(counter)
            print(f"Counter updated to: {counter}")
            await asyncio.sleep(1) # Wait for 1 second
if __name__ == "__main__":
    try:
        # Run the main async function
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nServer shutdown requested. Exiting...")

Step 3: Run the Server

Open your terminal or command prompt, navigate to the directory where you saved my_opc_server.py, and run:

python my_opc_server.py

You should see the following output, indicating the server is running and waiting for connections:

Server started at opc.tcp://0.0.0.0:4840/freeopcua/server/
Waiting for clients...
Counter updated to: 1
Counter updated to: 2
Counter updated to: 3
...

The server will now be running in the background, updating the MyVariable every second.

Step 4: Connect with an OPC UA Client

To see your server in action, you need an OPC UA client. You can use a free, open-source client like UAExpert (Windows) or open62541's own client (cross-platform).

  1. Download and install a client (e.g., UAExpert).
  2. Connect to the server:
    • In the client, create a new session.
    • Endpoint URL: opc.tcp://localhost:4840/freeopcua/server/ (use localhost if running on the same machine, or the server's IP address if on another machine).
    • Leave the username and password blank for now, as our server doesn't have security enabled.
  3. Browse the Address Space:
    • You should see a "Objects" folder.
    • Inside, you'll find your "MyObject".
    • Inside "MyObject", you will find your two variables: MyVariable (its value will be incrementing!) and MyStringVariable.
    • You should also see the ResetCounter method.
  4. Interact:
    • You can right-click MyVariable and "Read" its value to see the current number.
    • You can right-click MyStringVariable and "Write" a new string to it.
    • You can right-click the ResetCounter method and "Call" it. You'll see the print statement in your server's terminal, and the counter will jump back to 0.

Advanced Topics & Best Practices

Security (Highly Recommended for Production)

The basic server has no security. For any real-world application, you must enable security.

  • Certificate Management: The server needs a certificate to identify itself.
  • Username/Password or Certificates: You can configure the server to require clients to authenticate with a username/password or with their own certificate.

The asyncua library provides good documentation on how to set this up. You'll need to generate certificates and configure the server's policy during startup.

Data Change Monitoring

Instead of clients polling the server (which is inefficient), OPC UA supports "subscriptions." A client can subscribe to a variable and receive a notification from the server whenever its value changes.

The asyncua library handles this automatically. When a client subscribes to a variable, the server will automatically send notifications when you write a new value to it (like we do in the while True loop).

Using a Subscription for Custom Logic

If you need to run custom logic when a variable changes, you can use a subscription.

# Inside your main() function, after starting the server
sub = await server.create_subscription(500, myobj) # 500ms publishing interval
# Define a callback function for when a monitored item changes
async def data_change_handler(node, val, data):
    print(f"Data change detected on {node.nodeid}: New value = {val}")
# Add a monitored item to the subscription
handler = ua.SubscriptionHandler()
handler.data_change = data_change_handler
await sub.subscribe_data_change(myvar, handler)

Structured Data (Objects with Properties)

Instead of loose variables, it's often better to group related data into an object with properties.

# Inside the setup section
# Create an object for a sensor reading
sensor_obj = await objects_node.add_object(idx, "TemperatureSensor")
# Add properties to the sensor object
temp_var = await sensor_obj.add_property(idx, "Temperature", 25.0)
status_var = await sensor_obj.add_property(idx, "Status", "OK")
await temp_var.set_writable()
await status_var.set_writable()

This creates a much cleaner and more intuitive address space for clients to browse.

分享:
扫描分享到社交APP
上一篇
下一篇