Plant SCADA 2023 R2 export variables current value to CSV file

Dear Experts,

We are using Plant SCADA 2023 R2. We need to take a snapshot of 100 variables and store these values into a CSV/Excel file. We will store the current data only, no need for logging and multiple entries.

What is the most efficient way to achieve that?

Thanks

Parents
  • If you turn on persistence the values are automatically stored in an XML format.

    If you need to write this data multiple times into a file I would suggest to:

    • Start a TagBrowseOpen() function and use a filter on for instance a CUSTOM field to select the right tags
    • Loop through the tags and use TagSubscribe() and store the handles in for instance a Cicode Int array
    • When you want to write to a file, process the handles and use SubscriptionGetAttribute() to get the value and write it to a file
  • Dear Bas,

    Thank you for your support,

    I am sorry but your answer was not clear to me and it didn't explain how to write the variables value into a CSV/Excel file.

    I simply need to write a script when executed to write the current values of 100 variables into a temporary file. this file will only be used once then deleted. No need to reopen the file and append new values once the code is executed again.

    Thanks

  • Hi  
    Below is the sample code and hope it would help.

    Save the following sample code to a Cicode file and add it to your project. 

    MODULE STRING My100TagNames[100];
    MODULE INT nLoggingFlag=0;
    //Call this function on Button or an event - EnableLogging(1);
    FUNCTION EnableLogging(INT nFlag) 
    	nLoggingFlag = nFlag;
    END
    
    //This function should be called by a task
    FUNCTION StartLoggingSnapshotTagValues()
    	WHILE(1) DO
    		IF nLoggingFlag <> 0 THEN
    			nLoggingFlag = 0;
    			LoggingSnapshotTagValues();
    		END
    		SleepMS(1000);
    	END
    END
    
    Function LoggingSnapshotTagValues()
    INT hFileWrite;
    INT iFileHeader = 0;
    INT Index;
    STRING sLine;
    
    	// You can specify your own file location and use TIMESTAMP as part of the file Name
    	STRING sFileName = "[RUN]:MySnapshotValues.csv"; 
    	My100TagNames[0] = "DEV01_TAG00003";
    	My100TagNames[1] = "DEV04_TAG00002";
    
    	EnterCriticalSection("Log100TagValues");
    	ErrSet(1);
    	sFileName = PathToStr(sFileName);
    
    	IF FileExist(sFileName) THEN 
    		iFileHeader = 0;
    	ELSE
    		iFileHeader = 1;
    	END
    
    	hFileWrite = FileOpen(sFileName, "a+");
    	IF hFileWrite = -1 THEN
    		ErrLog("Fileopen failed!");
    		RETURN;
    	END
    
    	IF (iFileHeader = 1) THEN
    		// If an opened file is new, generate the header for each column in this csv file.
    		sLine = "Name,Value,Quality,Timestamp";	
    		FileWriteLn(hFileWrite, sLine);
      	END	
    
    	// Note: Tag Read is a blocking function
    	FOR Index = 0 TO 1 DO
    		sLine = My100TagNames[Index] + "," + TagReadEx(My100TagNames[Index]) + "," + TagReadEx(My100TagNames[Index] + ".Q") + "," + TagReadEx(My100TagNames[Index] + ".T");
    		FileWriteLn(hFileWrite, sLine);
    	END
    
    	// Logging is completed.
    	FileClose(hFileWrite);
    	ErrSet(0);
    	LeaveCriticalSection("Log100TagValues");
    END

    • Added TaskNew("StartLoggingSnapshotTagValues", "", 0); to your startup code of the client. 
    • In the sample code, only 2 tags are populated. 
    • You should create a separate function to assign those 100 tags to array My100TagNames[100]. As  suggested, you could also use TagBrowse functions to populate the wanted tags and assign them to the array. It is up to you how to obtain those 100 tags.
    • Remove Quality and Timestamp parts if not wanted and update the header as well
  • Hi  ,

    Thank you for you reply it solved my problem efficiently.

    Can you please clarify why did you used the EnterCiticalSection and why you are suggesting to call this function as a Task?

Reply Children
  • Hi  , 

    A task is an asynchronized operation that will not block the main thread of Plant SCADA. For example, TagRead() is a blocking function that cannot be called directly from a page button.

    EnterCiticalSection is just like a safe lock to prevent another request from breaking the data integrity in processing the current snapshot. If you have only one set of tags to process, it is not required. Hope I have answered your questions.

  • Hi  

    Thank you. Do you mean that if I am in my code only processing one snapshot request at a time I won't need the EnterCiticalSection?

    I have multiple sets of tags but I am proccing them one by one in a single Cicode file.

  • I am not sure if there is any sleep between processing multiple sets or signal indicating the previous set is completed before he next one is sent. Anyway, if some requests are overlapped, you do need this lock to ensure the data integrity of each snapshot is maintained.

  • I could be mistaken, but I feel like you're asking what the difference is between directly calling a function or using TaskNew to execute a function. When you directly call a function, it blocks the function calling it. The calling function will not continue execution until the called function completes its execution. TaskNew will initiate a new thread, call the function, then continue to execute lines in the calling function. Here are two examples . . . 

    Calling using TaskNew:

    FUNCTION

    CallingFunction()

    Variable1 = 23;

    Variable2 = 6;

    TaskNew("CalledFunction1", "", 0);

    TaskNew("CalledFunction2", "", 0);

    TaskNew("CalledFunction3", "", 0);

    Variable3 = 94;

    Variable4 = TRUE;

    END

    Calling directly:

    FUNCTION

    CallingFunction()

    Variable1 = 23;

    Variable2 = 6;

    CalledFunction1();

    Called Function2();

    CalledFunction3();

    Variable3 = 94;

    Variable4 = TRUE;

    END

    When calling via TaskNew, each called function will run in its own thread, then immediately move on to the next line of code in the calling function. A function like this will execute in just a few milliseconds. The beauty of this is that if there is a problem with the execution of any particular called function it won't interfere with the execution of either the calling function or any of the other called functions.

    When calling directly, the called function will suspend execution of the calling function, go run the called function, and when the called function has completed its execution will then return to the calling function and execute the next line of code. The potential danger in this is that (from above example) if there is a problem in any of the called functions (CalledFunction1(), CalledFunction2(), or CalledFunction3()) that would cause it to get stuck in an infinite loop or take an excessive amount of time to execute, it will delay all other lines of code in the calling function downstream from the point that it is stuck at.

    There are benefits and drawbacks to both approaches, and I use both in the same function. If you have several functions, and it is absolutely necessary that one function completes its operations before another is executed, call it directly. This is most frequently done when initializing systems and certain operations have to be online before calling other operations. If you have various, unrelated functions that all do different things you will get much faster execution by using TaskNew to call those functions.

  •   
    -If I am calling the requests sequentially in the same file without any sleep or signal indicating the previous set is completed before the next one is sent. In this manner as far as I understand each line will only be executed if the previous line is done so in this case it is impossible two TagRead() requests to be executed in parallel. Is that true?

    -To make sure I understand the blocking function is not called in by a task the main thread of the plant SCADA will be blocked and if there is an infinite loop the plant SCADA will be stuck and nothing will be executed until the end of the blocking function. Am I right here?

  •  If the function is called multiple times from another function (without using TaskNew()), they would run sequentially and would not conflict. The problem would be if multiple tasks were calling the function, such as a button on a page that could be clicked multiple times before the task finishes.

    Plant SCADA has multitasking built in so an infinite loop is OK. The Cicode task will not block the main thread unless you specifically call an external DLL function or try to access an unavailable file across the network.