В предыдущих частях (часть 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, мы увидим в следующей части…
ссылка на оригинал статьи https://habrahabr.ru/post/278565/
Добавить комментарий