Personal tools
You are here: Home Members srubio PyPLC Device Server
Log in


Forgot your password?
 

PyPLC Device Server

Tango Device Server for dynamic programming of PLC and other Modbus-based devices; using ESRF's modbus device server and PyTango_utils package.

PyPLC Device Server

  1. Description
  2. State Machine
  3. Configuring the Modbus Device
  4. PyPLC Commands Description
    1. List of Modbus commands
    2. Flag / Bit commands
    3. IeeeFloat?(argin)
  5. Dynamic Attributes and States
    1. Configuring DynamicAttributes
    2. Configuring DynamicStates
    3. Dynamic Commands Property
    4. Using Events and Dynamic Attributes
    5. More about DynamicAttributes
    6. Implementation
  6. The Mapping Property
    1. Reasons to use Mapping property instead of DynamicAttributes
    2. Customizing Mapping reading
    3. Mapping attributes and polling
    4. Attribute Allowance
    5. ModbusCacheConfig
    6. Internals: How MapDict, MapCommands, MapFlags and ReadMap work


Description

The PyPLC Device Server provides a Dynamic interface to any Modbus-based control device. The PyPLC allows to declare DynamicAttributes using Python language; several commands can be used inside Attributes declaration to access any type of variable mapped in Modbus addresses.

A C++ Modbus device server is used to manage the communications; it's currently used with Wago, Beckhoff, B&R, ABB and Phoenix devices. Future releases will communicate with Siemens PLC's using Fetch&Write protocol.

This device server is shared with the ESRF, it only contains the minimal Modbus and DynamicDS behaviours ; all the Alba customization is done in AlbaPlc and other subclasses.


State Machine

The States for a PyPLC device are:

  • INIT: Communication not tried yet
  • FAULT: Modbus is not able to communicate
  • UNKNOWN: Modbus Device is not running or not configured
  • ON: Modbus communicating and no DynamicStates defined
  • Others: As defined by DynamicStates rules

Configuring the Modbus Device

Naming Suggestion: name/of/plc-MBUS ; it will show together the PLC and its Modbus device in Jive and any other sorted list

Values for Properties

  • Protocol: TCP
  • Serialline: TCP
  • IpHost: XXX.XXX.XXX.XXX
  • TCPport: 502
  • ModbusID: 1

it refers to modbus unit identifier, it's usually 0 or 1 but could be other number (up to 15) if working with devices sharing the same IP.

  • SocketConnectionSleep: 1000
  • TCPTimeout: 3

these last two properties help PLC's to manage attempts of connection from several clients

  • CacheConfig: Command\nArg1\nArg2\nNextCommand\n...
  • CacheConfigSleep: 1000

List of commands that will be executed by Modbus device and stored in a cache; clients will read the pre-stored values.
Pause between CacheConfig? update cycles.


PyPLC Commands Description

List of Modbus commands

Command Argin Argout Result Description
Reg(Address) DevShort DevShort Value of the given register
Coil(Address) DevShort DevShort Value of the given coil ()
Flag(Address,Bit) DevVarShortArray DevShort Value of a bit in the given register
Bit(Number,Bit) DevVarShortArray DevShort Value of a bit in the given integer
Regs(Address,N) DevVarShortArray DevVarShortArray Values of N consecutive registers
Regs32(Address,N) DevVarShortArray DevVarShortArray N 32bit values from 2*N consecutive registers
Coils(Address,N) DevVarShortArray DevVarShortArray Values of N consecutive coils
IeeeFloat(Address) DevVarShortArray DevDouble 32bit IeeeFloat read from 2 consecutive registers
IeeeFloat(Int1,Int2) DevVarShortArray DevDouble 32bit IeeeFloat build using two 16bit integers
WriteFloat(Address,Value) DevVarStringArray DevString Writes a IeeeFloat number in two registers
WriteCoil(Address,Value) DevVarShortArray DevVoid Writes a 0 or 1 in a coil
WriteBit(Address,Value) DevVarShortArray DevVoid Writes a 0 or 1 in a bit of a register
WriteInt(Address,Value) DevVarShortArray DevVoid Writes a 16bit value in a register
WriteLong(Address,Value) DevVarLongArray DevVoid Writes a 32bit value in two registers

Flag / Bit commands

  • Flag can be used for extracting a bit from Hardware (a Modbus Addres) or an int passed as argument ... an Bit is an alias for this last behaviour.

IeeeFloat?(argin)

Parses multiple argument types. Now if argin is a single elment it is understood as a ModbusAddress? and two registers are read from the PLC to calculate a 32 bit float.

If argin is a list its 2 first elements are used to calculate the float, without access to the PLC


Dynamic Attributes and States

Devices that inherit from DynamicDS have new features like Dynamic Attributes, States and Commands configurable using Tango Properties. Due to its particularities it has its own Event/Polling control properties that can be tuned to improve performance (not needed if number of attributes is below 50).

These properties are UseEvents, KeepTime, KeepAttributes and CheckDependencies. More info about configuration and DynamicDS behavior here:  DynamicDS-HowTo.

Configuring DynamicAttributes

The DynamicAttributes Property is used to create the read/write attributes of the PLC Device.

This is the format that can be used to declare the Dynamic Attributes (more information is available in the PyTango_utils module user guide):

ATT_NAME=type(READ and !DevComm1(args) or WRITE and !DevComm2(args,VALUE))

Any of the PyPLC Device Commands can be used in the Attribute declaration.

The type of attributes can be declared using DevLong/DevDouble/DevBool/DevString, DevVarLongArray/DevVarDoubleArray/DevVarBoolArray/DevVarStringArray Or the equivalent python types: int , float, bool, str, list(int(i) for i in []), [float(i) for i in[]], ...

Therefore:

AnalogIntsREAD=list(long(r) for r in Regs(7800,100)) #Array of 100 integers read from address 7800

equals to

AnalogIntsREAD=DevVarLongArray(Regs(7800,100)) #Array of 100 integers read from address 7800

Configuring DynamicStates

When giving a value to this property it will override the default PyPLC state (ON). State managed by attribute qualities is disabled in PyPLC.

FAULT=CPU_STATUS < 0
WARNING=max([Temperature1,Temperature2])>70
OK=1 #State by default

See  DynamicDS-HowTo

Dynamic Commands Property

This is the syntax of DynamicCommands property:

Open_PNV_01=(WriteBit(193,2,1),'DONE')[-1]
Close_PNV_01=(WriteBit(193,1,1),'DONE')[-1]
Move_PNV_01=(WriteBit(193,1+int(ARGS[0]),1),'DONE')[-1]

All commands where ARGS are not used are created as DevVoid/DevString commands. If args is used then argin is always a DevVarStringArray.

Using Events and Dynamic Attributes

See  DynamicDS-HowTo

More about DynamicAttributes

DynamicAttributes that are constructed using others (e.g. Ts=float((T1+T2)/2)) don't generate new HW calls; they read a cache where the last read value is stored.

DynamicAttributes are READ/WRITE. To specify different behaviour for reading and writing I'm using the keywords READ, WRITE and VALUE in this way:

#A TestMode can be read/set using the 7th bit of the flags registers 
TestMode=bool(READ and Flag(XXX0,7) or WRITE and WriteFlag(XXX1,7,int(VALUE)))

A read_attribute access can be forced by using Attr('AttrName') instead of the attribute name directly, but if the 'parent' attribute has a polling configured it simply returns the last value in the polling buffer.

There's a parallel cache of 'stored values' that is used for Write Attributes in the PySignalSimulator. It uses the command VAR(name,VALUE), it can be used also to retrieve the last forced value.

Arrays can be constructed using a set of commands:

AnalogIntsWRITE=list(long(r) for r in (Regs(8120,125) + Regs(8246,125) + Regs(8372,125)))

The Type of each attribute created is determined by the first words in the declaration (int / list(long / str , ...) is not the best way to do it but I'm not able to evaluate expressions composed by attributes at the time dyn_attr creator is called.
Command updateDynamicAttributes allows to reload the DynamicAttributes property and create new attributes if needed.
A new Class DynamicAttribute? overloads all the operators needed to operate the values of the Attributes but keeping the quality and timestamp values following the newest/worst criteria, it works for both Scalar and Spectrum types.

Implementation

Local methods/commands are added in the DynamicDS.init call using a dictionary:

def __init__(self,cl, name):

DynamicDS.__init__(self,cl,name,globals(),_locals={
'Reg': lambda _addr: self.Reg(_addr),
...
},useDynStates=True)
PyPLC.init_device(self)

A declaration of attributes for a PyPLC device looks like this (although it's better to declare arrays using the Mapping property instead):

#Declare Arrays
DataArray=list(long(r) for r in (Regs(0,125)+Regs(126,50)))
#Individual Attributes
Register=int(Attr('DataArray')[0])
TestMode=bool(READ and Bit(Register,7) or WRITE and WriteFlag(1,7,int(VALUE)))

The Mapping Property

The values of Mapping will be used to create Read/Write DevVarLongArray attributes that will be always available independently of the contents of the DynamicAttributes property.

Arrays of data can be declared in any of these formats ($Address refers to an integer matching a Modbus address):

        Map1:StartAddress,+length
        Map2:StartAddress,EndAddress
        Map3:Regs(Addr1,length1)+Regs(Addr2,length2)

It equals to these DynamicAttribute? declarations:

        Map1=DevVarLongArray(Regs(StartAddres,length))
        Map2=DevVarLongArray(Regs(StartAddres,EndAddress-StartAddress))
        Map3=DevVarLongArray(Regs(A1,l1)+Regs(A2,l2))

Mapping configuration takes in account that Modbus doesn't allow to read more than 125 contiguous registers in a single order.

The arrays declared using the Mapping property will be automatically added to the Modbus.CacheConfig property if the PyPLC.ModbusCacheConfig property is set or if the PyPLC.SetModbusCacheConfig command is executed.

This Modbus.CacheConfig creates an intermediate polling buffer between Modbus device and its clients that improve the reliability of the communications and events generation (solving the PollingThreadOutOfSync exceptions problem).

Reasons to use Mapping property instead of DynamicAttributes

In our PLCs all the attributes are being read from certain areas of memory that are read together. e.g. If DigitalInputs are mapped between addresses 7500 and 7620 the attributes declaration is like:

        DigitalInputs=DevVarLongArray(Regs(7500,7620))
        DigInput1=int(DigitalInputs[0])
        DigInput2=int(DigitalInputs[1])

Problems of this declaration are:

  • DigitalInputs must be read before DigInput1 and DigInput2.

 * DigitalInputs will require a ModbusCacheConfig setup; or its bigger read time will provoke polling thread problems.

Mapping property allows to pre-declare some arrays of data that will be added to the Attributes list, but being read before any other attribute and forcing Modbus.CacheConfig and Modbus.CacheSleep properties setup if PyPLC.ModbusCacheConfig property has an integer value.

Customizing Mapping reading

  • DefaultReadCommand property allows to overwrite ReadHoldingRegisters as default command for Mappings reading
  • AddressOffset adds an integer offset to addresses when passed to the Modbus device

Mapping attributes and polling

  • In the PyPLC the DynamicDS.KeepAttributes flag is always valid for mapped attributes.
  • CheckDependencies and KeepAttributes have been improved to save times.
  • The ReadMap processing has been reduced to <1ms; and for every Mapped array it will update dyn_values and _locals dictionary. So it is not needed to call evalAttr or read_dyn_attr to update the Mappings.
  • If the last value stored for a dependency is older than KeepTime the read_dyn_attr(dependency) method will be called to update this value.
  • In addition the StateMachine() is executed every STATE_MACHINE_PERIOD (KeepTime) and will try to update Mappings if LastHWUpdate was more than a period ago.

Attribute Allowance

  • Attributes declared using the Mapping property are always readable.
  • DynamicAttributes depending on MappedAttributes will be allowed once MappedAttribute is updated (dependency parsed from properties)
  • Rest of DynamicAttributes are always enabled.

Dynamic Attributes::

The method DynamicDS.is_dyn_allowed is overwritten. It allows to read DynamicAttributes when Mappings are updated and out of INIT State.
INIT State is left once CPUStatus has been read.

ModbusCacheConfig

If TRUE, writes Modbus.CacheConfig using Mapping values. If Int() additionally configures CacheSleep

Internals: How MapDict, MapCommands, MapFlags and ReadMap work

  • MapDict will keep AttrName:Formula as declared in properties.
  • MapCommands will contain the list of modbus commands for each mapping (result of GetCommands4Map(formula) call).
  • MapFlags [Map] is set to True when registers belonging to a Map have been updated from hardware ( sendModbusCall().checkMaps() call).
  • ReadMap(Attr / Regs) will process the reading of the registers associated to a Map and will store the readings in dyn_values and _locals dictionaries. It will also set the MapFlag to False.
Document Actions