Пример использования библиотеки uniwidgets
В путях до 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 - бага на написание тестового примера