산업기술
이 기사에서는 PLCnext Controller에 이미 설치된 SQLite 데이터베이스 엔진을 사용하여 GDS(Global Data Space)를 통해 제공되는 데이터를 저장하는 방법에 대해 설명합니다. 데이터베이스는 프로세스 데이터를 표준화된 방식으로 저장할 수 있도록 하며 SFTP를 사용하여 다른 시스템으로 내보낼 수 있습니다.
plcncli 도구 버전이 컨트롤러의 펌웨어 버전과 일치하는지 확인하십시오.
다음 속성을 사용하여 PLCnext Info Center의 지침에 따라 Eclipse에서 새 C++ 프로젝트를 생성합니다.
다른 이름도 괜찮지만 일반적인 이름은 튜토리얼을 단순화합니다.
프로젝트에 새 폴더(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 in PLCnext Engineer
//#port
//#attributes(Input)
float32 floatArray[10] {}; // REAL in 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 Library는 프로젝트 디렉토리(C:\Users\eclipse-workspace\CppDB\bin)에서 찾을 수 있습니다.
이 접근 방식에서 WorkerThread는 쓰기 작업을 처리하는 데 사용됩니다. Stop()
까지 스레드된 코드의 실행을 반복하는 우선순위가 낮은 스레드입니다. 라고 합니다. 스레드에서 데이터베이스에 대한 새 데이터를 사용할 수 있는지 확인하고 데이터를 저장합니다. 실행 후 WorkerThreads는 지정된 시간(여기서는 10ms) 동안 기다립니다.
'제어' 포트의 도움으로 다양한 데이터베이스 작업을 트리거할 수 있습니다. 저장해야 할 데이터는 'intArray' 및 'floatArray' 포트로 제공됩니다.
간단한 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;
마지막으로 포트를 연결해야 합니다.
이제 프로젝트를 컴파일하여 연결된 PLC로 보낼 수 있습니다. '라이브 모드'에서는 'iControl' 변수에 다른 값을 할당하여 데이터베이스와 상호 작용할 수 있습니다.
컨트롤러 디렉토리 /opt/plcnext에 데이터베이스 'database.db'가 생성됩니다. WinSCP와 같은 도구로 액세스할 수 있습니다. DB Browser(SQLite) 도구를 사용하여 데이터베이스 내용을 확인할 수 있습니다.
SQlite 프라그마 문
산업기술
이 자습서에서는 다국어를 처리하는 방법을 보여줍니다. 2021.0 LTS부터 지원되므로 PLCnext Engineer의 기능 . 다음 예에서는 영어를 기본 언어로 사용하고 스웨덴어를 대체 언어로 사용합니다. 하드웨어:AXC F 2152 PLCnext 컨트롤러(FW 2021.0 LTS부터) 소프트웨어:PLCnext Engineer 2021.0 LTS 프로젝트:Starterkit 데모 프로젝트(2021.0 LTS에 프로젝트 채택 필요) 1단계. 프로젝트에서 다국어 활성화 옵션을 선택합니다. 국제 폴더를 클릭하고 기본 언어 설
easymon은 스마트폰 세계의 사용 편의성을 머신 컨트롤러 영역으로 가져오는 원격 모니터링 솔루션입니다. PLCnext 스토어에서 PLCnext 기능 확장을 설치하고 iOS 또는 Google Play 스토어에서 해당 스마트폰 앱을 설치하기만 하면 됩니다. 빠르고 쉬운 구성 후에 선택한 데이터 포인트 업데이트가 페어링된 스마트폰 앱 인스턴스에 전파됩니다. 현재 실행 중인 PLCnext 프로그램의 각 변수는 모니터링되는 데이터 포인트로 구성할 수 있습니다. 1. 디자인에 의한 개인정보 보호 사용 편의성 외에도 easymon을 개발하