libuniset2 — библиотека для создания АСУ. Лучше один раз увидеть…Часть 3 (Создание процесса управления)

от автора

В предыдущих частях (часть 1 и часть 2) я описал создание проекта и привёл пример создания имитатора… Теперь же реализуем собственно алгоритм управления…

Создание процесса управления

Создание процесса управления ничем не отличается от того, как мы создавали имитатор. Поэтому само создание я опишу быстро, но покажу некоторые другие возможности uniset-codegen, на которых не акцентировал внимание при создании имитатора.
Итак, шаги для создания процесса управления

Создаём файл описания процесса

controller.src.xml

<?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

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; 	} } 

Для полноты вот заголовочный файл

Controller.h

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

Тут всё по аналогии с имитатором, поэтому приведу сразу результат:

controller-main.cc

#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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *