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, мы увидим в следующей части

    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.