Skip to content

Latest commit

 

History

History
231 lines (183 loc) · 9.2 KB

File metadata and controls

231 lines (183 loc) · 9.2 KB

This is part of a series of articles that demonstrate how to implement a DataLogger application based on the DataLogger service provided by the PLCnext Control firmware. Each article builds on tasks that were completed in earlier articles, so it is recommended to follow the series in sequence.

Part 5 - Using RSC Services for access to the logged data via Rsc service

The example below uses the Call "GetLoggedVariables" of "IDataLoggerService" RSC service to access to the logged data, and store the values into byte array. In the folder "src" of this article you will find the completely implemented source code. If you don't have much time to implement the code yourself, you can replace the files in your Eclipse project with files in that src folder.

Please note, the following code has following critical implementation that will be fixed in the next part of this article: By storage of data in static byte array, you do not know how many records are read, only how big a record is! Therefore, the code is very critical, because the index of variables can go beyond the index limits of the array. In outher words, if the memory is not enough the storage will be written beyond the limits! This sample code should only be used for demonstration of service call method implementation and is an intermediate step to the final solution.

  1. Include into Project CppDataLogger the method "ReadVariablesDataToByte":
  • Open and include following declaration of methods and fields into header file of component "CppDataLoggerComponent.hpp"

    (click to see/hide code)
    private: // methods
    ErrorCode ReadVariablesDataToByte(const Arp::String& sessionName,
        const Arp::DateTime& startTime, const Arp::DateTime& endTime,
    	const std::vector<Arp::String>& variableNames, uint8* byteMemory);
    
    private: // fields
    //Session Name
    Arp::String sessionname = {};
    
    //Vector for Variable Names, sorted by name.
    std::vector<Arp::String> CountingVariableNames = {};
    
    //Start and End time as time window parameter
    Arp::DateTime startTime;
    Arp::DateTime endTime;
    
    //Define the buffer for the records. Please note, this code is very critical because,
    //if the memory is not enough the storage will be written beyond the array limits!
    uint8 m_records[2578000];
  • Include the method "ReadVariablesDataToByte" and adapt the "workerThreadBody" in "CppDataLoggerComponent.cpp" file as follows:

    (click to see/hide code)
    //////////////////////////////////////////////////////////////////////////////////////////
    //This is the ReadVariablesDataToByte method with ReadVariablesData service call of   	//
    //DataLogger service. The Service Call reads the data from the given variable from      //
    //the session. This service function returns the data values from the passed variable 	//
    //names including timestamps and data series consistent flags, which is called a record.//
    //                                                                                      //
    //In a record the values are in a static order and doesn't contain any type information.//
    //Each record starts with the timestamp followed by the values from the given variable 	//
    //by names and ends with the consistent flag.                                           //
    //////////////////////////////////////////////////////////////////////////////////////////
    
    ErrorCode CppDataLoggerComponent::ReadVariablesDataToByte(const Arp::String& sessionName,
    	const Arp::DateTime& startTime, const Arp::DateTime& endTime,
    	const std::vector<Arp::String>& variableNames, uint8* byteMemory)
    {
    	IDataLoggerService::ReadVariablesDataValuesDelegate readValuesDelegate =
    		IDataLoggerService::ReadVariablesDataValuesDelegate::create([&](
    			IRscReadEnumerator<RscVariant<512>>& readEnumerator)
    	{
    		size_t r_offset = 0;  //reinitialize the r_offset
    		memset(byteMemory, 0x00, sizeof(byteMemory));  //reinitialize the byteMemory array
    
    		// The readEnumerator gets the N-records,
    		// the number of record is not available, the records come as N (undefined) Records!
    		readEnumerator.BeginRead();
    		RscVariant<512> currentVariant;
    
    		while (readEnumerator.ReadNext(currentVariant))
    		{
    			RscType rscType = currentVariant.GetType();
    
    			// Check if the rscType is a Array,
    			// if yes -> the next record is founded
    			if (rscType == RscType::Array)
    			{
    				RscArrayReader arrayReader(currentVariant); //read currentVariant into arrayReader
    				size_t arraySize = arrayReader.GetSize();   //Get the size of Array
    
    				for (size_t i = 0; i < arraySize; i++)  // for each element in the array
    				{
    					// The Value will be copied into variant
    					RscVariant<512> valueTmp;
    					arrayReader.ReadNext(valueTmp);
    
    					// Each RscType should be check separately
    					// The following data types are expected: DateTime, Bool, Uint64 and Void(NULL)
    					switch (valueTmp.GetType())
    					{
    						case RscType::DateTime:  //if the DataType is DateTime
    						{
    							/*Start of dummy Code: Only for Output of TimeStamp*/
    							Arp::DateTime recordTime;
    							valueTmp.CopyTo(recordTime);
    							Log::Info("DateTime: {0}", recordTime.ToBinary());
    							/*End of dummy Code*/
    							
    						//reinitialize the dateTimeBuffer
    						uint8 dateTimeBuffer[8] = {0}; 		
    						
    						//copy the time stamp value to dateTimeBuffer
    						valueTmp.CopyTo(*((DateTime*)(dateTimeBuffer)));
    						
    						//write the dateTimeBuffer into byteMemory Array in Byte steps
    						for(int i = 0; i < sizeof(dateTimeBuffer); i++)  
    						{
    							memcpy((byteMemory + r_offset), &dateTimeBuffer[i], 1);
    							r_offset += 1;
    						}
    					 }
    					 break;
    
    					 case RscType::Void:
    					 {
    						//Log::Info("NULL Value = Void RSC-Datatype is found");
    					 }
    					 break;
    
    					 case RscType::Bool:  //if the DataType is Bool
    					 {
    						//copy the logging variable value into byteMemory Array
    						valueTmp.CopyTo(*((bool*)(byteMemory + r_offset))); 
    						r_offset += 1; //increment the offset
    					 }
    					 break;
    
    					 case RscType::Uint64:
    					 {
    							/*Start of dummy Code: Only for Output of EventCount*/
    							uint64 recordEventCounter;
    							valueTmp.CopyTo(recordEventCounter);
    							Log::Info("EvetCounter: {0}", recordEventCounter);
    							/*End of dummy Code*/
    
    						uint8 eventCountBuffer[8] = {0}; //reset eventCountBuffer
    						
    						//copy the event counter value to eventCountBuffer
    						valueTmp.CopyTo(*((uint64*)(eventCountBuffer)));  
    						
    						// write the event counter into byteMemory Array in Byte steps
    						for(int i = 0; i < sizeof(eventCountBuffer); i++)  
    						{
    							memcpy((byteMemory + r_offset), &eventCountBuffer[i], 1);
    							r_offset += 1;
    						}
    					 }
    					 break;
    
    				 default:
    					 break;
    				 }
    			   }
    			}
    		}
    		readEnumerator.EndRead();
    	});
    
    	ErrorCode result;
    
       //Call the ReadVariablesData Method from DataLogger Service
       result = this->m_pDataLoggerService->ReadVariablesData(
    			sessionName,
    			startTime,
    			endTime,
    
       // This is the Delegate for the transmission of VariableNames
       IDataLoggerService::ReadVariablesDataVariableNamesDelegate::create([&](
    		 IRscWriteEnumerator<RscString<512>>& writeEnumerator)
    		 {
    			writeEnumerator.BeginWrite(variableNames.size());
    			for (const auto& varName : variableNames)
    			{
    				writeEnumerator.WriteNext(varName);
    			}
    			writeEnumerator.EndWrite();
    		}),
    		readValuesDelegate);
    	return result;
    };
    
    /// Thread Body
    void CppDataLoggerComponent::workerThreadBody(void) {
    
    	if(!m_bInitialized) // If not initialized
    	{
    		//Set the startTime 1 second earlier as DateTime::Now().
    		Arp::Microseconds ticksNow(DateTime::Now().ToUnixMicrosecondTicks());
    		startTime = Arp::DateTime::FromUnixMicrosecondTicks((ticksNow - Arp::Seconds(1)).count());
    		//Log::Info("startTime: {0}", startTime.ToBinary());
    
    		Init();  //Call Init() function
    	}
    
    	else{
    		   //The time window includes records between two worker thread cycles
    		   endTime = Arp::DateTime::Now(); 
    
    		   ErrorCode result = this->ReadVariablesDataToByte(
    					sessionname, //sessionname is defined in datalogger.config file.
    					startTime,	 //start time 
    					endTime,	 //end time 
    					CountingVariableNames, //vector with logged variable names
    					m_records	//this is the pointer to the ByteArray
    					);
    					
    			//The time window includes records inside worker thread cycle
    			startTime = endTime;  		  	
    		}
    	}
    
  1. Compile the C++ Project.

  2. After compilation, the C++ Library should be updated automatically, if the path to library "CppDataLogger.pcwlx" in Eclipse workspace folder is valid. If not, please replace the library "CppDataLogger.pcwlx" in PLCnext Engineer project with the new generated library.

  3. Download and execute the PLCnEng project on the PLCnext target

  4. Find the DateTime TimeStamps in file "opt/plcnext/logs" on the PLCnext target


Copyright © 2019 Phoenix Contact Electronics GmbH

All rights reserved. This program and the accompanying materials are made available under the terms of the MIT License which accompanies this distribution.