Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Промышленные технологии

Хранение процессных данных с SQLite в проектах PLCnext C++

Эта статья описывает, как движок базы данных SQLite, уже установленный на контроллерах PLCnext, может использоваться для хранения данных, предоставляемых через Глобальное Пространство Данных (GDS). База данных позволяет хранить процессные данные стандартизированным способом и может быть экспортирована в другие системы с SFTP.

Пожалуйста, убедитесь, что версия инструмента plcncli соответствует версии прошивки вашего контроллера.

Создание проекта C++ в Eclipse

Создайте новый C++ проект в Eclipse, следуя инструкциям из PLCnext Info Center со следующими свойствами:

Другие имена также подойдут, однако общее имя упрощает обучение.

Создайте новую папку (такую же иерархию как src) в проекте и назовите 'cmake'. Внутри папки создайте файл и назовите 'FindSqlite.cmake' и вставьте в него следующий контент.

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() 

Замените содержимое файлов DBComponent.cpp и DBComponent.hpp на следующее:

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 в PLCnext Engineer //#port //#attributes(Input) float32 floatArray[10] {}; // REAL в 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 

Затем соберите проект. Созданная библиотека PLCnext может быть найдена в директории проекта (C:\Users\eclipse-workspace\CppDB\bin).

Пояснение

В этом подходе WorkerThread используется для обработки операции записи. Это поток с низким приоритетом, повторяющий выполнение кода потока до вызова Stop(). В потоке мы проверяем, доступны ли новые данные для базы и сохраняем данные. После выполнения WorkerThread ждет указанное время (здесь: 10 мс).

С помощью порта 'control' мы можем запускать разные операции базы данных. Данные, которые должны храниться, предоставляются портами 'intArray' и 'floatArray'.

Хранение процессных данных с SQLite в проектах PLCnext C++

Давайте создадим простую IEC программу:

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; 
Хранение процессных данных с SQLite в проектах PLCnext C++

Наконец, нужно соединить порты:

Хранение процессных данных с SQLite в проектах PLCnext C++

Теперь можно скомпилировать проект и отправить на подключенный ПЛК. В 'live-режиме' мы можем взаимодействовать с базой данных, присваивая разные значения переменной 'iControl'.

База данных 'database.db' создается в директории контроллера /opt/plcnext. Можно получить к ней доступ инструментами вроде WinSCP. Можно проверить содержимое базы инструментом DB Browser (SQLite):

Хранение процессных данных с SQLite в проектах PLCnext C++

Дополнительная информация

SQLite pragma statements


Промышленные технологии

  1. Понимание типов данных C++: основы, модификаторы и лучшие практики
  2. Как стать цифровым чемпионом в производстве: практическое руководство
  3. Консолидация дата-центров: сокращение затрат, усиление безопасности, повышение эффективности
  4. Овладение бизнес-интеллектом: создание выигрышной стратегии для успеха, основанного на данных
  5. Обеспечение надежных данных цепочки поставок: проверенная стратегия
  6. Как ИИ решает проблему «грязных» данных в цепочках поставок
  7. Абстракция данных в C++: разделение интерфейса и реализации
  8. Инкапсуляция данных в C++: защита вашего кода
  9. Ключевые факторы, предсказывающие успех Big Data проектов
  10. Интеграция Alibaba Cloud Connector с PLCnext: Пошаговое руководство