Retrieving data in structure

In this tutorial we will see how to use data models (pydantic models) to retrieve information on the PLC for any applications.

In pydevmgr_elt the data is completly separated to the engine to retrieve data from server. For instance in a pydevmgr_elt.Motor class their is no last_data or default_data or whatsoever instead the Motor has a Data class which can be instanciated and linked to the Motor device.

Native Data structure

The devices have all a Data class attached, this Data class can be instancied and used normaly :

from pydevmgr_elt import Motor

data = Motor.Data()

data.stat.pos_actual
# 0.0
data.stat.substate
# 0

At creation the data is filled with default values, one need to link the data to a motor instance with a pydevmgr_elt.DataLink. A call of the .download method will fill the data field to real values taken from the OPC-UA server

from pydevmgr_elt import Motor, DataLink

motor = Motor('my_motor',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1")
data = Motor.Data()

data_link = DataLink( motor, data )

try:
     # need to connect the opc-ua client
     motor.connect()

     # This will update all values of data from the OPC-UA
     data_link.download()

     print( f"motor is at {data.stat.pos_actual}mm with a position error of {data.stat.pos_error}mm")
     print( f"the configured backlash is  {data.cfg.backlash} mm" )

 finally:
     motor.disconnect()

This is a great way to separate the function/application dealing with the data informations (plot, logging, gui, tunning, … ) to the way to retrieve it.

Note that the .download method of the data_link will ask node values in one single OPC-UA call per server.

If you are dealing with several Data classes you can add them to a Downloader and call the download method of the Downloader. This way you assure the one request per OPC-UA server :

from pydevmgr_elt import Motor, Lamp, DataLink, Downloader

motor = Motor('my_motor',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1")
lamp = Lamp('my_lamp',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Lamp1")

motor_data = Motor.Data()
lamp_data = Lamp.Data()

downloader.add_datalink(..., DataLink(motor, motor_data))
downloader.add_datalink(..., DataLink(lamp, lamp_data))

try:

     motor.connect()
     lamp.connect()

     # tHe following will update the motor_data and the lamp_data in one single call
     downloader.download()
     my_function_using_data( motor_data, lamp_data )
 finally:

     motor.disconnect()
     lamp.disconnect()

The following script will give the same results :

from pydevmgr_elt import Motor, Lamp, DataLink, EltManager
from pydantic import BaseModel

class MyData(BaseModel):

     motor: Motor.Data = Motor.Data()
     lamp: Lamp.Data = Lamp.Data()

manager = EltManager('',  devices = dict(
              motor = Motor('my_motor',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1"),
              lamp = Lamp('my_lamp',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Lamp1")
             ))
data = MyData()
dl = DataLink( manager, data)


try:
     manager.connect()

     # tHe following will update the motor_data and the lamp_data in one single call
     dl.download()
     my_function_using_data( data )
finally:

     manager.disconnect()

Manager

Actually the exemple above can be simplified if one use a pydevmgr_elt.EltManager. the manager is used to concatenate some action and can create a Data class from available devices.

from pydevmgr_elt import EltManager, Motor, Lampo, DataLink, wait

devices = dict(
    motor =  Motor(address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1"),
    lamp = Lamp('my_lamp',  address="opc.tcp://myplc.local:4840", prefix="MAIN.Lamp1")
)

m = EltManager( 'fcs', devices=devices)

Data = m.create_data_class()
data = Data()
data_link = DataLink(m , data)


try:
    # connect all devices
    m.connect()
    # init all devices
    wait( m.reset() )
    wait( m.init() )
    wait( m.enable() )

    data_link.download()

    print( "Motor is at",  data.motor.stat.pos_actual )
finally:
    # diconnect all devices
    m.connect()

Custom Data structure

The data structure is built from a pydantic model.

The annotation in the structure indicate the DataLink one a field is refering to a Node, for instance :

from pydevmgr_elt import DataLink, NodeVar
from pydantic import BaseModel, Field

class MyMotorStatData(BaseModel):

     normal_value: int = 0  # a normal value,  ignored by DataLink
     pos_actual: NodeVar[float] = 0.0
     pos_error: NodeVar[float] = 0.0

stat_data = MyMotorStatData()
motor =  Motor(address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1")
dl = DataLink( motor.stat , stat_data )

The exemple above will work because pos_error and pos_actual are matching the node name inside motor.stat. If you wish to change the name or the path inside the data structure you need to use the Field pydantic class with a node keyword :

from pydevmgr_elt import DataLink, NodeVar
from pydantic import BaseModel, Field

class MyMotorData(BaseModel):

     pos: NodeVar[float] = Field(0.0, node="stat.pos_actual")
     err: NodeVar[float] = Field(0.0, node="stat.pos_error")
     backlash: NodeVar[float] = Field(0.0, node="cfg.backlash")

data = MyMotorData()
motor =  Motor(address="opc.tcp://myplc.local:4840", prefix="MAIN.Motor1")
dl = DataLink( motor , data )

try:
     motor.connect()
     dl.download()
     print( data )

finally:
     motor.disconnect()