MBLogic for an open world in automation
MBLogicEngine can be used as an embedded library inside another application. This allows any application to include soft logic capabilities. This is useful when creating things like automated test systems, labelling systems and other custom PC applications which may have to interact directly with manufacturing equipment. Using MBLogicEngine allows the core of your application to be written as a normal PC application while providing a familiar PLC programming language for the parts which you wish to allow users to customise and change.
MBLogicEngine is written entirely in Python. You can therefor easily write the rest of your PC application in Python. As Python is one of the major modern programming languages, it has an extensive selection of libraries, and is intended to allow rapid development of custom applications.
Alternatively, you can embed the Python interpreter in applications written in languages such as C or C++. More information on embedding Python may be found at the main Python web site http://www.python.org/. Additionally, there are versions of Python (Jython) written to run as part of a Java system and so allow Python to be used directly in Java programs. MBLogicEngine has not however, been actualy tested with Jython.
PLCs have what is generally termed a "scan cycle". The PLC program executes one complete "logic scan" (runs the PLC program all the way through once). Then the PLC updates its I/O and performs any general overhead functions before doing another logic scan.
To use MBLogicEngine as an embedded library in your own program you would need to regularly call the function which executes the PLC program scan. A typical program would do the following:
In this discussion we will assume that the code which implements the soft logic functions which emulate the desired PLC already exists (i.e. you are using an existing version). Creating your own instruction set is not described here. We will also assume that you are writing your application in Python, rather than embedding Python in C (or some other language). The examples below are illustrated with the version which is modeled on the Koyo "Click" series PLC.
First you must import the required libraries. These must include the following:
Library | File | Provides |
---|---|---|
PLC compiler | PLCCompile.py | This is the PLC compiler. This takes the PLC program source file, checks it for errors, and converts it into a form which can be executed. |
PLC interpreter | PLCInterp.py | This executes the PLC program each scan. |
Instruction library | DLCkInstructions.py (in this example) |
This is a library which defines each of the PLC instructions and provides the information required to compile them. Each different PLC which is being emulated will require a different instruction library to define its instructions. |
Data table | DLCkDataTable.py (in this example) |
This is a library which defines the PLC data table (the data memory). Each different PLC which is being emulated will require a different data table library to define its data table. |
Run time libraries | DLCkLibs.py (in this example) |
This is a library which provides run time support for the PLC instructions (e.g. math functions, etc.). Each different PLC which is being emulated will require a different set of run time support libraries. These will include a mixture of "standard" libraries (used for all PLC models) and "custom" libraries (created for that model of PLC). |
Example:
from mbsoftlogicck import PLCInterp from mbsoftlogicck import PLCCompile from mbsoftlogicck import DLCkInstructions from mbsoftlogicck import DLCkDataTable from mbsoftlogicck import DLCkLibs
Next, you must initialise the compiler. This provides the compiler with the list of valid instructions, plus a private data table for use by instructions which must store state information outside of the normal data table (e.g. some PLCs store differentiate or "one shot" state outside of the normal data table).
Example:
PLCCompiler = PLCCompile.PLCCompiler(DLCkInstructions.InstrDefList, DLCkDataTable.InstrDataTable)
Parameter | Type | Description | |
---|---|---|---|
1 | DLCkInstructions.InstrDefList | List | The list of instructions defining the instruction set. |
2 | DLCkDataTable.InstrDataTable | Dictionary | An empty dictionary for storing private instruction data. |
The compiler should be initialised once only when your application starts up. Once it is initialised, it may be called as many times as desired until your application exits.
Before the PLC program can be executed, it must be loaded into memory and then compiled. Compiling the PLC program checks it for errors and prepares it for execution.
The compiler has a function called "ReadInFile" which reads in the source file from disk. You must pass it a string which indicates the name of the file containing the PLC program.
Example:
PLCCompiler.ReadInFile('demoplcprog.txt')
If the file is not present or cannot otherwise be read, an exception will be raised which you must deal with.
Example:
try: PLCCompiler.ReadInFile('demoplcprog.txt') except: pass # Do something about the error.
If there were any compile errors, we can ask for a description. The error messages are contained in a list.
Example:
# Get any compiler error messages. CompileErrMsgs = PLCCompiler.GetCompileErrors() for i in CompileErrMsgs: print(i)
This will produce an output something like the following:
Example:
Error in line 12 for STR CTD100 - One or more parameters is of an incorrect type.
Once the PLC source file has been loaded into memory, it must be compiled. This involves checking the PLC program for obvious errors and then converting it into a form which allows it to be executed.
Example:
plcprogram, instrcount, CompileErrors = PLCCompiler.CompileProgram()
The "CompileProgram" function takes no parameters, but it does return three values. These are:
Return Value | Type | Description | |
---|---|---|---|
1 | plcprogram | Code Object | The compiled PLC program. This will be passed to the interpreter to execute. |
2 | instrcount | Integer | Indicates the number of instructions compiled. |
3 | CompileErrors | Boolean | Indicates whether and errors were encountered. If this is False, then no errors were found. If it is True, then at least one PLC program error was found, and the compiled program is not valid and you should stop at this point. |
Some types of errors may result in an exception which you must deal with.
The interpreter must be initialised with the PLC program as well as the data tables and various libraries.
Example:
MainInterp = PLCInterp.PLCInterp(plcprogram, DLCkDataTable.BoolDataTable, DLCkDataTable.WordDataTable, DLCkDataTable.InstrDataTable, DLCkLibs.TableOperations, DLCkDataTable.Accumulator, DLCkLibs.BinMathLib, DLCkLibs.FloatMathLib, DLCkLibs.BCDMathLib, DLCkLibs.WordConversions, DLCkLibs.CounterTimers, DLCkLibs.SystemScan)
The parameters in the above example are :
Parameter | Type | Description | |
---|---|---|---|
1 | plcprogram | Code object | The compiled PLC program from the compiler. |
3 | DLCkDataTable.BoolDataTable | Dictionary | The part of the data table used to store boolean data table values. |
4 | DLCkDataTable.WordDataTable | Dictionary | The part of the data table used to store word data table values. |
5 | DLCkDataTable.InstrDataTable | Dictionary | The part of the data table used to store private data table values. Not all PLCs make use if this but it is still required. |
6 | DLCkLibs.TableOperations | Library | This is a library used to provide libraries for instructions which operate on multiple registers. |
7 | DLCkDataTable.Accumulator | Library | A library which can provide accumulator and stack functions. This is not used in this version, but the parameter must still be present. |
8 | DLCkLibs.BinMathLib | Library | The binary math library. This provides all the math functions. |
9 | DLCkLibs.FloatMathLib | Library | The floating point math library. This is not used in this version, but the parameter must still be present. |
10 | DLCkLibs.BCDMathLib | Library | The BCD math library. This is not used in this version, but the parameter must still be present. |
11 | DLCkLibs.WordConversions | Library | A library which provides miscellaneous word conversion functions. |
12 | DLCkLibs.CounterTimers | Library | The library which provides counter and timer functions. Timers and counters are usually different enough between different models of PLC that they need to be implemented separately for each. |
13 | DLCkLibs.SystemScan | Library | A library which provides system scan overhead functions. These are things which must be updated between each scan.. |
These libraries should be already provided with a working version of MBLogicEngine and do not have to be created just to use it.
The PLC program must be loaded, compiled, and the interpreter initialised at least once before the PLC program is executed. In addition, the PLC program may also be reloaded, recompiled, and reinitialised at any time. Re-initialising the interpreter does not affect the data table contents.
Once the PLC program has been compiled and the interpreter initialised with it, the PLC program may be executed.
The application must read and write the data table in order for MBLogicEngine to receive updated input information or to write the output information to the actuators. The interpreter provides several functions to perform this. The functions to perform this are:
Function | Parameters | Return Value | Description | |
---|---|---|---|---|
1 | GetBoolData | List | Dictionary | This is used to read the boolean data table. It takes a list of strings. Each list element represents a boolean data table address label. It returns a dictionary with the list elements as dictionary keys, and the data table data as values. |
2 | SetBoolData | Dictionary | N/A | This is used to write to the boolean dat table. It takes a dictionary. The dictionary keys must be data table address labels, and the dictionary values are to be the updated data table values. |
3 | GetWordData | List | Dictionary | This is used to read the word data table. It takes a list of strings. Each list element represents a word data table address label. It returns a dictionary with the list elements as dictionary keys, and the data table data as values. |
4 | SetWordData | Dictionary | N/A | This is used to write to the word dat table. It takes a dictionary. The dictionary keys must be data table address labels, and the dictionary values are to be the updated data table values. |
5 | GetInstrDataTable | N/A | Dictionary | This returns the entire instruction data table as a dictionary. The instruction data table is used to hold private instruction data and there is normally no reason to use this function. |
6 | SetInstrDataTable | Dictionary | N/A | This takes a dictionary as a parameter and replaces the entire instruction data table. There is normally no reason to use this function. |
Example:
// Set the boolean data table. MainInterp.SetBoolData({'X17' : True, 'X277' : False}) // Read the boolean data table. BData = MainInterp.GetBoolData(['Y1', 'Y367', 'C50', 'CT100'])
With GetBoolData, SetBoolData, GetWordData, and SetWordData, there is no need to request or write to data table addresses which are not used.
Some additional functions may need to be executed as part of the scan cycle but are not part of the interpreter. The details of these depend on the particular PLC being emulated.
When the interpreter function "MainLoop" is called, the PLC program is executed for one complete logic scan. This function takes no parameters. Some run-time errors may cause an exception which must be handled by the application program.
Example:
MainInterp.MainLoop()
After the interpreter returns, a function ("GetExitCode") may be called to read exit and diagnostic information. The following information is returned:
Return Value | Type | Description | |
---|---|---|---|
1 | ExitCode | String | his is a text string indicating the reason the PLC program exited. The exit code 'normal_end_requested' means the program has exited normally. |
2 | ExitSubr | String | This is the name of the subroutine which was active when the program exited. |
3 | ExitRung | Integer | This is the rung number which was active when the program exited. |
Example:
ExitCode, ExitSubr, ExitRung = MainInterp.GetExitCode()
If no run-time errors were encountered, the program should exit with an exit code of "normal_end_requested". This exit code is set by the IL instructions that normally end the program scan. Any other exit code indicates that a run time error was encountered.
An exit code of "unexpected_end" indicates that the end of the program was encountered unexpectedly. The usual cause for this is forgetting to include the IL instruction that ends the program scan. If an "end" instruction is not encountered, the program scan will "fall off the end" of the program. There is usually no adverse consequence to this other than the expected exit code not being set.
An exit code of run "exception_error" indicates that an undefined run time error was encountered. This is always an error that terminates the scan immediately. This error should not be encountered under any normal circumstances and always indicates a serious error which prevents the program from executing correctly.
The following shows a simplified example. This examples omits exception handling. The first example shows the code used to initialise and compile the PLC program.
############################################################ from mbsoftlogicck import PLCInterp from mbsoftlogicck import PLCCompile from mbsoftlogicck import DLCkInstructions from mbsoftlogicck import DLCkDataTable from mbsoftlogicck import DLCkLibs ############################################################ # Compiler for PLC program. PLCCompiler = PLCCompile.PLCCompiler(DLCkInstructions.InstrDefList, DLCkDataTable.InstrDataTable) # Read in the PLC program. PLCCompiler.ReadInFile('demoplcprog.txt') # Compile the PLC program. plcprogram, instrcount, CompileErrors = PLCCompiler.CompileProgram() # Get any compiler error messages. CompileErrMsgs = PLCCompiler.GetCompileErrors() for i in CompileErrMsgs: print(i) # Initialise the interpreter with the PLC program and data table. if (not CompileErrors): MainInterp = PLCInterp.PLCInterp(plcprogram, DLCkDataTable.BoolDataTable, DLCkDataTable.WordDataTable, DLCkDataTable.InstrDataTable, DLCkLibs.TableOperations, DLCkDataTable.Accumulator, DLCkLibs.BinMathLib, DLCkLibs.FloatMathLib, DLCkLibs.BCDMathLib, DLCkLibs.WordConversions, DLCkLibs.CounterTimers, DLCkLibs.SystemScan)
The second half of the example below shows code which must be called each scan.
# Update the data table with new inputs. In a real application the values # would be variables passed in from the main application. A similar function # is available for the word data table. MainInterp.SetBoolData({'X17' : True, 'X277' : False}) # This causes the PLC program to be executed once. MainInterp.MainLoop() # This is gets the updated data table elements to be passed to the rest of # the application. BData = MainInterp.GetBoolData(['Y1', 'Y367', 'C50', 'CT100']) # Get the program exit code to see if the PLC program exited normally, or # if there was an error. ExitCode, ExitSubr, ExitRung = MainInterp.GetExitCode()