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

в 21:30, , рубрики: c++, linux, open source, scada, автоматизация, асу тп, Промышленное программирование

В предыдущих частях (часть 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, мы увидим в следующей части…

Автор: PavelVainerman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js