|
UniSet
2.2.1
|
Утилита uniset-codegen предназначена для генерирования "скелета" процесса управления на основе заданного xml-описания. В скелет процесса включается "рутинный" код по получению и проверке индетификаторов, переменных, обработке сообщений и ряд других вспомогательных функций. Помимо этого определяются базовые виртуальные функции, участвующие в процессе.
Процесс можно сгенерировать по нескольким шаблонам
Генерируемый процесс функционирует по следующему обобщённому алгоритму:
Помимо этого обрабатывается специальный режим: Специальный режим "тест" (TestMode)
Сам сгенерированный код представляет из себя класс ("_SK" - skeleton), который необходимо использовать как базовый класс для своего процесса. Переопределяя виртуальные функции, и реализуя в них необходимую функциональность.
Исходный xml-файл описания необходимый для генерирования выглядит следующим образом.
<?xml version="1.0" encoding="utf-8"?> <!-- name - название класса msgcount - сколько сообщений обрабатывается за один раз sleep_msec - пауза между итерациями в работе процесса type ==== in - входные регистры (только для чтения) out - выходные регистры (запись) --> <Test> <settings> <set name="class-name" val="TestGen"/> <set name="msg-count" val="20"/> <set name="sleep-msec" val="150"/> </settings> <variables> <!-- type = [int,str,bool,float] int: max,min,no_range_exception=[0,1] str: float: max,min,no_range_exception=[0,1] bool: min - минимальное значение (может быть не задано) max - максимальное значение (может быть не задано) default - значение по умолчанию (может быть не задано) no_range_exception=1 - при выходе за границы min или max только писать unideb[WARN]. --> <item name="startTimeout" type="int" min="0" comment="test int variable"/> <item name="stopTimeout" type="int" max="100" default="110" no_range_exception="1"/> <item name="test_float" type="float" max="100.0" default="50.0" public="1" const="1" /> <item name="test_bool" type="bool" /> <item name="test_str" type="str" default="ddd"/> </variables> <smap> <!-- name - название переменной в конф. файле --> <item name="input1_s" vartype="in" iotype="DI" comment="comment for input1"/> <item name="input2_s" vartype="in" iotype="DI" comment="comment for input2" /> <item name="output1_c" vartype="out" iotype="DO" omment="comment for output1" no_check_id="1"/> <item name="output2_c" vartype="out" iotype="DO" omment="comment for output2" force="1"/> </smap> <msgmap> <item name="msg1" comment="comment for Message 1" /> </msgmap> </Test>
В секции <settings> описываются следующие параметры:
В секции <smap> описываются "входы" и "выходы" процесса связанные с датчиками. При генерировании процесса, для каждого входа или выхода генерируется ряд свойств:
В секции <msgmap> описываются поля связанные с идентификаторами сообщений. По сути, сообщения это тоже датчики, только используемые специальным образом. Для посылки сообщения датчик выставляется в "1" и через некоторое время должен быть сброшен. (чтобы можно было опять послать тоже самое сообщение). Т.е. само событие "сообщение" это переход датчика "0" --> "1".
<settings>
...
<set name="resetMsgTime" val="500"/>
</settings>
Для работы с сообщениями существует ряд правил:
В данной секции можно перечислить переменные разных типов, для которых будет сгенерирован код по их "инициализации" и проверке "диапазона"(если указаны поля min или max). На данный момент поддерживаются переменные следующих типов:
Так же доступны следующие необязательные вспомогательные поля:
В генерируемом коде для каждой переменной происходит её инициализация по следующему шаблону (псевдокод):
varname = conf->getArgParam("--'arg_prefix'varname'",it.getProp("'varname'")); if( varname.empty() ) varname = 'default'
Где it.getProp() - получение значения из соответствующей настроечной секции в конфигурационном файле (см. Конфигурирование). Из кода можно видеть, что приоритетным является аргумент командной строки, потом значение из конф. файла и только потом default.
Если указаны поля min или max происходит проверка значения (после инициализации) на соответствие указанному диапазону. По умолчанию, при выходе за диапазон, генериурется исключение. Но если указано no_range_exception="1", то просто выдаётся warning в unideb[Debug::WARN].
По умолчанию эти поля генерируются как protected. Но если есть необходимость, то можно указать свойство public="1" или private="1" и тогда они будут иметь соответствующую область видимости.
Для возможности переопределять различные парамеры при помощи аргументов командной строки, сделан следующий механизм. В сгенерированном конструкторе (файл xx_SK.h) можно задать последний аргумент argprefix. Если он равен "", то в качестве префикса используется ObjectName. Все переменные из секции <variables> можно переопределять аргументом --prefix-varname val. В общем случае это будет: --ObjectName-varname val.
Помимо этого argprefix можно задать в секции <settings>
<settings>
...
<set name="arg-prefix" val="test-"/>
...
</settings>
Тогда вместо ObjectName будет использоваться указанный префикс --test- (обратите внимание на то, что префикс задаётся с - в конце).
Значения по умолчанию для входов (in_xxx) и выходов (out_xx) можно также определять через аргументы командной строки:
Дополнительно при помощи командной строки можно переопределять следующие свойства:
Для режима генерирования на основе отдельного xml-файла (Файл описания для генерирования базового класса) необходимо дополнительно производить конфигурирование. Конфигурирование - это привязка конкретных имён датчиков к указанным полям класса. Для этого в конфигурационном файле проекта (обычно в секции <settings>) создаётся настроечная секция для вашего объекта. А в самом классе генерируется специальный конcтруктор, позволяющий указать настроечный xml-узел:
ClassName( UniSetTypes::ObjectId id, xmlNode* node=UniSetTypes::conf->getNode("ClassName") );
Ниже приведён пример настроечной секции, для объекта сгенерированного на основе xml-файла указанного в Файл описания для генерирования базового класса
...
<TestGen name="TestGen" startTimeout="4000" stopTimeout="2000"
input1_s="Input1_S" node_input1_s="Node2"
input2_s="DumpSensor1_S"
output1_c="DO_C"
msg1="Message1"
/>
...
Обычно для каждого объекта класса создаётся своя настроечная секция.
Дополнительно в сгенерированном коде присутствуют следующие настройки:
В генератор кода заложен специальный код для перевода процесса в тестовый режим. В этом режиме отключается вся работа процесса. Перестают обрабатываться сообщения, обновлятся входы и выходы и т.д.
Для перевода процесса в "тестовый режим" необходимо задать идентификаторы для двух "DI" датчиков:
Переход в тестовый режим осуществляется, только если ОБА датчика станут равными "1".
uniset-codegen поддерживает генерирование двух видов процессов:
По умолчанию используется шаблон для "пассивнного процесса". Т.е. все "in" датчики будут "заказаны" при старте процесса и далее работа будет вестись по сообщениям об изменении (UniSetTypes::SensorMessage).
Для генерирования активного процесса необходимо использовать параметр --no-ask. В таком процессе происходит активная работа с датчиками. Т.е. на каждом шаге основного цикла, происходит "принудительное" обновление значений всех "входов" (getValue) независимо от того, менялись ли они.
Шаблон "Alone" предназначен для генерирования без использования специального xml-файла с описанием переменных. Генерирование происходит непосредственно по конфигурационному файлу проекта. Для этого всё-равно необходимо создать соответствующую настроечную секцию, в которой будут прописаны параметры необходимые для генерирования "SK"-класса. При этом используемые "входы" и "выходы" записываются непосредственно у каждого используемого датчика в секции <consumers>. Ниже приведён пример конфигурирования процесса файле проекта:
...
<settings>
...
<TestGenAlone name="TestGenAlone">
<set name="ID" val="TestGenAlone"/>
<set name="class-name" val="TestGenAlone"/>
<set name="msg-count" val="20"/>
<set name="sleep-msec" val="150"/>
</TestGenAlone>
...
</settings>
...
<sensors>
<item id="1" name="input1_s" iotype="DI" textname="xxx">
<consumers>
<consumer name="TestGenAlone" vartype="in" type="objects"/>
</consumers>
</item>
<item id="23" name="input2_s" iotype="DI" textname="xxx">
<consumers>
<consumer name="TestGenAlone" vartype="in" type="objects"/>
</consumers>
</item>
<item id="31" name="output1_c" iotype="DO" textname="xxx" node="Test1Node">
<consumers>
<consumer name="TestGenAlone" vartype="out" type="objects"/>
</consumers>
</item>
</sensors>
<objects>
<item id="2000" name="TestGenAlone" />
</objects>
Как видно из примера, vartype переменных записывается непосредственно в свойствах <consumer>.
Типичное правило для генерирования в Makefile.am выглядит следующим образом:
..._SOURCES= MyClass_SK.cc ...
MyClass_SK.cc: myclass.src.xml
@UNISET_CODEGEN@ -n MyClass --no-main myclass.src.xml
В этом примере
Для логирования событий каждый класс содержит объект mylog (класса DebugStream) и несколько предопределённых макросов для удобства использования.
Так же можно использовать функции напрямую:
mylog->info() << "....test..." << endl; mylog->log1() << "....test..." << endl;
Помимо объекта mylog класс содержит ряд вспомогательных функций:
ObjectName:
in_input1_s(Sensor1_S)=1
in_output2_c(Output1_C)=0
...
.. myinfo << str(input1_s) << endl;
.. myinfo << strval(input1_s) << endl;
Для динамического управления логами процесса предусмотрен механизм, который позволяет указать аналговый датчик (AI), значение которого будет использовано как указание уровня вывода логов (см. Debug::type). Чтобы назначить специальный "вход" (vartype="in") для управления логами, достаточно в секции <smap> ТОЛЬКО ДЛЯ ОДНОГО входа указать loglevel="1". Пример:
<smap>
...
<item name="loglevel_s" vartype="in" comment="log level control" loglevel="1"/>
</smap>
Данный механизм позволяет посредством этого датчика управлять уровнем вывода логов (mylog) во время работы процесса. Для удобства предусмотрена утилита uniset2-log2val позволяющая преобразовать текстовые названия уровней в число, которое необходимо выставить датчику. Пример:
uniset2-log2val info,level1,warn,level9
4117
Т.е. для того, чтобы включить указанные логи в датчик нужно записать число 4117
Для возможности удалённого управления логами (включением, отключением, просмотром, и т.п.) в каждом объекте предусмотрена возможность запуска LogServer, который запускается на порту равном ID-объекта. По умолчанию LogServer не запускается (выключен). Для его запуска необходимо указать:
--argprefix-run-logserver
Дополнительно доступны следующие аргументы:
--argprefix-logserver-host - по умолчанию localhost
--argprefix-logserver-port - по умолчанию ID-объекта
Непосредственно управление логами производиться при помощи утилиты uniset2-log
-h, --help - this message -v, --verbose - Print all messages to stdout [-i|--iaddr] addr - LogServer ip or hostname. [-p|--port] port - LogServer port. [-c|--command-only] - Send command and break. (No read logs). [-w|--timeout] msec - Timeout for wait data. Default: 0 - endless waiting [-x|--reconnect-delay] msec - Pause for repeat connect to LogServer. Default: 5000 msec. Commands: [--add | -a] info,warn,crit,... [logfilter] - Add log levels. [--del | -d] info,warn,crit,... [logfilter] - Delete log levels. [--set | -s] info,warn,crit,... [logfilter] - Set log levels. --off, -o [logfilter] - Off the write log file (if enabled). --on, -e [logfilter] - On(enable) the write log file (if before disabled). --rotate, -r [logfilter] - rotate log file. --list, -l [logfilter] - List of managed logs. --filter, -f logfilter - ('filter mode'). View log only from 'logfilter'(regexp) Note: 'logfilter' - regexp for name of log. Default: ALL logs.
В генерируемом коде встроена поддержка удалённого просмотра (монторинга) внутреннего состояния ВСЕХ in_xx и out_xxx переменных, а также переменных определённых пользователем. Для этого достаточно использовать утилиту uniset2-vmonit
Usage: uniset2-vmonit [-s watch_sec] OBJECT_ID[@node] [uniset-admin-options]
Где можно указать (-s sec) как часто обновлять информацию и собственно указать ID (или name) объекта (процесса).
Утилита требует наличия в системе утилиты watch и на самом деле является обёрткой над утилитой uniset2-admin.
Например для отслеживания процесса 12000 утилита запускается так:
uniset2-vmonit 12000
Для добавления своей переменной в "мониторинг" достаточно один раз (например в конструкторе своего класса) вызвать функцию (на самом деле макрос) vmonit(var); Пример:
class MyClass: public MyClass_SK: { public: MyClass(...) { vmonit(my_bool); vmonit(my_int); vmonit(my_long); vmonit(my_double); vmonit(my_float); } private: bool my_bool; int my_int; long my_long; double my_double; float my_float; ... }
После этого они появяться в выводе утилиты uniset2-vmonit
Для пользовательской информации введена виртуальная функция std::string getMonitInfo(), переопределив которую, можно сформировать свою информацию, которую можно будет удалённо читать.
1.7.6.1