Implement an user friendly Error Handling (Required)
Return only error codes which are documented within your library.
Never simply return the original error code from sub libraries.
Map it to your own error domain.
Each function block should have some outputs for error signaling. Please refer the chapter Behaviour Model and Interface Design
The error handling should be designed in a way that only error codes are returned, which are documented in the affected library. It is very convenient but not recommended simply to return untreated error codes from sublibraries. This would result in a bad user experience. It is recommended to map foreign error codes to the error range of the affected library.
With CODESYS 3.5.3.0 the tool generates a new warning message when the compiler detects an assignment of one ENUM type to another. See the example described in the following. In this example we take a closer look to the interaction between the Memory Block Manager library (MBM) and the Function Block Factory (FBF). Each library defines its own ERROR Enum data type.
The ERROR Enum of the library “Function Block Factory” (FBF)
{attribute 'qualified_only'}
TYPE ERROR : (
NO_ERROR := 0, // The defined operation was executed successfully
TIMEOUT := 1, // The specified operation time was exceeded
INVALID_PARAM := 10, // One or more function parameters have no valid value
NO_MEMORY := 20, // The extension of memory pool is not possible
(*...*)
END_TYPE
The ERROR Enum of the library “Memory Block Manager” (MBM)
{attribute 'qualified_only'}
TYPE ERROR : (
NO_ERROR := 0, // The defined operation was executed successfully
NO_MEMORY := 10 // The memory pool has no further capacity
HANDLE_INVALID := 20, // The object was not created properly or has been already released
WRONG_ALIGNMENT := 30, // The structure description aligns not properly to the block specification
(*...*)
END_TYPE
Two libraries are isolated with a namespace (in this example FBF and MBM).
Each ERROR Enum declaration should respect two predefined error codes.
NO_ERROR ⇒ 0 (Zero)
TIME_OUT ⇒ 1 (One)
If the TIME_OUT error code has no usage in a specific domain the value should not reused for an other error code.
Any error code need a short description about the background of its error condition.
A Enum data types should be isolated from other Enum data types with its own namespace ({attribute 'qualified_only'}). FBF.ERROR.NO_MEMORY has a completely different meaning as MBM.ERROR.NO_MEMORY.
In the FBF library (Version 3.5.1.0) we can find the following code:
// :return: hInst
METHOD prvAllocInstMem : CAA.HANDLE
VAR_OUTPUT
eError: ERROR;
END_VAR
(*...*)
prvAllocInstMem := MBM._PoolGetBlock(_hPool, ADR(eError)); // see note (1)
(*...*)
eError := MBM._PoolExtendH(_hPool, ctInstCount); // see note (2)
Try to write a value of type MBM.ERROR to a memory location of type FBF.ERROR => not recommended
Assign a value of type MBM.ERROR to a variable of type FBF.ERROR => Compiler warning
This code breaks rule #6 and cannot be translated without a compiler warning in CODESYS 3.5.3.0. In the current document we recommend two possible solutions:
Summarize all ERROR values from the sublibrary in one ERROR value of the affected library
Map some ERROR values of the sublibrary to the related values of the affected library
In the following see an example for solution 1:
// :return: hInst
METHOD prvAllocInstMem : CAA.HANDLE
VAR_OUTPUT
eError: ERROR;
END_VAR
VAR
(*...*)
iError : INT;
END_VAR
(*...*)
prvAllocInstMem := MBM._PoolGetBlock(_hPool, ADR(iError)); // see note (3)
eError := SEL(iError=0, ERROR.NO_MORE_MEMORY, ERROR.NO_ERROR); // see note (4)
(*...*)
iError := MBM._PoolExtendH(_hPool, ctInstCount); // see note (5)
eError := SEL(iError=0, ERROR.NO_MORE_MEMORY, ERROR.NO_ERROR); // see note (4)
Try to write a value of type MBM.ERROR to a memory location of type INT => ok
Summarize all MBM.ERROR values to one FBF.ERROR value
Assign a value of type MBM.ERROR to a variable of type INT => ok.
This new code assumes that all errors from the MBM library will be treated as a “no more memory available” situation.
Now see an example for solution 2:
Sometimes the two concerned ENUM data types of two libraries are in a special relationship. Some elements of the first ENUM type have the same integer values as the related elements of the second ENUM data type. In this case, using a simple cast operation is a possible solution to avoid the warning message.
eError := TO_INT(MBM._PoolExtendH(_hPool, ctInstCount));
This example assumes that the base data type of eError is INT. In CODESYS it is possible to define a different base type for a specific ENUM type. Please adapt the example in the right way. For example:
{attribute 'qualified_only'}
TYPE FILE_ENCODING :
(
UNKNOWN, // The encoding is not known
UTF8, // The Unicode encoding is UTF-8
UTF16BE, // The Unicode encoding is UTF-16 big endian
UTF16LE // The Unicode encoding is UTF-16 little endian
) : DINT;
END_TYPE
{attribute 'qualified_only'}
TYPE ENCODING :
(
UNKNOWN, // The encoding is not known
UTF8, // The Unicode encoding is UTF-8
UTF16BE, // The Unicode encoding is UTF-16 big endian
UTF16LE // The Unicode encoding is UTF-16 little endian
) : USINT;
END_TYPE
VAR
eFileEncoding : FILE_ENCODING;
eEncoding : ENCODING;
END_VAR
eEncoding := TO_USINT(eFileEncoding);
Working together with sub libraries brings up the need for mapping the different error domains to the one local domain. The next example demonstrates the possible design of an error code mapping function. It handles the error codes (from CS.ERROR and CO.ERROR) of two sub libraries and tries to map these to the one local Error Enum (CANOPEN_KERNEL_ERROR) (All enum data types in this example have the base type INT).
FUNCTION MapError : CANOPEN_KERNEL_ERROR
VAR_INPUT
eError : INT;
END_VAR
MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_UNKNOWN_ERROR;
IF eError = CS.ERROR.NO_ERROR THEN
MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_NO_ERROR;
ELSIF eError > CS.ERROR.FIRST_ERROR AND eError < CS.ERROR.LAST_ERROR THEN
CASE eError OF
CS.ERROR.TIME_OUT : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_TIMEOUT;
CS.ERROR.REQUEST_ERROR : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_REQUEST_ERROR;
CS.ERROR.WRONG_PARAMETER : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_WRONG_PARAMETER;
CS.ERROR.NODEID_UNKNOWN : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_NODEID_UNKNOWN;
CS.ERROR.SDOCHANNEL_UNKNOWN : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_SDOCHANNEL_UNKNOWN;
ELSE
MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_OTHER_ERROR;
END_CASE
ELSIF eError > CO.ERROR.FIRST_ERROR AND eError < CO.ERROR.LAST_ERROR THEN
CASE eError OF
CO.ERROR.TIME_OUT : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_TIMEOUT;
CO.ERROR.NO_MORE_MEMORY : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_NO_MORE_MEMORY;
CO.ERROR.WRONG_PARAMETER: MapError := CANOPEN_KERNEL_ERROR.CANOPEN_WRONG_PARAMETER;
CO.ERROR.NODEID_UNKNOWN : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_NODEID_UNKNOWN;
CO.ERROR.NETID_UNKNOWN : MapError := CANOPEN_KERNEL_ERROR.CANOPEN_NETID_UNKNOWN;
ELSE
MapError := CANOPEN_KERNEL_ERROR.CANOPEN_KERNEL_OTHER_ERROR;
END_CASE
END_IF
This design assumes CS.ERROR.NO_ERROR has the same value as CO.ERROR.NO_ERROR and the rest of the value range of CS.ERROR and CO.ERROR is disjunct.