Пример использования библиотеки uniwidgets

Материал из База знаний Etersoft
Перейти к навигацииПерейти к поиску

В путях до Projects необходимо указывать имя реального пользователя вместо user

Постановка задачи

Создать алгоритм управления, меняющий по команде значения аналогового и дискретного датчиков. Команды подаются с графического интерфейса (кнопкой). Состояния датчиков выводятся на графический интерфейс. Для аналогового датчика сделать два пороговых - срабатывающих при некотором минимальном и максимальном значениях. Состояния пороговых датчиков также выводятся на графический интерфейс.

Реализация

1. Создание инфраструктуры проекта

1) Для сборки примера необходимы библиотеки uniset и uniwidgets.

В библиотеке uniset собраны основные компоненты из которых строятся распределенные системы управления (базовые интерфейсы для разработки процессов управления, процессов управления вводом/выводом, менеджеров сообщений и т.п.).
Uniwidgets - библиотека графических элементов, работающих с uniset.

Последнюю версию uniset (пакеты libuniset и libuniset-devel) можно установить из репозитория.
Библиотеку uniwidgets лучше собрать вручную, т.к. она в собранном виде понадобится для работы в редакторе графических интерфейсов glade.

Для этого:

1. Склонировать репозиторий uniwidgets:

$git clone git.office:/projects/uniwidgets.git uniwidgets


2. Собрать:

$./autogen.sh
$./configure
$gmake


2) Файлы и скрипты для сборки проекта.

1. Типовые сборочные скрипты можно взять из тестового примера.
Клонируем репозиторий unitest:

$git clone git.eter:/people/shpigor/packages/unitest.git unitest

Создаем в своем Projects новый каталог. Копируем в него из unitest файлы autogen.sh и configure.ac.

2. В каталоге примера создаем файл Makefile.am:

SUBDIRS=src/GUI src/Algorithms/ControlProcess


3. Там же создаем каталог src, в нем каталог GUI.

4. В каталоге GUI создаем файл Makefile.am:

bin_PROGRAMS = test
test_SOURCES = gui.cc
test_LDADD = ${GTKMM_LIBS} ${UNISET_LIBS} ${GLADEMM_LIBS}
test_CXXFLAGS = ${GTKMM_CFLAGS} ${UNISET_CFLAGS} ${GLADEMM_CFLAGS}


5. В каталоге src создаем Algorithms, в нем каталог ControlProcess

6. В каталоге ControlProcess создаем файл Makefile.am:

bin_PROGRAMS = controlprocess
controlprocess_SOURCES  = ControlProcess_SK.cc ControlProcess.cc controlprocess.cc
ControlProcess_SK.cc: controlprocess.src.xml
       @UNISET_CODEGEN@ --ask -n ControlProcess --no-main controlprocess.src.xml
clean-local:
       rm -rf *_SK.cc *_SK.h


В итоге структура каталогов нашего проекта выглядит следующим образом:

conf/
docs/
src/
   /SharedMemory/ 
   /Algorithms/
              /ControlProcess/
   /Services/
            /Administrator/
   /GUI/
       /svg/

2. Создание вспомогательных утилит

1) Скрипты для запуска примера.

Для запуска каждого из компонентов примера создадим отдельный скрипт. Таких компонентов три - графический интерфейс, процесс отвечающий за реализацию логики и shared memory в которой хранится текущее состояние датчиков
Кроме того необходимы служебные скрипты для управления репозиторием omniNames и состоянием датчиков. В репозитории omniNames хранится соответствие между CORBA-объектами и CORBA-ссылками. Он нужен для обмена между uniset-объектами(процессами), взаимодействие между которыми построено на CORBA.

Во всех стартовых скриптах используется uniset-start.sh, позволяющий запускать необходимые uniset-процессы на специальном (уникальным для конкретного пользователя) порту. Что в свою очередь позволяет разным пользователям вести одновременную работу над одним и тем же проектом и запускать одни и те же процессы.

В реальных проектах вместо скрипта uniset-start.sh надо использовать uniset-smemory.

1. Создадим вспомогательные скрипты необходимые для отладки проекта.

Данные скрипты построены на использовании утилиты uniset-admin.
Скрипт admin.sh вызывает uniset-admin и передает в качестве аргумента свое имя. Благодаря этому с помощью одного скрипта через ссылки на него можно вызывать uniset-admin с разными параметрами.

Скрипт admin.sh в каталоге src/Services/Administrator:

#!/bin/sh
START=uniset-start.sh ${START} -f uniset-admin --confile ./configure.xml --`basename $0 .sh` $1 $2 $3 $4

Символьные ссылки на admin.sh в том же каталоге:

$ln -s admin.sh create
$ln -s admin.sh exist
$ln -s admin.sh getState
$ln -s admin.sh getValue
$ln -s admin.sh saveState
$ln -s admin.sh saveValue

create - создаёт дерево репозитория в omniNames
exist - получение информации об объектах зарегистрированных в данный момент в репозитории
getState - получение значения дискретного датчика
getValue - получение значения аналогового датчика
saveState - установка значения дискретного датчика
saveValue - установка значения аналогового датчика

В данном примере нам понадобится только create, все остальные ссылки нужны для отладки.

2. Создаем скрипт для запуска shared memory в которой хранится текущее состояние датчиков в каталоге src/SharedMemory.
Скрипт start_share.sh:

#!/bin/sh
START=uniset-start.sh ${START} -f uniset-smemory --pulsar-id pulsar --smemory-id Smemory1 --confile configure.xml --unideb-add-evels info,warn,crit

Описание параметров uniset-start.sh:
--pulsar-id - id пульсара. Это датчик, который каждые n секунд меняет свое значение
--smemory-id - имя объекта shared memory, указанное в configure.xml
--configure - используемый конфигурационный файл
--unideb-add-levels - ввывод отладочных сообщений библиотеки uniset

3. Создаем скрипт для запуска графического интерфейса в каталоге src/GUI.
Скрипт start_fg.sh:

#!/bin/sh
export LIBGLADE_MODULE_PATH=/srv/user/Projects/uniwidgets/plugins/libglade/.libs START=uniset-start.sh ${START} -f ./test --pulsar-id pulsar --confile configure.xml --unideb-add-levels info,warn,crit,system

В путь LIBGLADE_MODULE_PATH вместо user необходимо подставить имя реального пользователя.
Описание параметров uniset-start.sh см. в пункте 2.

4. Создаем скрипт для запуска процесса отвечающего за реализацию логики в каталоге src/Algorithms/ControlProcess.
Скрипт start_fg.sh:

#!/bin/sh
START=uniset-start.sh
${START} -f ./controlprocess --dlog-add-levels info,warn,crit

Описание параметров uniset-start.sh см. в пункте 2.

3. Создание конфигурационного файла

В конфигурационном файле описаны все датчики и объекты.

1) Файл configure.xml, находится в каталоге conf:

<?xml version = '1.0' encoding = 'utf-8'?>
<ExampleConfiguration id="$Id: configure.xml,v 1.48 2008/06/08 15:38:12 pv Exp $">
   
   <UniSet>
       <LocalNode name="LocalHostNode"/>
       <RootSection name="TEST"/>
       <ServicesSection name="Services"/>
       <NameService host="localhost" port="2809"/> 
       <CountOfNet name="1"/>
       <RepeatCount name="2"/>
       <RepeatTimeoutMS name="10"/>
       <ImagesPath name="images"/>
       <SizeOfMessageQueue name="100000"/>
       
       <WatchDogTime name="0"/> 
       <PingNodeTime name="0"/> 
       <AutoStartUpTime name="1"/> 
       <DumpStateTime name="10"/>
       <SleepTickMS name="500"/>
       <LocalIOR name="0"/> 
       
       <UniSetDebug name="unideb" levels="" file=""/>
       
       <ConfDir name=""/>
       <DataDir name="./"/>
       <BinDir name=""/>
       <LogDir name=""/>
       <DocDir name=""/>
       <LockDir name=""/>
   </UniSet>
   
   <Services/>
   
   <SharedMemory name="SharedMemory" shmID="LSM" time_msec="1000"/>
   <HeartBeatTime name="HeartBeatTime" time_msec="1000"/>
   <IOControl name="IOControl" conf-field="io1" heartbeat_max="10"/>
   
   <settings>
       <ControlProcess
                    overrun_delay_msec="15"
                    Sensor_AI="Test_AI"
                    Threshold_High="ThreshHi"
                    Threshold_Low="ThreshLo"
Sensor_AI_Out="Test_AI" Start_Timer="Start_Timer_DI" /> </settings> <ObjectsMap idfromfile="1"> <nodes port="2809"> <item id="3001" name="LocalHostNode" alias="" textname="Контроллер N1" ip="127.0.0.1" infserver="" dbserver=""/> </nodes> <sensors name="Sensors" section="Sensors" > <item id="101" name="Test_DI" iotype="DI" default="0" textname="Test DI"/> <item id="102" name="ThreshHi" iotype="DI" textname="Threshold high"/> <item id="103" name="ThreshLo" iotype="DI" textname="Threshold low"/> <item id="104" name="Test_AI" iotype="AI" textname="Test AI"/> <item id="107" name="Start_Timer_DI" iotype="DI" default="0" textname="Start Timer"/>
<item id="105" name="TSCPU_Confirm_S" iotype="DI" textname="Квитирование ЦПУ" node="LocalHostNode"/> <item id="106" name="pulsar" textname="xxpulsar" iotype="DI"/> </sensors> <thresholds name="thresholds"></thresholds> <controllers name="Controllers" section="Controllers" > <item id="21605" name="Smemory1" /> </controllers> <services name="Services" section="Services"> </services> <objects name="objects" section="Objects"> <item id="21606" name="MainWindow" evnt_gui1="1"/> <item id="21607" name="ControlProcess" evnt_ts1="1"/> </objects> </ObjectsMap> <messages name="messages" idfromfile="1"> </messages> </ExampleConfiguration>


2) Создание символьных ссылок на конфигурационный файл

Т.к. по умолчанию конфигурационный файл ищется в текущем каталоге, то для удобства (чтобы не "загромождать" скрипт запуска лишними параметрами) сделаем в каталогах компонентов примера ссылки на конфигурационный файл.

В src/Services/Administrator

$ln -s ../../../conf/configure.xml

В src/SharedMemory

$ln -s ../../conf/configure.xml

В src/GUI

$ln -s ../../conf/configure.xml

В src/Algorithms/ControlProcess

$ln -s ../../../conf/configure.xml

4. Создание графического интерфейса

Все файлы, описываемые ниже, относятся к графическому интерфейсу и должны лежать в каталоге src/GUI.

1) Графический интерфейс.

Все объекты графического интерфейса и их параметры описываются в отдельном файле. Для создания и редактирования этого файла воспользуемся редактором графических интерфейсов glade.
Картинки для виджетов должны лежать в каталоге src/GUI/svg. Их можно взять из репозитория готового примера unitest.

1. Для запуска редактора glade:

$export PROJECTS=/srv/user/Projects
$GLADE_CATALOG_PATH=$PROJECTS/uniwidgets/plugins/glade
$export GLADE_CATALOG_PATH
$GLADE_MODULE_PATH=$PROJECTS/uniwidgets/plugins/glade/.libs
$export GLADE_MODULE_PATH
$glade-3

PROJECTS - каталог, где находятся исходный код uniwidgets и собираемого примера.

2. В открывшемся окне glade создаем новое окно (GtkWindow)
Окно GtkWindow является контейнером для всех остальных графических объектов.

3. В окно добавляем объект UProxyWidget
Он создает UniSetObject, через который все виджеты получают информацию

4. В UProxyWidget добавляем таблицу GtkTable
Этот объект нужен для позиционирования остальных объектов.

5. В ячейки таблицы добавляем:
USVGIndicatorplus - для отображения состояния дискретного датчика
GtkFixed, а в него USVGBar2 - для отображения состояния аналогового датчика
UButton - для изменения состояния дискретного датчика
UButton - для запуска таймера, по которому будет изменяться состояние аналогового датчика

В объект GtkFixed, поверх USVGBar2 добавляем:
USVGIndicatorplus - для отображения состояния порогового датчика на максимум
USVGIndicatorplus - для отображения состояния порогового датчика на минимум

6. Настраиваем параметры параметры графических объектов

UProxyWidget

parameters = MainWindow;106;105

MainWindow - объект определенный в configure.xml
106 - id пульсара
105 - id датчика квитирования

USVGIndicatorplus (состояние дискретного датчика)

di-sensor-id = 101
imgae0-path = <путь до картинки датчик не активен>
imgae1-path = <путь до картинки датчик активен>

di-sensor-id - id дискретного датчика

USVGBar2 (состояние аналогового датчика)

ai-sensor-id = 104
linfile-svg-file = <картинка шкалы>
topfile-svg-file = <картинка заливки>
backfile=svg-file = <картинка фона>

ai-sensor-id - id аналогового датчика

UButton (кнопка для изменения дискретного датчика)

dout-sensor-id = 101

dout-sensor-id - id дискретного датчика, изменяющего состояние при нажатии на кнопку

UButton (кнопка для запуска таймера по которому изменяется аналоговый датчик)

dout-sensor-id = 107

dout-sensor-id - см. выше

USVGIndicatorplus (состояние порогового датчика на максимум)

di-sensor-id = 102
imgae0-path = <путь до картинки датчик не активен>
imgae1-path = <путь до картинки датчик активен>

di-sensor-id - id порогового датчика

USVGIndicatorplus (состояние порогового датчика на минимум)

di-sensor-id = 103
imgae0-path = <путь до картинки датчик не активен>
imgae1-path = <путь до картинки датчик активен>


7. Сохраняем файл как test.glade

2) Программа для запуска графического интерфейса.

Программа связывает объекты графического интерфейса, описанные в glade файле с реальными датчиками, информация о состоянии которых хранится в shared memory.

Файл gui.cc:

#include <iostream>
#include <gtkmm.h>
#include <libglademm.h>
#include <Configuration.h>
using namespace UniSetTypes; using namespace std;
int main (int argc, char **argv) { try { Gtk::Main Kit(argc, argv); uniset_init(argc,argv); Glib::RefPtr<Gnome::Glade::Xml> gxml = Gnome::Glade::Xml::create("test.glade"); Gtk::Window *w; gxml->get_widget("window1", w); if (w == NULL) { cout << "error with the window widget getting" << endl; return 1; } Kit.run(*w); } catch(SystemError& err) { cout << err << endl; } catch(Exception& ex) { cout << ex << endl; } catch(...) { cout << "catch(...)" << endl; } return 0; }

5. Создание процесса отвечающего за реализацию логики управления

Все файлы, описываемые ниже, относятся к реализации процесса управления и должны лежать в каталоге src/Algorithms/ControlProcess.

1) Конфигурационный файл для генерирования скелета класса реализующего процесс управления.

Файл controlprocess.src.xml:

 <?xml version="1.0" encoding="utf-8"?>
 <ControlProcess>
  <settings>
  	<set name="class-name" val="ControlProcess"/>
  	<set name="msg-count" val="30"/>
	<set name="sleep-msec" val="150"/>
  </settings>
  <smap> 
    <item name="Sensor_AI" vartype="in" iotype="AI" comment="Аналоговый датчик"/>
    <item name="Threshold_High" vartype="out" iotype="DI" comment="Порог на максимальное значение"/>
    <item name="Threshold_Low" vartype="out" iotype="DI" comment="Порог на минимальное значение"/>
    <item name="Start_Timer" vartype="in" iotype="DI" comment="Запуск таймера по которому изменяется значение а
    <item name="Sensor_AI_Out" vartype="io" iotype="AI" comment="Аналоговый датчик"/>
  </smap>
  <msgmap></msgmap>	
 </ControlProcess>
 

В блоке <settings> указываются параметры.
class-name - имя класса реализующего логику управления
msg-count - сколько сообщений обрабатывается за один раз
sleep-msec - пауза между итерациями в работе процесса

В блоке <smap> перечислены входные и выходные переменные класса, которые через конфигурационный файл configure.xml будут привязаны к конкретным датчикам при запуске процесса.

2) Запускающая функция

Файл controlprocess.cc

 #include <sstream>
 #include <ObjectsActivator.h>
 #include "ControlProcess.h"
 // -----------------------------------------------------------------------------
 using namespace UniSetTypes;
 using namespace std;
 // -----------------------------------------------------------------------------
 int main( int argc, char **argv )
 {
	try
	{
		string confile = UniSetTypes::getArgParam( "--confile", argc, argv, "configure.xml" );
		conf = new Configuration(argc, argv, confile);

		xmlNode* cp_cnode = conf->getNode("ControlProcess");
		if( cp_cnode == NULL )
		{
			cout << "(controlprocess): not found <ControlProcess> in conffile" << endl;
			return 1;
		}

		UniSetTypes::ObjectId cp_id = conf->getObjectID("ControlProcess");
		if( cp_id == UniSetTypes::DefaultObjectId )
		{
			cout << "(controlprocess): Not found ID for ControlProcess in section=" << conf->getObjectsSection() << endl;
			return 1;
		}

		ControlProcess cp1(cp_id,cp_cnode);

		ObjectsActivator act;
		act.addObject( static_cast<UniSetObject*>(&cp1) );

		SystemMessage sm(SystemMessage::StartUp); 
		act.broadcast( sm.transport_msg() );

		cout << "\n\n\n";
		cout << "(main): -------------- ControlProcess START -------------------------\n\n";
		act.run(false);
	}
	catch(SystemError& err)
	{
		cout << "(controlprocess): " << err << endl;
	}
	catch(Exception& ex)
	{
		cout << "(controlprocess): " << ex << endl;
	}
	catch(...)
	{
		cout << "(controlprocess): catch(...)" << endl;
	}

	return 0;
 }
 // -----------------------------------------------------------------------------
 


3) Класс ControlProcess

В классе ControlProcess реализуется логика выставления пороговых датчиков по значению аналогового. В основном вся логика управления описывается в функции sensorInfo(), которая вызывается при любом изменении состояния датчиков, с которыми ведётся работа. Если необходимо постоянно (а не по изменению) производить какую-то работу, то эта логика может быть описана в функции step(). Помимо этого имеется возможность "заказывать" таймеры, для выполнения периодических действий. Для этого предназначена функция timerInfo().

В sensorInfo() по вызову askTimer() (параметры см. в doxygen документации на Uniset) "заказываем" таймер на N секунд. Через N секунд таймер срабатывает и вызывается функция timerInfo(). В данном проекте, при каждом срабатывании таймера увеличивается значение аналогового датчика на inc единиц. Когда значение аналогового датчика становится максимальным, оно начинает уменьшаться до минимального значения, затем опять увеличиваться и т.д.

Файл ControlProcess.cc:

 #include "ControlProcess.h"
 // ------------------------------------------------------------------------------------------
 using namespace std;
 using namespace UniSetTypes;
 // ------------------------------------------------------------------------------------------

 ControlProcess::ControlProcess(UniSetTypes::ObjectId id, xmlNode* cnode):
	ControlProcess_SK(id, cnode),
	delay_msec(conf->getIntProp(cnode, "overrun_delay_msec"))
 {	
	inc = 2;
	
        if( cnode == NULL )
		throw Exception( myname + ": FAILED! not found confnode in confile!" );

	if( delay_msec < 0 )
	    delay_msec = 0;
 }
 // ------------------------------------------------------------------------------------------
 ControlProcess::~ControlProcess()
 {
 }
 // ------------------------------------------------------------------------------------------
 ControlProcess::ControlProcess():
	ControlProcess_SK()
 {
	cout << myname << ": init failed!!!!!!!!!!!!!!!"<< endl;
	throw Exception();
 }
 // ------------------------------------------------------------------------------------------
 void ControlProcess::sensorInfo(SensorMessage *sm)
 {
       if (sm->id == Start_Timer)
       {
               if (in_Start_Timer)
                       askTimer(tmDelay, delay_msec, -1);
               else
                       askTimer(tmDelay, 0, -1);
        }

	if (sm->id == Sensor_AI)
	{
		out_Threshold_High = (in_Sensor_AI > 90);
		out_Threshold_Low = (in_Sensor_AI < 10);
	}
 }
 // ------------------------------------------------------------------------------------------
void ControlProcess::timerInfo( TimerMessage *tm )
{
       if( tm->id == tmDelay )
       {
               io_Sensor_AI_Out += inc;

               if (io_Sensor_AI_Out > 100)
               {
                       io_Sensor_AI_Out = 100;
                       inc = -2;
               }
               else if (io_Sensor_AI_Out < 0)
               {
                       io_Sensor_AI_Out = 0;
                       inc = 2;
               }
       }
}
// ------------------------------------------------------------------------------------------
 

Файл ControlProcess.h

 #ifndef ControlProcess_H_
 #define ControlProcess_H_
 // ------------------------------------------------------------------------------------------
 #include <UniXML.h>
 #include "ControlProcess_SK.h"
 // ------------------------------------------------------------------------------------------

 class ControlProcess:
	public ControlProcess_SK
 {
	public:

		ControlProcess(UniSetTypes::ObjectId id, xmlNode* node = 
							UniSetTypes::conf->getNode("ControlProcess"));
		virtual ~ControlProcess();

                enum Timers
                {
                        tmDelay,
                };
		
	protected:
		ControlProcess();
		virtual void step(){};
		
		void sensorInfo( UniSetTypes::SensorMessage *sm );

                void timerInfo( UniSetTypes::TimerMessage *tm );

		int delay_msec;
                int inc;		 
	private:

 };
 // ------------------------------------------------------------------------------------------
 #endif // ControlProcess_H_
 

6. Сборка и запуск примера

1) Сборка проекта

$export UWPATH=/srv/user/Projects/uniwidgets
$export UNIWIDGETS_CFLAGS="-I$UWPATH"
$export UNIWIDGETS_LIBS="-L$UWPATH/lib -luniwidgets -L$UWPATH/plugins/libglade -lunigui"
$automake
$./autogen.sh
$gmake


2) Запуск примера

1. Создание репозитория omniNames.

Назначение репозитория omniNames см. в разделе "Создание вспомогательных утилит".

Для создания репозитория, необходимо один раз запустить скрипт create из src/Services/Administrator:

$./create

Проверяется командой: $./exist На экране должно быть

||=======********  TEST/Services  ********=========|| 

пусто!!!!!!

||=======********  TEST/Controllers  ********=========||

пусто!!!!!!

||=======********  TEST/Objects  ********=========||

пусто!!!!!!

2. Запускаем shared memory из src/SharedMemory:

$./start_share.sh

3. В другой консоли запускаем логику из src/Algorithms/ControlProcess:

$./start_fg.sh

4. В другой консоли запускаем графику из src/GUI:

$./start_fg.sh

Ссылки

http://wiki.office.etersoft.ru/mb745/Kompilirovanie?v=cgs - сборка проектов с локальными uniset и uniwidgets
http://wiki.office.etersoft.ru/mb745/Repozitorii?v=1ctr - репозитории проекта МБ745
http://fay.nm.ru/pmo/doc/use_doxygen.htm - комментирование исходного текста в формате Doxygen
http://bugs.etersoft.ru/show_bug.cgi?id=4894 - бага на написание тестового примера