Пример реализации подключен к Examples.sln как mplc_protocol_with_params:

Для разработки своих собственных протоколов нужно открыть пример и изменить те части проекта, которые отвечают за логику работы. Вспомогательные файлы, служащие для взаимодействия с исполнительной системой, редактировать не требуется.
Порядок разработки
test_protocol_params.h
Общая структура и регистрация класса протокола не отличается от той, что использовалась в примере протокола с модулями. Однако в данном случае дополнительно в класс протокола необходимо добавить два поля типа mplc::api::ScadaParameter и перегрузить отдельный метод Create именно для параметров.
Для этого в примере был создан заголовочный файл test_protocol_params.h:

Содержимое файла:
|
|
|

|
#pragma once
#include <mplc/api.h>
#include "test_module_params.h"
#include "emulator.h"
class ProtocolWithParameters final : public mplc::api::ScadaProtocol {
ModuleWithParameters* param_module;
mplc::api::ScadaParameter InputParameter;
mplc::api::ScadaParameter OutputParameter;
Emulator* emulator;
bool connected{false};
int input_param_value {0};
bool Connect();
public:
MPLC_OBJECT(ProtocolWithParameters);
ProtocolWithParameters();
~ProtocolWithParameters() override;
void Init() override;
void Execute() override;
mplc::api::ScadaModule* Create(const mplc::vm::IOModule* module) override;
mplc::api::ScadaParameter* Create(const mplc::vm::Variable* var) override;
};
|
test_protocol_params.cpp
Создание параметров отличается от создания каналов и модулей тем, что в данном случае необходимо определять параметр по названию, что задается в редакторе. Работа с самими параметрами не сильно отличается от работы с каналами. Тут так же при чтении желательно проверять, изменилось ли значение, и только после этого считывать его. Для записи значения нужно сначала проверить валидность параметра, после чего записать в него не само значение на прямую, а предварительно поместив его в OpcUa_VariantHlp. В остальном отличий от обычного протокола нет.
Для этого в примере был создан файл test_protocol_params.cpp:

Содержимое файла:
|
|
|


|
#include "test_protocol_params.h"
ProtocolWithParameters::ProtocolWithParameters() {
emulator = new Emulator();
}
ProtocolWithParameters::~ProtocolWithParameters() {
emulator->close();
delete emulator;
}
bool ProtocolWithParameters::Connect() {
if (connected) {
return true;
}
if (!emulator->connect()) {
return false;
}
connected = true;
SetFaultState(false, "");
return true;
}
void ProtocolWithParameters::Init() {
emulator->init();
}
void ProtocolWithParameters::Execute() {
if (!isConnect()) {
return;
}
if (!Connect()) {
SetFaultState(true, "Failed to connect");
return;
}
if (!isExecute()) {
return;
}
std::string buff;
// Чтение значения из параметра
std::string test_str;
if (InputParameter.Changed()) {
input_param_value = InputParameter.Get<int>();
}
int result = param_module->Execute(emulator, input_param_value);
// Запись значения в параметр
if (OutputParameter.Valid()) {
OpcUa_VariantHlp value;
value.SetInt(result);
OutputParameter.Update(value);
}
AcknowledgeAllChanges();
}
mplc::api::ScadaModule* ProtocolWithParameters::Create(const mplc::vm::IOModule* module) {
param_module = new ModuleWithParameters();
mplc::api::ScadaModule& req = *param_module;
return &req;
}
mplc::api::ScadaParameter* ProtocolWithParameters::Create(const mplc::vm::Variable* var) {
if (var->name.translited() == "input_parameter") {
InputParameter.Init(var);
return &InputParameter;
}
if (var->name.translited() == "output_parameter") {
OutputParameter.Init(var);
return &OutputParameter;
}
return nullptr;
}
MPLC_PROTOCOL_TYPE(ProtocolWithParameters, ProtocolWithParameters);
|
test_module_params.h
Теперь рассмотрим реализацию модуля. Как и в случае протокола отличий от примера с модулями почти нет. Здесь так же нужно добавить два поля типа mplc::api::ScadaParameter и перегрузить метод Create для параметров. Так же стоит отметить, что метод создания каналов для модулей обязателен к перегрузке, но в этом примере работы с каналами нет, поэтому просто добавим реализацию, что возвращает nullptr.
Для этого в примере был создан файл test_module_params.h:

Содержимое файла:
|
|
|

|
#pragma once
#include <mplc/api.h>
#include "emulator.h"
class ModuleWithParameters : public mplc::api::ScadaModule {
mplc::api::ScadaParameter InputStruct;
mplc::api::ScadaParameter OutputStruct;
public:
ModuleWithParameters();
~ModuleWithParameters() override;
mplc::api::ScadaParameter* Create(const mplc::vm::Variable* var, LuaDataProvider* provider) override;
mplc::api::ScadaChannel* Create(const mplc::vm::Channel* channel, LuaDataProvider* provider) override;
int Execute(Emulator* emulator, int protocol_value);
};
|
test_module_params.cpp
Как и в случае протокола, создание параметров в модуле выполняется поименно. Однако, при чтении из параметра структуры нужно обратиться напрямую к хранимому значению OpcUa_VariantHlp Value, у которого потребуется вызвать метод GetJson(), что вернет OpcUa_Json. Его нужно будет разбирать отдельно при помощи набора функций GetSafe...Value. При записи в структуры, необходимо не просто поместить значение в OpcUa_VariantHlp, а предварительно поместить его в rapidjson::Value. При этом наименования ключей в JSON должны полностью совпадать с названиями полей в редакторе.
Для этого в примере был создан файл test_module_params.cpp:

Содержимое файла:
|
|
|

|
#include "test_module_params.h"
ModuleWithParameters::ModuleWithParameters() {}
ModuleWithParameters::~ModuleWithParameters() {}
mplc::api::ScadaChannel* ModuleWithParameters::Create(const mplc::vm::Channel* channel, LuaDataProvider* provider) {
return nullptr;
}
mplc::api::ScadaParameter* ModuleWithParameters::Create(const mplc::vm::Variable* var, LuaDataProvider* provider) {
if (var->name.translited() == "input_struct") {
InputStruct.Init(var);
return &InputStruct;
}
if (var->name.translited() == "output_struct") {
OutputStruct.Init(var);
return &OutputStruct;
}
return nullptr;
}
int ModuleWithParameters::Execute(Emulator* emulator, int protocol_value) {
std::string buff;
auto& json_input = InputStruct.Value.GetJson();
std::string str_string = GetSafeStringValue(json_input.get(), "text_data", "");
int dint_field = GetSafeInt64Value(json_input.get(), "dint_data", 0);
emulator->write(str_string, protocol_value, dint_field);
buff = emulator->read();
if (OutputStruct.Valid()) {
mplc::OpcUa_Json json;
rapidjson::Value val(rapidjson::kObjectType);
val.AddMember("text_data", buff, json.GetAllocator());
val.AddMember("dint_data", buff.size(), json.GetAllocator());
OpcUa_VariantHlp value;
value.SetJson(val);
OutputStruct.Update(value);
}
return buff.size();
}
|
Далее можно компилировать проект.
Компиляция проекта
ОС Windows
Для компиляции проекта на ОС Windows нужно выполнить команду Build в контекстном меню проекта:

Скомпилированные файлы появятся в папке проекта: C:\mplc\api\platform\x64\bin\Release (адрес действителен, если название папок, которые делал разработчик, совпадают с названиями, которые давались в этой инструкции).
ОС Linux
Чтобы произвести сборку под Linux платформы, нужно сохранить проект и перенести все файлы из папки C:\mplc\api (адрес действителен, если название папок, которые делал разработчик, совпадают с названиями, которые давались в этой инструкции) в созданный каталог на компьютере с ОС Linux. Подробнее смотрите в разделе Сборка под Linux платформы.
После сборки можно приступать к тестированию созданного ФБ.