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