В предыдущих частях (часть 1 и часть 2) я описал создание проекта и привёл пример создания имитатора… Теперь же реализуем собственно алгоритм управления…
Создание процесса управления
Создание процесса управления ничем не отличается от того, как мы создавали имитатор. Поэтому само создание я опишу быстро, но покажу некоторые другие возможности uniset-codegen, на которых не акцентировал внимание при создании имитатора.
Итак, шаги для создания процесса управления
Создаём файл описания процесса
<?xml version="1.0" encoding="utf-8"?>
<!--
name - название класса
msgcount - сколько сообщений обрабатывается за один раз
sleep_msec - пауза между итерациями в работе процесса
type
====
in - входы (только для чтения)
out - выходы (запись)
-->
<Controller>
<settings>
<set name="class-name" val="Controller"/>
<set name="msg-count" val="30"/>
<set name="sleep-msec" val="150"/>
</settings>
<variables>
<item name="HiLevel" type="long" const="1" default="95" max="100" comment="Верхний порог срабатывания"/>
<item name="LowLevel" type="long" const="1" default="5" min=0" comment="Нижний порог срабатывания"/>
</variables>
<smap>
<item name="OnControl_s" vartype="in" iotype="DI" comment="Разрешение на работу"/>
<item name="Level_s" vartype="in" iotype="AI" comment="Текущий уровень в цистерне"/>
<item name="cmdLoad_c" vartype="out" iotype="DO" comment="Включить насос 'закачивающий'"/>
<item name="cmdUnload_c" vartype="out" iotype="DO" comment="Включить насос 'откачивающий'"/>
</smap>
<msgmap>
</msgmap>
</Controller>
Тут опять же всё просто. Для нашего процесса управления есть команда на включение OnControl_s(вход), есть текущий уровень в цистерне Level_s(вход) и есть два выхода: для управления cmdLoad_c — наполнение, cmdUnload_c — опустошение.
Из интересного (новенького) тут обращаю внимание на секцию variables. В ней описаны две константы,
определяющие минимальный и максимальный пороги, при достижении которых процесс меняет команды. Само по себе наличие констант не интересно, но здесь показана одна из возможностей, предоставляемых утилитой uniset2-codegen, позволяющая часть «полей класса» определять ещё в src.xml файле.
Зачем это нужно?
Понятно что любой нормальный процесс управления должен иметь возможность конфигурироваться, т.е. нужна возможность без перекомпиляции менять какие-то параметры процесса. Всякие пороги срабатывания, timeout-ы и т.п. — лучшие кандидаты для этого. Есть три уровня для конфигурирования подобных параметров (в порядке повышения приоритета):
- значение по умолчанию
- значение, определяемое в настроечной секции процесса (configure.xml)
- значение, заданное в командной строке
Так вот при объявлении параметра в src.xml файле для него будет автоматически сгенерирован код, как раз реализующий эту логику. Сначала переменная инициализируется значением по умолчанию, указанным в поле default="..". Если необходимо его переопределить, то в настроечной секции для данного процесса (в файле проекта configure.xml) можно переопределить это значение
<settings>
...
<Controller name="Controller1" HiLevel="90"/>
</settings>
А если нужно переопределить параметр прямо в скрипте запуска (например для тестирования), то можно задать его аргументом командной строки --ObjectName-variable val. В нашем случае это будет так:
--Controller1-HiLoad 93
Всю эту рутину по созданию последовательности и приоритета инициализации берёт на себя uniset-codegen.
Помимо этого если внимательно присмотреться к controller.src.xml можно увидеть ещё два параметра min и max. Если для переменной задаются такие поля (или одно из них), то будет сгенерирован код, проверяющий (в конструкторе объекта), что заданное значение входит в указанный диапазон: >=min и <=max. Если не входит, будет выкинуто исключение (хотя и эту политику можно настроить, запретив исключение и останется только warning в логи).
Все возможности утилиты uniset-codegen описаны в её документации…
Генерируем скелет для процесса
uniset2-codegen -n Controller --ask --no-main controller.src.xml
А точнее вставляем эту команду в Makefile.am
bin_PROGRAMS = controller
BUILT_SOURCES = Controller_SK.h Controller_SK.h
controller_LDADD = $(top_builddir)/lib/libUniSetExample.la
#controller_CPPFLAGS =
controller_SOURCES = Controller_SK.cc Controller.cc controller-main.cc
Controller_SK.h Controller_SK.cc: controller.src.xml
@UNISET_CODEGEN@ -n Controller --topdir $(top_builddir)/ --ask --no-main controller.src.xml
clean-local:
rm -rf *_SK.cc *_SK.h *.log
Конфигурируем процесс управления (привязки)
Конфигурирование это два шага:
- Задать объекту уникальный идентификатор
- Создать настроечную секцию и привязать датчики
Задать идентификатор
Для этого просто пропишем в секции objects следующую строку (id — любой, но главное уникальный)
<objects name="Objects" section="Objects">
...
<item id="20002" name="Controller1"/>
</objects>
Создать настроечную секцию и привязать датчики
Как вы помните, конфигурирование можно делать либо вручную, либо при помощи утилиты uniset-linkeditor (запуская скрипт edit_controller.sh в каталоге src/Algorithms/Controller), в итоге в файле проекта configure.xml должна появиться настроечная секция:
<settings>
<Controller name="Controller1" HiLevel="90" OnControl_s="OnControl_S" Level_s="Level_AS" cmdLoad_c="CmdLoad_C" cmdUnload_c="CmdUnload_C"/>
</settings>
Пишем реализацию
Тут нам понадобится реализовать обработку сообщений от датчиков, т.е. определить только одну функцию virtual void sensorInfo( const UniSetTypes::SensorMessage* sm ) override;
void Controller::sensorInfo(const UniSetTypes::SensorMessage* sm)
{
if( sm->id == OnControl_s )
{
if( sm->value )
{
myinfo << myname << "(sensorInfo): Команда начать работу.." << endl;
if( in_Level_s > LowLevel && in_Level_s < HiLevel )
{
// по умолчанию "наполняем"
out_cmdLoad_c = true;
out_cmdUnload_c = false;
}
else
processing();
}
else
{
myinfo << myname << "(sensorInfo): Команда завершить работу.." << endl;
// сбрасываем все команды управления
out_cmdLoad_c = false;
out_cmdUnload_c = false;
}
}
else if( sm->id == Level_s )
{
// если управление включено, то обрабатываем
if( in_OnControl_s )
processing();
}
}
// -----------------------------------------------------------------------------
void Controller::processing()
{
if( in_Level_s >= HiLevel )
{
myinfo << myname << "(sensorInfo): Достигнут верхний уровень(" << HiLevel << "). Начинаем опустошать.." << endl;
// начинаем "опустошать"
out_cmdLoad_c = false;
out_cmdUnload_c = true;
}
else if( in_Level_s <= LowLevel )
{
myinfo << myname << "(sensorInfo): Достигнут нижний уровень(" << LowLevel << "). Начинаем наполнять.." << endl;
// начинаем "наполнять"
out_cmdLoad_c = true;
out_cmdUnload_c = false;
}
}
Для полноты вот заголовочный файл
#ifndef Controller_H_
#define Controller_H_
// -----------------------------------------------------------------------------
#include <string>
#include "Controller_SK.h"
// -----------------------------------------------------------------------------
/*!
page_Controller Процесс наполнения цистерны
- ref sec_controller_Common
section sec_controller_Common Описание алгоритма наполнения цистерны
Процесс запускается по команде OnControl_s=1 и работает, пока не наполнит цистерну, до уровня
задаваемого в настройках параметром HiLevel. После этого начинает «опустошать» цистерну до уровня,
задаваемого в настройках параметров LowLevel. И так по кругу. Если приходит команда OnControl_s=0,
процесс управления останавливается.
*/
class Controller:
public Controller_SK
{
public:
Controller( UniSetTypes::ObjectId id, xmlNode* cnode, const std::string& prefix = "" );
virtual ~Controller();
protected:
virtual void sensorInfo( const UniSetTypes::SensorMessage* sm ) override;
virtual std::string getMonitInfo() override;
// обработка (реализация логики)
void processing();
private:
};
// -----------------------------------------------------------------------------
#endif // Controller_H_
Создаём main()
Тут всё по аналогии с имитатором, поэтому приведу сразу результат:
#include <UniSetActivator.h>
#include "UniSetExampleConfiguration.h"
#include "Controller.h"
// -----------------------------------------------------------------------------
using namespace UniSetTypes;
using namespace std;
// -----------------------------------------------------------------------------
int main( int argc, const char** argv )
{
try
{
auto conf = uniset_init(argc, argv);
auto act = UniSetActivator::Instance();
auto cn = UniSetExample::make_object<Controller>("Controller1", "Controller");
act->add(cn);
SystemMessage sm(SystemMessage::StartUp);
act->broadcast( sm.transport_msg() );
act->run(false);
return 0;
}
catch( const Exception& ex )
{
cerr << "(controller): " << ex << endl;
}
catch( const std::exception& ex )
{
cerr << "(controller): " << ex.what() << endl;
}
catch(...)
{
cerr << "(controller): catch(...)" << endl;
}
return 1;
}
// -----------------------------------------------------------------------------
Пробный запуск
Если всё скомпилировалось, то заходим в каталог src/Algorithms/Controller и запускаем ./start_fg.sh. На экране, если всё хорошо, видим примерно такое:
[pv@pvbook Controller]$ ./start_fg.sh
04/03/2016 15:39:19( info): Controller1(waitSM): waiting SM ready 60000 msec testID=100
04/03/2016 15:39:19( info): Controller1(sensorInfo): Команда завершить работу..
Важно: Не забудьте перед этим запустить SharedMemory (в каталоге src/SharedMemory запустить ./start_fg.sh).
Проверим что процесс виден другим. Для этого заходим в src/Services/Administrator и запускам ./exist. На экране должны увидеть:
[pv@pvbook Administrator]$ ./exist
||=======******** UNISET-EXAMPLE/Services ********=========||
пусто!!!!!!
||=======******** UNISET-EXAMPLE/Controllers ********=========||
(22000 )SharedMemory1 <--- exist ok
||=======******** UNISET-EXAMPLE/Objects ********=========||
(20002 )Controller1 <--- exist ok
Ну вот и всё с созданием процесса управления… дальше наладка.
Небольшой итог
Процесс управления (или имитатор) создать не сложно. Всего несколько шагов:
- Создать xml-файл с описанием входов/выходов для процесса
- В файле проекта (configure.xml) внести идентификатор объекта и настроечную секцию
- Сгенерировать скелет процесса
- Реализовать необходимую логику
Как налаживать работу и какие есть для этого инструменты в uniset, мы увидим в следующей части…
Автор: PavelVainerman