FreeOpcUa / python-opcua

LGPL Pure Python OPC-UA Client and Server
http://freeopcua.github.io/
GNU Lesser General Public License v3.0
1.37k stars 662 forks source link

Connecting OPC server to PLC #116

Closed kashp closed 8 years ago

kashp commented 8 years ago

Hi I am new to PLC, Can i use your server code as a replacement of kepserver, can your code directly communicate with plc, is there any specific driver support ??

destogl commented 8 years ago

I don't understand your question. Do you want to run code on PLC or comunicate with PLC through OPC UA?

zerox1212 commented 8 years ago

This library is only the OPC UA side of things. The only way you could connect directly with a PLC is if your PLC has a built in OPC UA client or server. If you want to use Python OPC UA like Kepserver you need to write your own driver as a bridge between PLC and OPC UA server. Depending on your PLC it could be rather easy (like if you need to talk Modbus RTU) to extremely difficult (Allen Bradly CIP).

I have used Python Modbus_tk to make a Modbus RTU to OPC UA bridge and it wasn't that difficult. The Modbus master polls the PLC in its own thread, the OPC UA server grabs the data and updates the OPC UA nodes.

destogl commented 8 years ago

@kashp did you find solution?

LJBD commented 8 years ago

@zerox1212 have you released that bridge anywhere? And if not, would you mind doing so? I'm a complete newbie to the OPC UA and I'm trying to connect a Eurotherm Mini8 PLC with Modbus TCP via OPC UA to a control system based on Tango Controls (open source framework, used in synchrotrons). Getting through the "lower" side of things (Modbus to OPC UA server) with your help would be an extreme speed-up in my work.

zerox1212 commented 8 years ago

I never released it because my implementation is extremely specific (hard coded slaves and messages). However I can probably make a gist of a few parts of my code when I get some time if that would help you.

LJBD commented 8 years ago

Yes, that would surely help - thanks! Will you post the links here once it's ready?

zerox1212 commented 8 years ago

https://github.com/zerox1212/python-opcua-modbus-driver

I didn't actually run this code, but maybe you can take a look at it and get an idea how I did it.

It does have some more advanced concepts, that you will need to understand because it was faster to copy paste my code than write you an example from scratch. The main thing you need to know is that my implementation tries to sync a UA Object to a matching python class. That is why there is a Device UA object and a Device python class. They get unified via theUaObject super class.

You need to edit the slaves objects in modbus to match your modbus situation. Then you need to edit the XML file that defines MyDevice UA object with the slave address. That way the python Device object can collect the data from the driver when it calls device.update().

I had many slaves and wanted to build my application dynamically via XML with a generic structure, so this is probably over kill for what you need. Let me know if you have questions.

zerox1212 commented 8 years ago

What the Device object looks like in UA (python class is identical to this) image

If you have a slave address 1, that you read a single INT from you need to set MyDevices.DataAddress to "1:1" and DataSource to "ModbusRTU"

LJBD commented 8 years ago

That is amazing, thank you so much!

zerox1212 commented 8 years ago

I hope it helps you. Probably the best way to approach your solution is to design a UA object type that matches your Eurotherm Mini8 PLC, or perhaps some how similar to how that PLC has its data organized. Then you could modify my code to "mirror" everything in the PLC to OPC UA.

LJBD commented 8 years ago

I've worked with your code (thanks again!), wrote my own ModbusTcp driver (since I've got the PLC connected via Ethernet) and I've encountered 2 issues so far:

  1. I have a single ModbusTcp slave. As far as I understand, if I want to get many parameters from it, I have to create a Device OPC object for every single one of them, because there's only one DataAddress defined in the Device type. Or should I define more properties/variables in the Device type?

  2. I get an Exception from inside the package when the first Device object is created. It's caused by creating a subscription in your UaObject (I haven't modified it). The traceback is included below. It seems that the ThreadLoop's call_later() should never be called before its run() method since the loop attribute is created in run() method and not in the constructor (as it possibly should be?). Is it a bug or I'm doing something wrong?

Traceback (most recent call last):
  File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 157, in <module>
    main()
  File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 142, in main
    my_server = OPCUAServer(logger)
  File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 66, in __init__
    device = Device(self.server, device_node, self.modbus_driver, logger)
  File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/device.py", line 14, in __init__
    super(Device, self).__init__(opcua_server, ua_node)
  File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/ua_object.py", line 27, in __init__
    sub = opcua_server.create_subscription(500, handler)
  File "/usr/lib/python2.7/site-packages/opcua/server/server.py", line 323, in create_subscription
    return Subscription(self.iserver.isession, params, handler)
  File "/usr/lib/python2.7/site-packages/opcua/common/subscription.py", line 88, in __init__
    response = self.server.create_subscription(params, self.publish_callback)
  File "/usr/lib/python2.7/site-packages/opcua/server/internal_server.py", line 341, in create_subscription
    result = self.subscription_service.create_subscription(params, callback)
  File "/usr/lib/python2.7/site-packages/opcua/server/subscription_service.py", line 33, in create_subscription
    sub.start()
  File "/usr/lib/python2.7/site-packages/opcua/server/internal_subscription.py", line 270, in start
    self._subscription_loop()
  File "/usr/lib/python2.7/site-packages/opcua/server/internal_subscription.py", line 279, in _subscription_loop
    self.subservice.loop.call_later(self.data.RevisedPublishingInterval / 1000.0, self._sub_loop)
  File "/usr/lib/python2.7/site-packages/opcua/common/utils.py", line 162, in call_later
    p = functools.partial(self.loop.call_later, delay, callback)
AttributeError: 'NoneType' object has no attribute 'call_later'
oroulet commented 8 years ago

you need to start server, before subscribing

On Fri, 25 Nov 2016 at 09:26 Łukasz Dudek notifications@github.com wrote:

I've worked with your code (thanks again!), wrote my own ModbusTcp driver (since I've got the PLC connected via Ethernet) and I've encountered 2 issues so far:

1.

I have a single ModbusTcp slave. As far as I understand, if I want to get many parameters from it, I have to create a Device OPC object for every single one of them, because there's only one DataAddress defined in the Device type. Or should I define more properties/variables in the Device type? 2.

I get an Exception from inside the package when the first Device object is created. It's caused by creating a subscription in your UaObject (I haven't modified it). The traceback is included below. It seems that the ThreadLoop's call_later() should never be called before its run() method since the loop attribute is created in run() method and not in the constructor (as it possibly should be?). Is it a bug or I'm doing something wrong?

Traceback (most recent call last): File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 157, in main() File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 142, in main my_server = OPCUAServer(logger) File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/server.py", line 66, in init device = Device(self.server, device_node, self.modbus_driver, logger) File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/device.py", line 14, in init super(Device, self).init(opcua_server, ua_node) File "/home/admin/PycharmProjects/lib-agh-modbus2opcua/m2opc_server/ua_object.py", line 27, in init sub = opcua_server.create_subscription(500, handler) File "/usr/lib/python2.7/site-packages/opcua/server/server.py", line 323, in create_subscription return Subscription(self.iserver.isession, params, handler) File "/usr/lib/python2.7/site-packages/opcua/common/subscription.py", line 88, in init response = self.server.create_subscription(params, self.publish_callback) File "/usr/lib/python2.7/site-packages/opcua/server/internal_server.py", line 341, in create_subscription result = self.subscription_service.create_subscription(params, callback) File "/usr/lib/python2.7/site-packages/opcua/server/subscription_service.py", line 33, in create_subscription sub.start() File "/usr/lib/python2.7/site-packages/opcua/server/internal_subscription.py", line 270, in start self._subscription_loop() File "/usr/lib/python2.7/site-packages/opcua/server/internal_subscription.py", line 279, in _subscription_loop self.subservice.loop.call_later(self.data.RevisedPublishingInterval / 1000.0, self._sub_loop) File "/usr/lib/python2.7/site-packages/opcua/common/utils.py", line 162, in call_later p = functools.partial(self.loop.call_later, delay, callback) AttributeError: 'NoneType' object has no attribute 'call_later'

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/FreeOpcUa/python-opcua/issues/116#issuecomment-262904014, or mute the thread https://github.com/notifications/unsubscribe-auth/ACcfzqg4IthWekJP6YXRO5PAPFXmO3roks5rBpu-gaJpZM4HGyv7 .

LJBD commented 8 years ago

Oh, that seems obvious, of course! Now it's not crashing, thank you!

LJBD commented 8 years ago

Okay, so I've got the server up and running and connected to my PLC. Now, I've got another question: how should I approach writing parameters through OPC to Modbus?

zerox1212 commented 8 years ago

Sorry about the subscribe thing, my example code is wrong. This code should be in a different function that gets called after server is started.

# get Objects node
        objects = self.server.get_objects_node()

        # get the device object type so we can find all the devices in the address space
        hw_type = self.server.nodes.base_object_type.get_child("2:Device")
        device_nodes = find_o_types(objects, hw_type)

        # instantiate a device python object and make it an attribute of the server class
        # FIXME do your own organization, most likely an object oriented model
        for device_node in device_nodes:
            device = Device(self.server, device_node)
            setattr(self, device.b_name, device)
            # keep track of the device because so that we can update it cyclically using the driver
self.devices.append(device)

My code was for a read only scenario. You probably want to start looking at https://github.com/FreeOpcUa/python-opcua/issues/365 because it's going to be the same issue you face. You might need to get creative.