Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Tecnología Industrial

Almacenamiento de Datos de Proceso con SQLite en Proyectos C++ de PLCnext

Este artículo describe cómo el motor de base de datos SQLite ya instalado en Controladores PLCnext podría usarse para almacenar datos proporcionados a través del Espacio Global de Datos (GDS). La base de datos permite el almacenamiento de datos de proceso de forma estandarizada y podría exportarse a otros sistemas con SFTP.

Por favor asegúrate de que la versión de la herramienta plcncli coincida con la versión de firmware de tu controlador.

Crear un Proyecto C++ en Eclipse

Crea un nuevo proyecto C++ en Eclipse, siguiendo las instrucciones del PLCnext Info Center con las siguientes propiedades:

Otros nombres también estarían bien, sin embargo, un nombre común simplifica el tutorial.

Crea una nueva carpeta (misma jerarquía que la carpeta src) en el proyecto y nómbrala 'cmake'. Dentro de la carpeta crea un archivo y nómbralo 'FindSqlite.cmake' e inserta el siguiente contenido en él.

FindSqlite.cmake

# Copyright (c) 2018 PHOENIX CONTACT GmbH & Co. KG 
# Created by Björn sauer 
# 
# - Find Sqlite 
# Find the Sqlite headers and libraries. 
# 
# Defined Variables: 
# Sqlite_INCLUDE_DIRS - Where to find sqlite3.h. 
# Sqlite_LIBRARIES - The sqlite library. 
# Sqlite_FOUND - True if sqlite found. 
# 
# Defined Targets: 
# Sqlite::Sqlite 
find_path(Sqlite_INCLUDE_DIR NAMES sqlite3.h) 
find_library(Sqlite_LIBRARY NAMES sqlite3) 
include(FindPackageHandleStandardArgs) 
find_package_handle_standard_args(Sqlite DEFAULT_MSG Sqlite_LIBRARY Sqlite_INCLUDE_DIR) 
if(Sqlite_FOUND) 
set(Sqlite_INCLUDE_DIRS "${Sqlite_INCLUDE_DIR}") 
set(Sqlite_LIBRARIES "${Sqlite_LIBRARY}") 
mark_as_advanced(Sqlite_INCLUDE_DIRS Sqlite_LIBRARIES) 
if(NOT TARGET Sqlite::Sqlite) 
add_library(Sqlite::Sqlite UNKNOWN IMPORTED) 
set_target_properties(Sqlite::Sqlite PROPERTIES IMPORTED_LOCATION "${Sqlite_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Sqlite_INCLUDE_DIRS}") 
endif() 
endif() 

Reemplaza el contenido de los archivos DBComponent.cpp y DBComponent.hpp con lo siguiente:

DBComponent.hpp

#pragma once 
#include "Arp/System/Core/Arp.h" 
#include "Arp/System/Acf/ComponentBase.hpp" 
#include "Arp/System/Acf/IApplication.hpp" 
#include "Arp/Plc/Commons/Esm/ProgramComponentBase.hpp" 
#include "DBComponentProgramProvider.hpp" 
#include "Arp/Plc/Commons/Meta/MetaLibraryBase.hpp" 
#include "Arp/System/Commons/Logging.h" 
#include "CppDBLibrary.hpp" 
#include "Arp/System/Acf/IControllerComponent.hpp" 
#include "Arp/System/Commons/Threading/WorkerThread.hpp" 
#include <sqlite3.h> 
namespace CppDB { 
using namespace Arp; 
using namespace Arp::System::Acf; 
using namespace Arp::Plc::Commons::Esm; 
using namespace Arp::Plc::Commons::Meta; 
//#component 
class DBComponent : public ComponentBase, public IControllerComponent, public ProgramComponentBase, private Loggable<DBComponent> { 
public: 
// typedefs 
public: 
// construction/destruction 
DBComponent(IApplication& application, const String& name); 
virtual ~DBComponent() = default; 
public: 
// IComponent operations 
void Initialize() override; 
void LoadConfig() override; 
void SetupConfig() override; 
void ResetConfig() override; 
void PowerDown() override; 
public: 
// IControllerComponent operations 
void Start(void) override; 
void Stop(void) override; 
public: 
// ProgramComponentBase operations 
void RegisterComponentPorts() override; 
void WriteToDB(); 
private: 
// methods 
DBComponent(const DBComponent& arg) = delete; 
DBComponent& operator= (const DBComponent& arg) = delete; 
public: 
// static factory operations 
static IComponent::Ptr Create(Arp::System::Acf::IApplication& application, const String& name); 
private: 
// fields 
DBComponentProgramProvider programProvider; 
WorkerThread workerThread; 
private: 
// static fields 
static const int workerThreadIdleTimeWrite = 10; // 10 ms 
public: 
// Ports 
//#port 
//#attributes(Input) 
int16 control = 0; 
//#port 
//#attributes(Input) 
int16 intArray[10] {}; // INT en PLCnext Engineer 
//#port 
//#attributes(Input) 
float32 floatArray[10] {}; // REAL en PLCnext Engineer 
//#port 
//#attributes(Output) 
int16 status = 0; 
}; 
// inline methods of class DBComponent 
inline DBComponent::DBComponent(IApplication& application, const String& name) 
: ComponentBase(application, ::CppDB::CppDBLibrary::GetInstance(), name, ComponentCategory::Custom) 
, programProvider(*this) 
, workerThread(make_delegate(this, &DBComponent::WriteToDB), workerThreadIdleTimeWrite, "CppDB.WriteToDatabase") 
// WorkerThread 
, ProgramComponentBase(::CppDB::CppDBLibrary::GetInstance().GetNamespace(), programProvider) 
{ } 
inline IComponent::Ptr DBComponent::Create(Arp::System::Acf::IApplication& application, const String& name) { 
return IComponent::Ptr(new DBComponent(application, name)); 
} 
} // end of namespace CppDB 

DBComponent.cpp

#include "DBComponent.hpp" 
#include "Arp/Plc/Commons/Esm/ProgramComponentBase.hpp" 
namespace CppDB { 
sqlite3 *db = nullptr; // pointer to the database 
sqlite3_stmt * stmt = nullptr; // needed to prepare 
std::string sql = ""; // sqlite statement 
int rc = 0; // for error codes of the database 
void DBComponent::Initialize() { 
// never remove next line 
ProgramComponentBase::Initialize(); 
// subscribe events from the event system (Nm) here 
} 
void DBComponent::LoadConfig() { 
// load project config here 
} 
void DBComponent::SetupConfig() { 
// never remove next line 
ProgramComponentBase::SetupConfig(); 
// setup project config here 
} 
void DBComponent::ResetConfig() { 
// never remove next line 
ProgramComponentBase::ResetConfig(); 
// implement this inverse to SetupConfig() and LoadConfig() 
} 
#pragma region IControllerComponent operations 
void DBComponent::Start() { 
// start your threads here accessing any Arp components or services 
// open the database connection 
// the database path (/opt/plcnext/) and name (database) could be modified 
rc = sqlite3_open("/opt/plcnext/database.db", &db); 
if( rc ) { 
Log::Error("DB - 1 - {}", sqlite3_errmsg(db)); 
status = 1; 
return; 
} else{ 
// modify the database behaviour with pragma statements 
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, NULL); 
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, NULL); 
sqlite3_exec(db, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL); 
// create tables 
sql = "CREATE TABLE IF NOT EXISTS tb0 (" 
"_id INTEGER PRIMARY KEY, " 
"value1 INTEGER DEFAULT 0, " 
"value2 REAL DEFAULT 0.0 );"; 
// execute the sql-statement 
rc = sqlite3_exec(db, sql.c_str(), 0, 0, 0); 
if(rc) { 
Log::Error("DB - 3 - {}", sqlite3_errmsg(db)); 
status = 3; 
} 
} 
// prepare sql-statement 
sql = "INSERT INTO tb0 (value1, value2) VALUES (?,?)"; 
rc = sqlite3_prepare_v2(db, sql.c_str(), strlen(sql.c_str()), &stmt, nullptr); 
if(rc) { 
Log::Error("DB - 4 - {}", sqlite3_errmsg(db)); 
status = 4; 
} 
// start the WorkerThread 
this->workerThread.Start(); 
} 
void DBComponent::Stop() { 
// stop your threads here accessing any Arp components or services 
// delete the prepared sqlite statements 
rc = sqlite3_finalize(stmt); 
{ 
Log::Error("DB - 1 - {}", sqlite3_errmsg(db)); 
status = 1; 
} 
// close the database connection 
rc = sqlite3_close(db); 
{ 
Log::Error("DB - 1 - {}", sqlite3_errmsg(db)); 
status = 1; 
} 
// stop the WorkerThread 
this->workerThread.Stop(); 
} 
#pragma endregion 
void DBComponent::PowerDown() { 
// implement this only if data must be retained even on power down event 
// Available with 2021.6 FW 
} 
void DBComponent::WriteToDB() { 
// store data in the database 
if(control == 1) { 
// start transaction 
rc = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL); 
if(rc) { 
Log::Error("DB - 5 - {}", sqlite3_errmsg(db)); 
status = 5; 
} 
// iterate over the arrays 
for(int i = 0; i < 10; i++) { 
// bind values to the prepared statement 
rc = sqlite3_bind_int(stmt, 1, intArray[i]); 
if(rc) { 
Log::Error("DB - 6 - {}", sqlite3_errmsg(db)); 
status = 6; 
} 
rc = sqlite3_bind_double(stmt, 2, floatArray[i]); 
if(rc) { 
Log::Error("DB - 6 - {}", sqlite3_errmsg(db)); 
status = 6; 
} 
// execute the sqlite statement and reset the prepared statement 
rc = sqlite3_step(stmt); 
rc = sqlite3_clear_bindings(stmt); 
if(rc) { 
Log::Error("DB - 6 - {}", sqlite3_errmsg(db)); 
status = 6; 
} 
rc = sqlite3_reset(stmt); 
if(rc) { 
Log::Error("DB - 6 - {}", sqlite3_errmsg(db)); 
status = 6; 
} 
} 
// end transaction 
rc = sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); 
if(rc) { 
Log::Error("DB - 5 - {}", sqlite3_errmsg(db)); 
status = 5; 
} 
} 
// delete the database entries 
if(control == 2) { 
// begin transaction 
rc = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL); 
if(rc) { 
Log::Error("DB - 5 - {}", sqlite3_errmsg(db)); 
status = 5; 
} 
rc = sqlite3_exec(db, "DELETE FROM tb0", 0, 0, 0); 
if(rc) { 
Log::Error("DB - 7 - {}", sqlite3_errmsg(db)); 
status = 7; 
} 
// end transaction 
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); 
if(rc) { 
Log::Error("DB - 5 - {}", sqlite3_errmsg(db)); 
status = 5; 
} 
// release the used memory 
rc = sqlite3_exec(db, "VACUUM", 0, 0, 0); 
if(rc) { 
Log::Error("DB - 8 - {}", sqlite3_errmsg(db)); 
status = 8; 
} 
} 
} } // end of namespace CppDB 

Posteriormente, construye el proyecto. La librería PLCnext creada puede encontrarse en el directorio del proyecto (C:\Users\eclipse-workspace\CppDB\bin).

Explicación

En este enfoque se usa un WorkerThread para manejar la operación de escritura. Este es un hilo de baja prioridad que repite la ejecución del código enhebrado hasta que Stop() es llamado. En el hilo verificaremos si hay nuevos datos para la base de datos disponibles y almacenaremos los datos. Después de la ejecución, el WorkerThread espera un tiempo especificado (aquí: 10 ms).

Con la ayuda del puerto 'control', podemos disparar diferentes operaciones de base de datos. Los datos que deben almacenarse se proporcionan con los puertos 'intArray' y 'floatArray'.

Almacenamiento de Datos de Proceso con SQLite en Proyectos C++ de PLCnext

Creemos un programa IEC simple:

IF iControl = 1 THEN iControl := 0; END_IF; 
IF xWrite THEN 
arrInt[0] := 0; arrInt[1] := 1; arrInt[2] := 2; 
arrInt[3] := 3; arrInt[4] := 4; arrInt[5] := 5; 
arrInt[6] := 6; arrInt[7] := 7; arrInt[8] := 8; 
arrInt[9] := 9; 
arrReal[0] := 9.0; arrReal[1] := 8.0; arrReal[2] := 7.0; 
arrReal[3] := 6.0; arrReal[4] := 5.0; arrReal[5] := 4.0; 
arrReal[6] := 3.0; arrReal[7] := 2.0; arrReal[8] := 1.0; 
arrReal[9] := 0.0; 
iControl := 1; 
xWrite := FALSE; 
END_IF; 
Almacenamiento de Datos de Proceso con SQLite en Proyectos C++ de PLCnext

Finalmente, tenemos que conectar los puertos:

Almacenamiento de Datos de Proceso con SQLite en Proyectos C++ de PLCnext

Ahora, podemos compilar el proyecto y enviarlo a un PLC conectado. En el 'modo en vivo', podemos interactuar con la base de datos, asignando diferentes valores a la variable 'iControl'.

Una base de datos 'database.db' se crea en el directorio del controlador /opt/plcnext. Podemos acceder a ella con herramientas como WinSCP. Podemos verificar el contenido de la base de datos con la herramienta DB Browser (SQLite):

Almacenamiento de Datos de Proceso con SQLite en Proyectos C++ de PLCnext

Información Adicional

Sentencias pragma de SQLite

Tecnología Industrial

  1. Cómo convertirse en un campeón digital en la fabricación: Guía práctica
  2. Convertir proyectos de analítica de datos en éxito real: De la planificación al despliegue
  3. Dominar la inteligencia de negocio: Crear una estrategia ganadora para el éxito basado en datos
  4. Garantizando datos confiables en la cadena de suministro: Una estrategia probada
  5. Cómo la IA resuelve los desafíos de datos "sucios" en las cadenas de suministro
  6. Factores clave que predicen el éxito de proyectos Big Data
  7. Integrar Alibaba Cloud Connector con PLCnext: Guía paso a paso
  8. Consolidación Data Center: Reduciendo Costes, Mejorando Seguridad y Eficiencia
  9. Entendiendo los Tipos de Datos en C++: Fundamentos, Modificadores y Mejores Prácticas
  10. Dominando la Abstracción de Datos en C++: Simplifica el Diseño con Separación Interfaz-Implementación
  11. Encapsulamiento de Datos en C++: Protege tu Código