PLCnext on LinkedInPLCnext on Instagram  PLCnext on YouTube Github PLCnext CommunityStore PLCnext Community

 

 How to create a Blog Entry

Datalogging in IEC 61131-3: Create your own file manager

Following on my previous blog "Datalogging in IEC 61131-3: The basic principle", I want to show you something extra.
To read and write data, you only need to implement some basic commands.

In this blog, we are going to create the following methods:

  • Create a file
  • Create a file and directly write a header line
  • Delete a file
  • Read a line
  • Write a line

Please note that some extra error handling is needed for real life applications.

If you are interested in the complete PLCnext Engineer project, just let me know. (This email address is being protected from spambots. You need JavaScript enabled to view it.)

First, we are going to create one custom datatype, for reading and writing data. (arr_Byte_1_500 : Array[1..500] Of Byte)

Then, we need the implementation of our methods. In fact they only transfer some data towards the function block. We don't use a return type because the complete process takes more than one program cycle.

 

// *** Method code: ***

/* Create a file */
sRequestedFileName:= sFileName;

IF iFileManager = 0 THEN
    iFileManager:= 20;
END_IF;

/* Create a file and directly write a header line */
sRequestedFileName:= sFileName;
sRequestedText:= sHeader;

IF iFileManager = 0 THEN
    iFileManager:= 10;
END_IF;

/* Delete a file */
sRequestedFileName:= sFileName;

IF iFileManager = 0 THEN
    iFileManager:= 30;
END_IF;

/* Read a line */
sRequestedFileName:= sFileName;

IF xFirstLine THEN
    diMemoryCursor:= DINT#0;
END_IF;    

IF iFileManager = 0 THEN
    iFileManager:= 40;
END_IF;

/* Write a line */
sRequestedFileName:= sFileName;
sRequestedText:= sText;

IF iFileManager = 0 THEN
    iFileManager:= 10;
END_IF;

The following function block implements the real program behavior.

// *** Function block code: ***

xIdle:= iFileManager = 0;

CASE iFileManager OF

10: // Determine the length of the string and convert it to a byte array

    diLengthTempText:= TO_DINT(LEN(sRequestedText));
    sTempText:= sRequestedText;

    xConvert:= TRUE;
    iFileManager:= 11;

11: IF xDoneConvert THEN
        xConvert:= FALSE;

        iTempPointer:= TO_INT(diLengthTempText) + 1;
        arrBuffer_FW[iTempPointer]:= BYTE#16#0D; /* Carriage return */
        iTempPointer:= iTempPointer + 1;
        arrBuffer_FW[iTempPointer]:= BYTE#16#0A; /* Line feed */
        udiLength_FW:= TO_UDINT(iTempPointer);

        iFileManager:= 150; // Open file
        iReturnStep:= 12;
    END_IF;

12: // Move the current file pointer to a new position, in our case the end of the file

    xExecute_FS:= TRUE;
    uiHandle_FS:= uiHandleMemory;
    diPosition_FS:= DINT#0;
    uiMode_FS:= UINT#2;
    iFileManager:= 13;

13: IF xDone_FS THEN
        xExecute_FS:= FALSE;
        iFileManager:= 14;
    END_IF;

14: // Write data in the file

    xExecute_FW:= TRUE;
    uiHandle_FW:= uiHandleMemory;
    iFileManager:= 15;

15: IF xDone_FW THEN
        xExecute_FW:= FALSE;
        iFileManager:= 100; // Close file
    END_IF;    

20: iFileManager:= 150; // Open file
    iReturnStep:= 100; // Close file

30: // Delete a file
    sName_FRe:= sRequestedFileName;
    xExecute_FRe:= TRUE;
    iFileManager:= 31;

31: IF xDone_Fre THEN
        xExecute_FRe:= FALSE;
        iFileManager:= 0;
    END_IF;

40: iFileManager:= 150; // Open file
    iReturnStep:= 42;

42: // Move the current file pointer to a new position

    xExecute_FS:= TRUE;
    uiHandle_FS:= uiHandleMemory;
    diPosition_FS:= diMemoryCursor;
    uiMode_FS:= UINT#0;
    iFileManager:= 43;

43: IF xDone_FS THEN
        xExecute_FS:= FALSE;
        iFileManager:= 44;
    END_IF;

44: // Read data

    xExecute_FR:= TRUE;
    uiHandle_FR:= uiHandleMemory;
    udiMaxLength_FR:= UDINT#100; // We expect a line with a maximum length of 80 char
    iFileManager:= 45;   

45: IF xDone_FR OR (xError_FR & uiErrorID_FR = UINT#10) THEN
        xExecute_FR:= FALSE;

        iAmountOfData:= TO_INT(udiLengthRead_FR);

        IF iAmountOfData > 0 THEN

            FOR iLoop:= 1 TO iAmountOfData
                DO
                    IF arrBuffer_FR[iLoop] = BYTE#16#0D THEN /* Carriage return */
                        iLengtLine:= iLoop - 1;
                        diLengtLine:= TO_DINT(iLengtLine);
                        diMemoryCursor:= diMemoryCursor + diLengtLine + DINT#2;
                        EXIT;
                    END_IF;
            END_FOR;

            iFileManager:= 46;   
        ELSE
            diMemoryCursor:= DINT#0;
            sData:= '';
            iFileManager:= 100; // Close file  
        END_IF;

    END_IF;

46: xConvertRead:= TRUE;
    iFileManager:= 47;

47: IF xDoneConvertRead THEN
        xConvertRead:= FALSE;
        iFileManager:= 100; // Close file
    END_IF;

// Close file

100:    xExecute_FC:= TRUE;
        uiHandle_FC:= uiHandleMemory;
        iFileManager:= 101;

101:    IF xDone_FC THEN
            xExecute_FC:= FALSE;
            iFileManager:= 0;
        END_IF;

// Open file

150:    sName_FO:= sRequestedFileName;
        xExecute_FO:= TRUE;
        iFileManager:= 151;

151:    IF xDone_FO THEN
            uiHandleMemory:= uiHandle_FO;
            xExecute_FO:= FALSE;
            iFileManager:= iReturnStep;
        END_IF;     

END_CASE;

// Converts the string variable to a byte array    

ConvertString_To_Buf(Req := xConvert,
Buf_Format := FALSE,
Buf_Offs := DINT#0,
Buf_Cnt := diLengthTempText,
Done => xDoneConvert,
Error => xErrorConvert,
Status => iStatusConvert,
SRC := sTempText,
BUFFER := arrBuffer_FW);

// Converts a byte array to a string variable

ConvertBuf_To_String(REQ := xConvertRead,
BUF_FORMAT := FALSE,
BUF_OFFS := DINT#0,
BUF_CNT := diLengtLine,
DONE => xDoneConvertRead,
ERROR => xErrorConvertRead,
STATUS => iStatusConvertRead,
BUFFER := arrBuffer_FR,
DST := sData);

// Opens an existing file in the parameterization memory with a specific name or creates a new file.

FileOpenLogging(Execute := xExecute_FO,
Name := sName_FO,
Done => xDone_FO,
Handle => uiHandle_FO,
Error => xError_FO,
ErrorID => uiErrorID_FO);

// Moves the current file pointer to a new position.

FileSeekLogging(Execute := xExecute_FS,
Handle := uiHandle_FS,
Position := diPosition_FS,
Mode := uiMode_FS,
Done => xDone_FS,
Error => xError_FS,
ErrorID => uiErrorID_FS);

// Writes data to a file that was opened previously.

FileWriteLogging(Execute := xExecute_FW,
Handle := uiHandle_FW,
Done => xDone_FW,
LengthWritten => udiLengthWritten_FW,
Buffer := arrBuffer_FW,
Length := udiLength_FW,
Error => xError_FW,
ErrorID => uiErrorID_FW);

// Reads data from a file that has been opened using the FILE_OPEN function block.

FileReadLogging(Execute := xExecute_FR,
Handle := uiHandle_FR,
Done => xDone_FR,
LengthRead => udiLengthRead_FR,
Buffer := arrBuffer_FR,
MaxLength := udiMaxLength_FR,
Error => xError_FR,
ErrorID => uiErrorID_FR);

// Closes a file in the parameterization memory that has been opened using the FILE_OPEN function block.

FileCloseLogging(Execute := xExecute_FC,
Handle := uiHandle_FC,
Done => xDone_FC,
Error => xError_FC,
ErrorID => uiErrorID_FC);

// Deletes a file with a specific name.

FileRemoveLogging(Execute := xExecute_FRe,
Name := sName_FRe,
Done => xDone_FRe,
Error => xError_FRe,
ErrorID => uiErrorID_FRe);

The following program gives you an overview how you can access our 'file manager'. Please note that we don't have a buffer implemented. This means that you cannot give multiple commands at once.

// *** Program code: ***

FileManager(xIdle => xFileManagerReady,
sData => sResult);

IF xCreateFileWithHeader & xFileManagerReady THEN
    xCreateFileWithHeader:= FALSE;

    FileManager.CreateFileWithHeader('ThisIsMyFirstFile.txt','Timestamp;Value_1;Value_2');
END_IF;

IF xCreateFile & xFileManagerReady THEN
    xCreateFile:= FALSE;

    FileManager.CreateFile('ThisIsMySecondFile.txt');
END_IF;

IF xDeleteFile & xFileManagerReady THEN
    xDeleteFile:= FALSE;

    FileManager.DeleteFile('ThisIsMySecondFile.txt');
END_IF;

IF xWriteLine & xFileManagerReady THEN
    xWriteLine:= FALSE;

    FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 09:34:12;78.3;1285');
END_IF;

CASE iWriteMultipleLine OF

0:  IF xWriteMultiLine THEN
        xWriteMultiLine:= FALSE;
        iWriteMultipleLine:= 1;
    END_IF;

1:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 10:34:12;78.3;1285');    
        iWriteMultipleLine:= 2;
    END_IF;

2:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 11:45:12;7.3;185');    
        iWriteMultipleLine:= 3;
    END_IF;    

3:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 12:04:08;35.8;17777');    
        iWriteMultipleLine:= 0;
    END_IF;

END_CASE;

IF xReadFirstLine & xFileManagerReady THEN // Output is written to variable 'sResult'
    xReadFirstLine:= FALSE;

    // Read the first line from this file, the cursor position is set to zero
    FileManager.ReadLine('ThisIsMyFirstFile.txt',TRUE);
END_IF;

IF xReadNextLine & xFileManagerReady THEN // Output is written to variable 'sResult'
    xReadNextLine:= FALSE;

    // Read the next line from this file, the cursor position is shifted by the program
    // When the end of the file is reached, you will receive an empty string
    // The next attempt, you will receive the first line again
    FileManager.ReadLine('ThisIsMyFirstFile.txt',FALSE);
END_IF;